标签: python
花了两天,终于把 Python 的 setup.py 给整明白了
来自公众号:Python编程时光 
1. 为什么需要对项目分发打包?
平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 打包。
打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。
不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。
Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢?
你可能听过 disutils、 distutils 、distutils2、setuptools等等,好像很熟悉,却又很陌生,他们都是什么关系呢?
2. 包分发的始祖:distutils
distutils 是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。
distutils 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。
那么如何编写 setup.py 呢?这里面的内容非常多,我会在后面进行详细的解析,请你耐心往下看。
你有可能没写过 setup.py ,但你绝对使用过 setup.py 来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。
$ python setup.py install
这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装,同样我也会在后面进行介绍。
3. 分发工具升级:setuptools
setuptools 是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。
distribute,或许你在其他地方也见过它,这里也提一下。
distribute 是 setuptools 有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools 开发太慢了。但现在,distribute 又合并回了 setuptools 中。因此,我们可以认为它们是同一个东西。
还有一个大包分发工具是 distutils2,其试图尝试充分利用distutils,detuptools 和 distribute 并成为 Python 标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。
因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。
那么如何在一个干净的环境中安装 setuptools 呢?
主要有两种方法:
- 
源码安装:在 https://pypi.org/project/setuptools/#files 中下载 zip 包 解压执行
python setup.py install安装 - 
通过引导程序安装:下载引导程序,它可以用来下载或者更新最新版本的 setuptools
 
$ wget http://peak.telecommunity.com/dist/ez_setup.py
# 安装
$ python ez_setup.py
# 更新,以下两种任选
$ python ez_setup.py –U setuptools
$ pip install -U setuptools
4. easy_install 使用指南
当你安装完 setuptools 后,就拥有了一个叫做 easy_install 的第三方管理工具,这也是它区分于 distutils 的一大改进。
这里简单介绍一下它的用法,虽然它已经用得非常少了。
先是包的安装
# 通过包名,从PyPI寻找最新版本,自动下载、编译、安装
$ easy_install pkg_name
# 通过包名从指定下载页寻找链接来安装或升级包
$ easy_install -f http://pythonpaste.org/package_index.html 
# 指定线上的包地址安装
$ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz
# 从本地的 .egg 文件安装
$ easy_install xxx.egg
# 在安装时你可以添加额外的参数
指定安装目录:--install-dir=DIR, -d DIR
指定用户安装:--user
再者是包的升级
# 从 pypi 中搜索并升级包
$ easy_install --upgrade pkg_name
# 指定版本进行升级
$ easy_install "SomePackage==2.0"
最后是包的删除
$ easy_install -m pkg_name
需要注意的是,这样的删除,仅是在 easy-install.pth 文件中删除,使其不能在 python 中使用 这个模块,但实际的包还在你的电脑中,若要删除彻底,需要你手动删除相关的 .egg 及 其他文件。
默认情况下,easy_install 只会从 pypi 上下载相关软件包,由于这个源在国外,下载包的速度并不理想,使用过pip的朋友自然会想,easy_install 是否能指定源进行安装呢?
答案是,可以的。
编辑配置文件 /root/.pydistutils.cfg
[easy_install]
index-url=http://mirrors.aliyun.com/pypi/simple/
find-links=http://mirrors.aliyun.com/pypi/simple/
以上仅介绍了 easy_install 的一些常用的方法,想要了解更多,你可以点击官方文档:https://setuptools.readthedocs.io/en/latest/easy_install.html
总结一句:setuptools 是官方提供的一个专业用于包分发的工具,若只从安装的角度来看,它的功能确实简单。它更大的意义是对包的分发很有用,定制化程序非常高,我们现在也还在用它进行版本包的发布。
5. 源码包与二进制包什么区别?
Python 包的分发可以分为两种:
- 
以源码包的方式发布
 
源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。
源码包的本质是一个压缩包,其常见的格式有:

- 
以二进制包形式发布
 
二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。
由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。
二进制包的常见格式有:

6. eggs 与 wheels 有什么区别?
Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 Python 的二进制包的标准格式。
以下是 Wheel 和 Egg 的主要区别:
- 
Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义
 - 
Wheel 是一种分发格式,即打包格式。而 Egg 既是一种分发格式,也是一种运行时安装的格式,并且是可以被直接 import
 - 
Wheel 文件不会包含 .pyc 文件
 - 
Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info 目录
 - 
Wheel 有着更丰富的命名规则。
 - 
Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现
 - 
Wheel 在内部被 sysconfig path type 管理,因此转向其他格式也更容易
 
wheel 包可以通过 pip 来安装,只不过需要先安装 wheel 模块,然后再使用 pip 的命令。
$ pip install wheel
$ pip wheel --wheel-dir=/local/wheels pkg
7. 超详细讲解 setup.py 的编写?
打包分发最关键的一步是编写 setup.py 文件。
以下是一个 setup.py 简单的使用示例
from setuptools import setup, find_packages
setup(
    name="mytest",
    version="1.0",
    author="wangbm",
    author_email="wongbingming@163.com",
    description="Learn to Pack Python Module  -->公众号:Python编程时光",
    # 项目主页
    url="http://iswbm.com/", 
    # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包
    packages=find_packages()
)
接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。
程序分类信息
classifiers 参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers
示例:
from setuptools import setup, find_packages
setup(
    classifiers = [
        # 发展时期,常见的如下
        #   3 - Alpha
        #   4 - Beta
        #   5 - Production/Stable
        'Development Status :: 3 - Alpha',
        # 开发的目标用户
        'Intended Audience :: Developers',
        # 属于什么类型
        'Topic :: Software Development :: Build Tools',
        # 许可证信息
        'License :: OSI Approved :: MIT License',
        # 目标 Python 版本
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
    ]
)
关于文件的分发
from setuptools import setup, find_packages
setup(
    name="mytest",
    version="1.0",
    author="wangbm",
    author_email="wongbingming@163.com",
    description="Learn to Pack Python Module",
    url="http://iswbm.com/", 
    packages=find_packages(),
    # 安装过程中,需要安装的静态文件,如配置文件、service文件、图片等
    data_files=[
        ('', ['conf/*.conf']),
        ('/usr/lib/systemd/system/', ['bin/*.service']),
               ],
    # 希望被打包的文件
    package_data={
        '':['*.txt'],
        'bandwidth_reporter':['*.txt']
               },
    # 不打包某些文件
    exclude_package_data={
        'bandwidth_reporter':['*.txt']
               }
)
除了以上的参数配置之外,还可以使用一个叫做 MANIFEST.in 的文件,来控制文件的分发。
如下这是一个 MANIFEST.in 的样例:
include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build
这些配置,规定了如下几点
- 
所有根目录下的以 txt 为后缀名的文件,都会分发
 - 
根目录下的 examples 目录 和 txt、py文件都会分发
 - 
路径匹配上 examples/sample?/build 不会分发
 
MANIFEST.in 需要放在和 setup.py 同级的顶级目录下,setuptools 会自动读取该文件。
关于依赖包下载安装
from setuptools import setup, find_packages
setup(
    ...
    # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中下载安装
    install_requires=['docutils>=0.3'],
    # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置
    # 这里列出的包,不会自动安装。
    setup_requires=['pbr'],
    # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。
    # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。
    tests_require=[
        'pytest>=3.3.1',
        'pytest-cov>=2.5.1',
    ],
    # 用于安装setup_requires或tests_require里的软件包
    # 这些信息会写入egg的 metadata 信息中
    dependency_links=[
        "http://example2.com/p/foobar-1.0.tar.gz",
    ],
    # install_requires 在安装模块时会自动安装依赖包
    # 而 extras_require 不会,这里仅表示该模块会依赖这些包
    # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装
    extras_require={
        'PDF':  ["ReportLab>=1.2", "RXP"],
        'reST': ["docutils>=0.3"],
    }
)
关于 install_requires, 有以下五种常用的表示方法:
- 
'argparse',只包含包名。这种形式只检查包的存在性,不检查版本。方便,但不利于控制风险。 - 
'setuptools==38.2.4',指定版本。这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。缺点是不利于更新,每次更新都需要改动代码。 - 
'docutils >= 0.3',这是比较常用的形式。当对某个库比较信任时,这种形式可以自动保持版本为最新。 - 
'Django >= 1.11, != 1.11.1, <= 2',这是比较复杂的形式。如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。对于一些大型、复杂的库,这种形式是最合适的。 - 
'requests[security, socks] >= 2.18.4',这是包含了额外的可选依赖的形式。正常安装requests会自动安装它的install_requires中指定的依赖,而不会安装security和socks这两组依赖。这两组依赖是定义在它的extras_require中。这种形式,用在深度使用某些库时。 
关于安装环境的限制
有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 Python 环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。
这样的功能,可以使用 python_requires 来实现。
setup(
    ...
    python_requires='>=2.7, <=3',
)
生成可执行文件的分发
from setuptools import setup, find_packages
setup(
    name="mytest",
    version="1.0",
    author="wangbm",
    author_email="wongbingming@163.com",
    description="Learn to Pack Python Module",
    url="http://iswbm.com/", 
    packages=find_packages(),
    # 用来支持自动生成脚本,安装后会自动生成 /usr/bin/foo 的可执行文件
    # 该文件入口指向 foo/main.py 的main 函数
    entry_points={
        'console_scripts': [
            'foo = foo.main:main'
        ]
    },
    # 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中
    # 执行 python setup.py install 后
    # 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.py
    scripts=['bin/foo.sh', 'bar.py']
)
上面的 scripts 里有的脚本中有 sh 和 py 后缀,那么安装后,setuptools 会原封不动的移动到 /usr/bin 中,并添加可执行权限。
若你想对这些文件再作一些更改,比如去掉多余的后缀,可以这样做
from setuptools.command.install_scripts import install_scripts
class InstallScripts(install_scripts):
    def run(self):
        setuptools.command.install_scripts.install_scripts.run(self)
        # Rename some script files
        for script in self.get_outputs():
            if basename.endswith(".py") or basename.endswith(".sh"):
                dest = script[:-3]
            else:
                continue
            print("moving %s to %s" % (script, dest))
            shutil.move(script, dest)
setup(
    ...
    scripts=['bin/foo.sh', 'bar.py'],
    cmdclass={
        "install_scripts": InstallScripts
    }
)
ext_modules
ext_modules 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension 实例的列表,每一个 Extension 实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如:
setup(
    # other arguments here...
    ext_modules=[
        Extension('foo',
                  glob(path.join(here, 'src', '*.c')),
                  libraries = [ 'rt' ],
                  include_dirs=[numpy.get_include()])
    ]
)
详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options
指定release
setup.py 里只能指定 version,而不能指定 release,如果你需要变更版本号,可以使用 --release 参数进行指定
python setup.py bdist_rpm --release=20200617
setup.py 的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 setup 函数常用的一些参数:

更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html
8. 打包辅助神器PBR 是什么?
pbr 是 setuptools 的辅助工具,最初是为 OpenStack 开发(https://launchpad.net/pbr),基于d2to1。
pbr 会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给 setup.py 作为参数。包含如下功能:
- 
从git中获取Version、AUTHORS and ChangeLog信息
 - 
Sphinx Autodoc。pbr 会扫描project,找到所有模块,生成stub files
 - 
Requirements。pbr会读取requirements.txt,生成setup函数需要的
install_requires/tests_require/dependency_links 
这里需要注意,在 requirements.txt 文件的头部可以使用:--index https://pypi.python.org/simple/,这一行把一个抽象的依赖声明如 requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from pypi.python.org/simple/
- 
long_description。从README.rst, README.txt or README file中生成
long_description参数 
使用pbr很简单:
from setuptools import setup
setup(
    setup_requires=['pbr'],
    pbr=True,
)
使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key:
packages:指定需要包含的包,行为类似于setuptools.find_packages
namespace_packages:指定namespace packages
data_files: 指定目的目录和源文件路径,一个示例:
[files]
data_files =
    etc/pbr = etc/pbr/*
    etc/neutron =
        etc/api-paste.ini
        etc/dhcp-agent.ini
    etc/init.d = neutron.init
[entry_points] 段跟 setuptools 的方式相同。
到此,我讲了三种编写使用 setup.py 的方法
- 
使用命令行参数指定,一个一个将参数传递进去(极不推荐)
 - 
在 setup.py 中的setup函数中指定(推荐使用)
 - 
使用 pbr ,在 setup.cfg 中指定(易于管理,更推荐)
 
9. 如何使用 setup.py 构建包
1、构建源码发布包。
用于发布一个 Python 模块或项目,将源码打包成 tar.gz (用于 Linux 环境中)或者 zip 压缩包(用于 Windows 环境中)
$ python setup.py sdist
那这种包如何安装呢?
答案是,使用下一节即将介绍的 setuptools 中提供的 easy_install 工具。
$ easy_install xxx.tar.gz
使用 sdist 将根据当前平台创建默认格式的存档。在类 Unix 平台上,将创建后缀后为 .tar.gz  的 gzip 压缩的tar文件分发包,而在Windows上为 ZIP 文件。
当然,你也可以通过指定你要的发布包格式来打破这个默认行为
$ python setup.py sdist --formats=gztar,zip
你可以指定的格式有哪些呢?
创建一个压缩的tarball和一个zip文件。可用格式为:

对以上的格式,有几点需要注意一下:
- 
在版本3.5中才添加了对
xztar格式的支持 - 
zip 格式需要你事先已安装相应的模块:zip程序或zipfile模块(已成为Python的标准库)
 - 
ztar 格式正在弃用,请尽量不要使用
 
另外,如果您希望归档文件的所有文件归root拥有,可以这样指定
python setup.py sdist --owner=root --group=root
2、构建二进制分发包。
在windows中我们习惯了双击 exe 进行软件的安装,Python 模块的安装也同样支持 打包成 exe 这样的二进制软件包。
$ python setup.py bdist_wininst
而在 Linux 中,大家也习惯了使用 rpm 来安装包,对此你可以使用这条命令实现 rpm 包的构建
$ python setup.py bdist_rpm
若你喜欢使用 easy_install 或者 pip 来安装离线包。你可以将其打包成 egg 包
$ python setup.py bdist_egg
若你的项目,需要安装多个平台下,既有 Windows 也有 Linux,按照上面的方法,多种格式我们要执行多次命令,为了方便,你可以一步到位,执行如下这条命令,即可生成多个格式的进制包
$ python setup.py bdist
10. 如何使用 setup.py 安装包
正常情况下,我们都是通过以上构建的源码包或者二进制包进行模块的安装。
但在编写 setup.py 的过程中,可能不能一步到位,需要多次调试,这时候如何测试自己写的 setup.py 文件是可用的呢?
这时候你可以使用这条命令,它会将你的模块安装至系统全局环境中
$ python setup.py install
如若你的项目还处于开发阶段,频繁的安装模块,也是一个麻烦事。
这时候你可以使用这条命令安装,该方法不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试。
$ python setup.py develop
11. 如何发布包到 PyPi?
通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 share 给其他人使用,你可以将其上传到 PyPi (Python Package Index)上,它是 Python 官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。
如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 ~/.pypirc 文件,此文件中配置 PyPI 访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。
典型的 .pypirc 文件
[distutils]
index-servers = pypi
[pypi]
username:xxx
password:xxx
然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。
$ python setup.py register
注册完了后,你还要上传源码包,别人才使用下载安装
$ python setup.py upload
或者也可以使用 twine 工具注册上传,它是一个专门用于与 pypi 进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。
参考文章
- 
http://blog.konghy.cn/2018/04/29/setup-dot-py/
 - 
https://note.qidong.name/2018/01/python-setup-requires/
 
开源框架 Zappa:上线 Python 应用仅需一条命令!
作者:HelloGitHub-吱吱
这里是 HelloGitHub 推出的《讲解开源项目》系列,今天要向小伙伴们介绍一个 Python 无服务(Serverless)框架 Zappa。
Zappa 让我们可以轻松部署 Python 应用程序:仅需几条命令、打包代码、上传云服务器、程序上线,bingo 一气呵成!从此减少部署成本,放下运维的重担。仅需你有一点点 Python Web 基础!
它到底有多便捷?一条命令即刻部署!

项目地址:https://github.com/Miserlou/Zappa
下面就让我们动手来试试吧!
一、前言
1.1 介绍 Serverless
刚开篇便提到了一个莫名其妙的名词:无服务(Serverless),一开始我也是问号脸,经过多方搜证,我们可以简单的认为 Serverless 是指不必担心底层基础结构,不需要管理服务器,从而来构建和运行应用程序。具体概念小课堂如下:
1.1.1 什么鬼?
回忆一下,平时上线一个简单的 Python Web 应用的过程。
- 
一个 24 小时不间断运行的服务器:比如云主机,用以搭建代码运行环境和进行系统配置,维持着运行我们的应用;  - 
部署 Web 服务器:我们需要选择合适的 Web 服务器,经过配置和启动,实现反向代理和负载均衡;  - 
域名绑定:最后如果要被广泛用户访问,我们需要注册域名,并且绑定在服务器;  - 
运营维护:配置和启动在应用上线之后,我们还需要管理和维护我们的服务器,预防黑客攻击,应对未来用户访问高峰期。  
而对于使用 Serverless 架构的应用,我们只需要关心我们的应用编写和核心业务,无需操心云主机、操作系统、资源分配和 Web 服务器配置等相关问题,无需考虑服务器的规格大小、存储类型、网络带宽、自动扩缩容问题,无需再对服务器进行运维、不断打系统补丁和应用补丁、无需进行数据备份等工作。一切非核心业务都外包给了公共云营运商,让开发人员从复杂的部署和运维环境中脱身出来,专注于业务本身的价值。
用 Zappa 里的一句话说就是 “without any permanent infrastructure”(无需任何永久性基础设施)。
敲黑板,尽管从名字上说是 Serverless,但是仍然需要物理服务器,只是我们开发人员成了甩手掌柜。
1.1.2 好处有?
- 
降低运维需求和维护成本;  - 
完全自动化的弹性扩容和缩容:在业务高峰期时,产品的计算能力和容量自动扩大,承载更多的用户请求;反之,在业务下降时,所使用的资源也会同时收缩,避免资源浪费;  - 
节省开支,全新的计量计费模式:开发者仅需根据使用量来付费。在无业务量的情况下,不会有空闲资源占用,也不会有费用产生。  
1.1.3 普遍认为 Serverless = FaaS + BaaS
- 
BaaS(Backedn as a Service 后端即服务) - 
后端,指的就是各种云产品和云服务,例如对象存储 OS ,消息队列 MQ,云数据库 DB,云缓存 Redis以及各种以 API 形式提供的服务。用户直接开通即可使用,无需考虑部署、扩容、备份、安全等各种运维工作。  
 - 
 - 
FaaS(Functions as a Service 函数即服务) - 
是 Serverless 的核心,让用户仅需编写和上传核心业务代码,交由平台完成部署、调度、流量分发和弹性伸缩等能力,它提供了一种新的方式来提供计算资源,进一步降低云计算的使用门槛。  
 - 
 
1.1.4 AWS Lambda
在该项目中,伸手白piao AWS 海外区域账户免费 AWS Lambda 套餐。AWS Lambda 作为 Serverless 最早的框架产品由亚马逊在2014年推出,是一种无服务器的计算服务,无需预置或管理服务器即可运行代码。Lambda 几乎可以为任何类型的应用程序或后端服务运行代码,我们只需上传相应的代码,它会处理运行和扩展代码所需的一切工作。

1.2 Python 的 Serverless 框架
本篇文章的主角:Zappa 登场!我们可以通过 Zappa 工具体验一下 Serverless 技术,用它实现我们 Python 应用程序的无服务器部署,初步体验无限伸缩扩展、零宕机、零维护的快捷。有了 Zappa,我们无需:
- 
配置 Web 服务器  - 
付费 24/7 服务器的正常运行时间  - 
担心负载平衡和可扩展性  - 
保持自己的服务器时刻在线状态  
二、亲自动手
实战时间:已经实验(踩坑)成功(不断)的我就来分享部署一个简单的 Flask 应用的过程,不要担心跟着做你也可以~
2.1 环境
- 
Python版本要求:3.6/3.7/3.8  - 
测试系统:Ubuntu 18.04.4 LTS  
2.2 准备
- 
保证自己的项目是运行在虚拟环境下。
# 需要安装 Python 3.x 版本
python --version
# 安装 Pipenv
pip install --user pipenv
# 进入自己的项目
cd demo
# 实例化 pipfile 和 venv
pipenv shell
 - 
安装 Zappa 和 Flask,项目需要其他库的话,可自行添加。
nbsp;pipenv install zappa flask
 - 
在目录下创建
my_app.py文件,写入官方样例,可以先pipenv run python my_app.py看看是否能正常运行from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "hello, from Zappa!n"
if __name__ == '__main__':
app.run() - 
注册 AWS 账户,并且正确安装 AWS credentials file
- 
登录 AWS,找到
My Security Credentials下的Access keys (access key ID and secret access key),如果没有则创建一个,记住access key ID和secret access key - 
安装 AWS 的命令行界面,添加 credentials
# 在虚拟环境下安装
pipenv install awscli
# 查看信息
aws configure list
# 添加,并且按照提示将 access key ID 和 secret access key 填入
aws configure
# 后两个 region name 和 output format 选填
 - 
此时在
~/.aws目录下会出现两个文件 config 和 credentials,credentials 中储存了 AWS 的access key ID和secret access key, config 中储存了 region name 和 output format 信息。 - 
如果是在 Windows 上操作的同学,可以查看官方提供的安装 AWS credentials file 的教程。
 
 - 
 
2.3 安装与配置
- 
通过执行下面语句进行初始化,定义部署和配置的设置,自动检测应用类型(Flask 或 Django)
nbsp;zappa init在执行过程中,可能需要如下设置,后续也可以在新生成的
zappa_setting.json的配置文件中修改:
完成后,我们的项目目录中将有一个
zappa_settings.json文件,里面是我们刚刚定义的基本部署设置,后期我们可以按照自己的需求修改此文件。{
"dev": {
"app_function": "my_app.app",
"profile_name": null,
"project_name": "demo",
"runtime": "python3.6",
"s3_bucket": "zappa-ti0ra29xi"
}
} - 
注意如果之前已经在
~/.aws/config文件中添加 region 信息,则会在zappa init的时候自动寻找到这些 region 信息,无需后续修改。如果之前没有添加,则修改
zappa_settings.json,添加 region 信息如下:# 修改如下
{
"dev": {
"app_function": "my_app.app",
"profile_name": null,
"project_name": "demo",
"runtime": "python3.6",
"s3_bucket": "zappa-ti0ra29xi"
"aws_region": "us-west-2"
}
}region 的信息可以自行选择。
 
2.4 部署和使用
配置设置后,可以使用如下命令将应用程序打包并部署:
nbsp;zappa deploy dev
当我们调用 deploy 时,Zappa 会自动将我们的应用程序和本地虚拟环境打包到 Lambda 兼容的 archive,用为 Lambda 预先编译的版本替换所有依赖项,设置功能处理程序和必要的 WSGI 中间件,然后上传 archive 到 S3,创建和管理必要的Amazon IAM 策略和角色,将其注册为新的 Lambda function,创建新的 API 网关资源,为其创建 WSGI 兼容的路由,将其链接到新的 Lambda function,最后从 S3 bucket 中删除 archive。
执行成功后,就会出现一个链接,点击链接即可访问我们的简易 Web 应用。看到已上线的应用程序,心内窃喜,直呼快准狠。

三、其他命令
- 
更新操作:假设应用程序已经部署完毕,并且只需要上传新的 Python 代码,而无需修改基础路由,则可以执行以下操作:
nbsp;zappa update dev这将创建一个新的 archive,将其上传到 S3 并更新 Lambda function 以使用新代码。
 - 
查看部署和事件计划的状态,只需使用命令:
nbsp;zappa status production - 
查看部署的日志:
nbsp;zappa tail dev
# 过滤 HTTP 请求nbsp;zappa tail dev --http
# 执行相反操作,并且仅显示非 HTTP 事件和日志消息nbsp;zappa tail dev --non-http
# 选择时长nbsp;zappa tail dev --since 4h # 4 hours - 
回滚操作:通过提供要返回的修订版本数将部署的代码回滚到以前的版本。
# 回滚到3年前部署的版本nbsp;zappa rollback production -n 3 - 
安排 function 定期执行:修改
zappa_setting.json,加入如下内容:{
"dev": {
...
"events": [{
// The function to execute
"function": "your_module.your_function",
// When to execute it (in cron or rate format)
"expression": "rate(1 minute)"
}],
...
}
}然后执行如下操作,我们的 function 就会在每分钟执行一次。
nbsp;zappa schedule dev
# cancalnbsp;zappa unschedule dev - 
取消部署:如果要删除以前发布的 API Gateway 和 Lambda function,则只需:
nbsp;zappa unschedule dev 
四、踩坑建议
在成功运行一次之前,踩坑千千万万遍,都是因为自己手残眼瞎魔改了很多地方,把经历过的报错记录下来,分享给和我一样的小小白。
- 
“Unable to import module ‘handler’: attempted relative import with no known parent package”:原因是我们期望的依赖在虚拟的环境中没有,需要查看自己虚拟环境中的依赖是否完整。 - 
出现如下报错,可以更换一个 region 信息。
nbsp;zappa deploy dev
Calling deploy for stage dev..
Creating demo-dev-ZappaLambdaExecutionRole IAM Role..
Error: Failed to manage IAM roles!
You may lack the necessary AWS permissions to automatically manage a Zappa execution role.
Exception reported by AWS:An error occurred (InvalidClientTokenId) when calling the CreateRole operation: The security token included in the request is invalid.
To fix this, see here: https://github.com/Miserlou/Zappa#custom-aws-iam-roles-and-policies-for-deployment - 
如果我们在
zappa init的时候,不使用默认分配的s3_bucket,则须注意自己的名称是不允许重名的,否则会报错botocore.errorfactory.BucketAlreadyExists: An error occurred (BucketAlreadyExists) when calling the CreateBucket operation: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.。 
五、写在最后
是不是当自己成功部署后,突然觉得妙不可言,一身轻松,好像再也没有了之前所说的繁琐的过程,反而几条命令,白piao AWS 的服务,咱的应用程序就轻巧上线了呢,还不赶紧把生成的链接分享给小伙伴们点击一下。
至此,我们已经可以基本实现快速部署一个简单的 Flask 应用了,由于篇幅有限,还有部分 Zappa 的高级功能没有提及,以及如何使用 Zappa 部署 Django 应用或者一个更为庞大的项目(包含数据库等),希望感兴趣的小伙伴们能够多多尝试,我已经开始期待得搓搓手了
转自:https://mp.weixin.qq.com/s/Yfg163FzIY4kZpeTKoU05w
Python 如何仅用 5000 行代码,实现强大的 logging 模块?
者:肖恩
来源:游戏不存在
Python 的 logging 模块实现了灵活的日志系统。整个模块仅仅 3 个类,不到 5000 行代码的样子,学习它可以加深对程序日志的了解,本文分下面几个部分:
- 
logging 简介  - 
logging API 设计  - 
记录器对象 Logger  - 
日志记录对象 LogRecord  - 
处理器对象 Hander  - 
格式器对象 Formatter  - 
滚动日志文件处理器  - 
小结  - 
小技巧  
logging 简介
本次代码使用的是 python 3.8.5 的版本,官方中文文档 3.8.8 。参考链接中官方中文文档非常详细,建议先看一遍了解日志使用。
| 类 | 功能 | 
|---|---|
| logging-module | logging的API | 
| Logger | 日志记录器对象类,可以创建一个对象用来记录日志 | 
| LogRecord | 日志记录对象,每条日志记录都封装成一个日志记录对象 | 
| Hander | 处理器对象,负责日志输出到流/文件的控制 | 
| Formatter | 格式器,负责日志记录的格式化 | 
| RotatingFileHandler | 按大小滚动的日志文件记录器 | 
| TimedRotatingFileHandler | 按时间滚动的日志文件处理器 | 
我们主要研究日志如何输出到标准窗口这一主线;日志的配置,日志的线程安全及各种特别的Handler等支线可以先忽略。
logging API 设计
先看看日志使用:
import logging
logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(name)-10s %(asctime)s %(message)s')
lang = {"name": "python", "age":20}
logging.info('This is a info message %s', lang)
logging.debug('This is a debug message')
logging.warning('This is a warning message')
logger = logging.getLogger(__name__)
logger.warning('This is a warning')
输出内容如下:
INFO     root       2021-03-04 00:03:53,473 This is a info message {'name': 'python', 'age': 20}
WARNING  root       2021-03-04 00:03:53,473 This is a warning message
WARNING  __main__   2021-03-04 00:03:53,473 This is a warning
可以看到 logging 的使用非常方便,模块直接提供了一组API。
root = RootLogger(WARNING)  # 默认提供的logger
Logger.root = root
Logger.manager = Manager(Logger.root)
def debug(msg, *args, **kwargs): # info,warning等api类似
    if len(root.handlers) == 0:
        basicConfig()  # 默认配置
    root.debug(msg, *args, **kwargs)
def getLogger(name=None):
    if name:
        return Logger.manager.getLogger(name)  # 创建特定的logger
    else:
        return root  # 返回默认的logger
这种API的提供方式,我们在requests中也有看到。api中很重要的设置config的方式:
def basicConfig(**kwargs):
    ...
    if handlers is None:
        filename = kwargs.pop("filename", None)
        mode = kwargs.pop("filemode", 'a')
        if filename:
            h = FileHandler(filename, mode)
        else:
            stream = kwargs.pop("stream", None)
            h = StreamHandler(stream)  # 默认的handler
        handlers = [h]
    dfs = kwargs.pop("datefmt", None)
    style = kwargs.pop("style", '%')
    fs = kwargs.pop("format", _STYLES[style][1])
    fmt = Formatter(fs, dfs, style)  # 生成formatter
    for h in handlers:
        if h.formatter is None:
            h.setFormatter(fmt)
        root.addHandler(h)  # 设置root的handler
    level = kwargs.pop("level", None)
    if level is not None:
        root.setLevel(level)  # 设置日志级别
可以看到,日志的配置主要包括下面几项:
- 
level 日志级别  - 
format 信息格式化模版  - 
filename 输出到文件  - 
datefmt %Y-%m-%d %H:%M:%S,uuu时间的格式模版 - 
style [ %,{,$] 格式样板 
演示代码输出中,可以看到debug日志没有显示,是因为 debug < info:
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
记录器对象 Logger
查看Logger之前,先看logger对象的管理类Manager
_loggerClass = Logger
class Manager(object):
    def __init__(self, rootnode):
        self.root = rootnode
        self.disable = 0
        self.loggerDict = {}  # 所有日志记录对象的字典
    ...
    def getLogger(self, name):
        rv = None
        if name in self.loggerDict:
            rv = self.loggerDict[name]  # 获取已经创建过的同名logger
            ...
        else:
            rv = (self.loggerClass or _loggerClass)(name)  # 创建新的logger
            rv.manager = self
            self.loggerDict[name] = rv
            ...
        return rv
日志过滤器
class Filterer(object):
    def __init__(self):
        self.filters = []
    def addFilter(self, filter):
        self.filters.append(filter)
    def removeFilter(self, filter):
        self.filters.remove(filter)
    def filter(self, record):
        rv = True
        for f in self.filters:  # 过滤日志
            if hasattr(f, 'filter'):
                result = f.filter(record)
            else:
                result = f(record) # assume callable - will raise if not
            if not result:
                rv = False
                break
        return r
核心的 Logger 实际上只是一个控制中心:
class Logger(Filterer):  # logger可以过滤日志
    def __init__(self, name, level=NOTSET):
        Filterer.__init__(self)
        self.name = name
        self.level = _checkLevel(level)
        self.parent = None  # 日志可以有层级
        self.propagate = True
        self.handlers = []  # 可以输出到多个handler
        self.disabled = False  # 可以关闭
        self._cache = {}
    
    def debug(self, msg, *args, **kwargs):  # 输出debug日志
        if self.isEnabledFor(DEBUG):
            self._log(DEBUG, msg, args, **kwargs)
logger可以判断日志级别:
def isEnabledFor(self, level):
    if self.disabled:
        return False
    try:
        return self._cache[level]
    except KeyError:
        try:
            if self.manager.disable >= level:
                is_enabled = self._cache[level] = False
            else:
                is_enabled = self._cache[level] = (
                    level >= self.getEffectiveLevel()
                )
        return is_enabled
def getEffectiveLevel(self):
    logger = self
    while logger:
        if logger.level:
            return logger.level
        logger = logger.parent
    return NOTSET
日志输出:
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False,
         stacklevel=1):
    ...
    fn, lno, func = "(unknown file)", 0, "(unknown function)"
    ...
    # 生成日志记录
    record = self.makeRecord(self.name, level, fn, lno, msg, args,
                             exc_info, func, extra, sinfo)
    # 使用handler处理日志
    self.handle(record)
日志记录的生产,就是创建一个LogRecord对象:
_logRecordFactory = LogRecord
def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
               func=None, extra=None, sinfo=None):
    ...
    rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
                         sinfo)
    ...
    return rv
使用logger对象的所有handler处理日志:
def handle(self, record):
    c = self
    found = 0
    while c:
        for hdlr in c.handlers:  # 使用所有的handler处理日志
            found = found + 1
            if record.levelno >= hdlr.level:
                hdlr.handle(record)
root-logger 的handler是在config中配置的:
def basicConfig(**kwargs):
    ...
    root.addHandler(h)  # 设置root的handler
日志记录对象 LogRecord
日志记录对象非常简单:
class LogRecord(object):
    def __init__(self, name, level, pathname, lineno,
                 msg, args, exc_info, func=None, sinfo=None, **kwargs):
        ct = time.time()
        self.name = name  # logger名称
        self.msg = msg  # 日志标识信息
        ...
        self.args = args  # 变量
        self.levelname = getLevelName(level)
        ...
    
    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            msg = msg % self.args  # 格式化消息
        return msg
处理器对象 Hander
顶级Handler定义了Handler的模版方法
class Handler(Filterer):  # 处理器也可以过滤日志
    def __init__(self, level=NOTSET):
        Filterer.__init__(self)
        self._name = None
        self.level = _checkLevel(level)  # handler也有日志级别
        self.formatter = None
        _addHandlerRef(self)
        self.createLock()
        
    def handle(self, record):  # 处理日志
        rv = self.filter(record)  # 过滤日志
        if rv:
            self.acquire()  # 申请锁
            try:
                self.emit(record)  # 提交记录,由不同子类实现 
            finally:
                self.release()  # 释放锁
        return rv
默认的console流 StreamHandler
class StreamHandler(Handler):
    terminator = 'n'  # 自动换行
    def __init__(self, stream=None):
        Handler.__init__(self)
        if stream is None:
            stream = sys.stderr  # 默认使用stderr输出
        self.stream = stream
    
    def emit(self, record):
        try:
            msg = self.format(record)  # 格式化日志记录
            stream = self.stream
            stream.write(msg + self.terminator)  # 写日志
            self.flush()  # 刷新写缓存
        except Exception:
            ...
    
    def format(self, record):
        if self.formatter:
            fmt = self.formatter
        else:
            fmt = _defaultFormatter
        return fmt.format(record)  # 使用格式化器格式化日志记录
为什么使用stderr,可以看下面的测试中的输出都是到console:
print("haha")
print("fatal error", file=sys.stderr)
sys.stderr.write("fatal errorn")
格式器对象 Formatter
格式化器主要使用Formatter和Style实现
class Formatter(object):
    def __init__(self, fmt=None, datefmt=None, style='%', validate=True):
        self._style = _STYLES[style][0](fmt)
        self._fmt = self._style._fmt
        self.datefmt = datefmt
    
    def format(self, record):
        record.message = record.getMessage()
        s = self.formatMessage(record)
        return s
        
    def formatMessage(self, record):
        return self._style.format(record)  # 格式化
Style类
class PercentStyle(object):
    default_format = '%(message)s'
    asctime_format = '%(asctime)s'
    asctime_search = '%(asctime)'
    validation_pattern = re.compile(r'%(w+)[#0+ -]*(*|d+)?(.(*|d+))?[diouxefgcrsa%]', re.I)
    def __init__(self, fmt):
        self._fmt = fmt or self.default_format
    def usesTime(self):
        return self._fmt.find(self.asctime_search) >= 0
    def validate(self):
        """Validate the input format, ensure it matches the correct style"""
        if not self.validation_pattern.search(self._fmt):
            raise ValueError("Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0]))
    def _format(self, record):
        return self._fmt % record.__dict__  # 格式化日志记录对象
    def format(self, record):
        try:
            return self._format(record)
        except KeyError as e:
            raise ValueError('Formatting field not found in record: %s' % e)
滚动日志文件处理器
线上的日志持续输出到一个文件的话,会让文件巨大,即有加剧了丢失的风险,也难以处理。通常有按照大小滚动或者按照日期滚动的方法,这个功能非常重要。先看滚动日志处理器模版:
class BaseRotatingHandler(logging.FileHandler):
    def emit(self, record):
        try:
            if self.shouldRollover(record): # 判断是否需要滚动
                self.doRollover()  # 滚动日志
            logging.FileHandler.emit(self, record)  # 输出日志
        except Exception:
            self.handleError(record)
    
    def rotate(self, source, dest):
        if not callable(self.rotator):
            if os.path.exists(source):
                os.rename(source, dest)  # 重命名日志文件
        else:
            self.rotator(source, dest)
按大小滚动 RotatingFileHandler
按照文件大小滚动的处理器:
class RotatingFileHandler(BaseRotatingHandler):
    def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
        if maxBytes > 0:
            mode = 'a'
        BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
        self.maxBytes = maxBytes  # 单个文件大小上限
        self.backupCount = backupCount  # 日志备份数量
        
    def doRollover(self):  # 执行滚动
        if self.stream:
            self.stream.close()  # 关闭当前的流
            self.stream = None
        if self.backupCount > 0:
            for i in range(self.backupCount - 1, 0, -1):
                sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
                dfn = self.rotation_filename("%s.%d" % (self.baseFilename,
                                                        i + 1))
                if os.path.exists(sfn):
                    if os.path.exists(dfn):
                        os.remove(dfn)
                    os.rename(sfn, dfn)
            dfn = self.rotation_filename(self.baseFilename + ".1")
            if os.path.exists(dfn):
                os.remove(dfn)
            self.rotate(self.baseFilename, dfn)  # 重命名文件
        if not self.delay:
            self.stream = self._open()  # 如果shouldRollover延迟,可以打开新的流
    def shouldRollover(self, record):  # 判断是否需要滚动
        if self.stream is None:  # 立即打开流
            self.stream = self._open()
        if self.maxBytes > 0:   
            msg = "%sn" % self.format(record)
            self.stream.seek(0, 2)  #due to non-posix-compliant Windows feature
            if self.stream.tell() + len(msg) >= self.maxBytes:  # 判断大小
                return 1
        return 0
文件大小滚动就是在记录日志时候判断文档是否超过上限,超过则重命名旧日志,生成新日志。
按照日期滚动 TimedRotatingFileHandler
按照日期滚动的处理器:
class TimedRotatingFileHandler(BaseRotatingHandler):
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
        BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
        self.when = when.upper()
        self.backupCount = backupCount
        self.utc = utc
        self.atTime = atTime
        # 日期设置,支持多种方式
        if self.when == 'S':
            self.interval = 1 # one second
            self.suffix = "%Y-%m-%d_%H-%M-%S"
            self.extMatch = r"^d{4}-d{2}-d{2}_d{2}-d{2}-d{2}(.w+)?$"
        ...
        self.extMatch = re.compile(self.extMatch, re.ASCII)
        self.interval = self.interval * interval # multiply by units requested
        filename = self.baseFilename
        if os.path.exists(filename):
            t = os.stat(filename)[ST_MTIME]  # 最后修改时间
        else:
            t = int(time.time())
        self.rolloverAt = self.computeRollover(t)  # 提前计算终止时间
    def computeRollover(self, currentTime):
        # 判断的方法还是很长很复杂的,先pass
    def shouldRollover(self, record):
        t = int(time.time())
        if t >= self.rolloverAt:  # 判断是否到期
            return 1
        return 0
    def doRollover(self):
        ...
        dfn = self.rotation_filename(self.baseFilename + "." +
                                     time.strftime(self.suffix, timeTuple))
        #  滚动日志文件
        if os.path.exists(dfn):
            os.remove(dfn)
        self.rotate(self.baseFilename, dfn)
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        ...
        # 计算下一个时间点
        newRolloverAt = self.computeRollover(currentTime)
        ...
        self.rolloverAt = newRolloverAt
日期滚动就是计算最后时间点,超过时间点则重新生成新的日志文件。
小结
logging的处理逻辑大概是这样的:
- 
创建Logger对象,提供API,用来接收应用程序日志  - 
Logger对象包括多个Handler  - 
每个Handler有一个Formatter对象  - 
每条日志都会生成一个LogRecord对象  - 
使用不同的Handler对象将LogRecored对象提交到不同的流  - 
每个日志对象通过Formatter格式化输出  - 
可以使用按日期/文件大小的方式进行日志文件的滚动记录  
小技巧
覆盖对象的 __reduce__ 方法,让对象支持reduce函数:
class RootLogger(Logger):
    def __init__(self, level):
        Logger.__init__(self, "root", level)
    def __reduce__(self):
        return getLogger, ()
线程锁的创建和释放:
_lock = threading.RLock()
def _acquireLock():
    if _lock:
        _lock.acquire()
def _releaseLock():
    if _lock:
        _lock.release()
线程锁的使用:
def addHandler(self, hdlr):
    _acquireLock()
    try:
        self.handlers.append(hdlr)
    finally:
        _releaseLock()
def removeHandler(self, hdlr):
    _acquireLock()
    try:
        self.handlers.remove(hdlr)
    finally:
        _releaseLock()
参考链接
- 
Logging in Python https://realpython.com/python-logging/  - 
日志操作手册 https://docs.python.org/zh-cn/3.8/howto/logging-cookbook.html#cookbook-rotator-namer  - 
Python 的日志记录工具 https://docs.python.org/zh-cn/3.8/library/logging.html  
转自:https://mp.weixin.qq.com/s/7wiDBdGRRQymJz5oeyvboQ
15 个顶级 Python 库,你必须要试试!
英文:Erik van Baaren,翻译:Python猫 – 数据黑客
为什么我喜欢Python?对于初学者来说,这是一种简单易学的编程语言,另一个原因:大量开箱即用的第三方库,正是23万个由用户提供的软件包使得Python真正强大和流行。
在本文中,我挑选了15个最有用的软件包,介绍它们的功能和特点。
1. Dash
Dash是比较新的软件包,它是用纯Python构建数据可视化app的理想选择,因此特别适合处理数据的任何人。Dash是Flask,Plotly.js和React.js的混合体。

2. Pygame
Pygame是SDL多媒体库的Python装饰器,SDL(Simple DirectMedia Layer)是一个跨平台开发库,旨在提供对以下内容的低级接口:
- 
音频
 - 
键盘
 - 
鼠标
 - 
游戏杆
 - 
基于OpenGL和Direct3D的图形硬件
 
Pygame具有高度的可移植性,几乎可以在所有平台和操作系统上运行。尽管它具有完善的游戏引擎,但您也可以使用此库直接从Python脚本播放MP3文件。
3. Pillow
Pillow专门用于处理图像,您可以使用该库创建缩略图,在文件格式之间转换,旋转,应用滤镜,显示图像等等。如果您需要对许多图像执行批量操作,这是理想的选择。
为了快速了解它,看以下代码示例(加载并渲染图片):

4. Colorama
Colorama允许你在终端使用颜色,非常适合Python脚本,文档简短而有趣,可以在Colorama PyPI页面上找到。

5. JmesPath
在Python中使用JSON非常容易,因为JSON在Python字典上的映射非常好。此外,Python带有自己出色的json库,用于解析和创建JSON。对我来说,这是它最好的功能之一。如果我需要使用JSON,可以考虑使用Python。
JMESPath使Python处理JSON更加容易,它允许您明确的地指定如何从JSON文档中提取元素。以下是一些基本示例,可让您对它的功能有所了解:

6. Requests
Requests建立在世界上下载量最大的Python库urllib3上,它令Web请求变得非常简单,功能强大且用途广泛。
以下代码示例说明requests的使用是多么简单。

Requests可以完成您能想到的所有高级工作,例如:
- 
认证
 - 
使用cookie
 - 
执行POST,PUT,DELETE等
 - 
使用自定义证书
 - 
使用会话Session
 - 
使用代理
 
7. Simplejson
Python中的本地json模块有什么问题?没有!实际上,Python的json是simplejson。意思是,Python采用了simplejson的一个版本,并将其合并到每个发行版中。但是使用simplejson具有一些优点:
- 
它适用于更多Python版本。
 - 
它比Python随附的版本更新频率更高。
 - 
它具有用C编写的(可选)部分,因此非常快速。
 
由于这些事实,您经常会在使用JSON的脚本中看到以下内容:

我将只使用默认的json,除非您特别需要:
- 
速度
 - 
标准库中没有的东西
 
Simplejson比json快很多,因为它用C实现一些关键部分。除非您正在处理数百万个JSON文件,否则您不会对这种速度感兴趣。
8. Emoji
Emoji库非常有意思,但并非每个人都喜欢表情包,分析视角媒体数据时,Emoji包非常有用。

以下是简单的代码示例:

9. Chardet
您可以使用chardet模块来检测文件或数据流的字符集。例如,这在分析大量随机文本时很有用。但是,当您不知道字符集是什么时,也可以在处理远程下载的数据时使用它。
10. Python-dateutil
python-dateutil模块提供了对标准datetime模块的强大扩展。我的经验是,常规的Python日期时间功能在哪里结束,而python-dateutil就出现了。
您可以使用此库做很多很棒的事情。我将这些示例限制为我发现特别有用的示例:模糊分析日志文件中的日期,例如:

有关更多功能,请参见完整文档,例如:
- 
计算相对增量(下个月,明年,下周一,该月的最后一周等)和两个给定日期对象之间的相对增量。
 - 
使用iCalendar规范的超集,根据重复规则计算日期。
 - 
tzfile文件(/ etc / localtime,/ usr / share / zoneinfo等)的时区(tzinfo)实现,TZ环境字符串(所有已知格式),iCalendar格式文件,给定范围(在相对增量的帮助下),本地计算机 时区,固定偏移时区,UTC时区和基于Windows注册表的时区。
 - 
基于奥尔森数据库的内部最新世界时区信息。
 - 
使用Western,Orthodox或Julian算法计算任意一年的复活节周日日期。
 
11. 进度条:progress和tqdm
这里有点作弊,因为这是两个包,但忽略其中之一是不公平的。
您可以创建自己的进度条,这也许很有趣,但是使用progress或tqdm程序包更快,更不容易出错。
progress
借助这个软件包,您可以轻松创建进度条:

tqdm
tqdm的功能大致相同,但似乎是最新的。首先以gif动画形式进行一些演示:

12. IPython

我确定您知道Python的交互式外壳,这是运行Python的好方法。但是您也知道IPython shell吗?如果您经常使用交互式外壳程序,但您不了解IPython,则应该检查一下!
增强的IPython shell提供的一些功能包括:
- 
全面的对象自省。
 - 
输入历史记录,跨会话持续存在。
 - 
在具有自动生成的引用的会话期间缓存输出结果。
 - 
制表符补全,默认情况下支持python变量和关键字,文件名和函数关键字的补全。
 - 
“魔术”命令,用于控制环境并执行许多与IPython或操作系统相关的任务。
 - 
会话记录和重新加载。
 - 
对pdb调试器和Python分析器的集成访问。
 - 
IPython的一个鲜为人知的功能:它的体系结构还允许并行和分布式计算。
 
IPython是Jupyter Notebook的核心,它是一个开放源代码Web应用程序,可让您创建和共享包含实时代码,方程式,可视化效果和叙述文本的文档。
13. Homeassistant

我喜欢家庭自动化。这对我来说是一种嗜好,但我至今仍对此深表歉意,因为它现在控制着我们房屋的大部分。我使用Home Assistant将房子中的所有系统捆绑在一起。尽管它确实是一个完整的应用程序,但是您也可以将其安装为Python PyPI软件包。
- 
我们的大多数灯具都是自动化的,百叶窗也是如此。
 - 
我监视我们的天然气用量,电力用量和产量(太阳能电池板)。
 - 
我可以跟踪大多数电话的位置,并在进入一个区域时开始操作,例如当我回家时打开车库灯。
 - 
它还可以控制我们所有的娱乐系统,例如三星电视和Sonos扬声器。
 - 
它能够自动发现网络上的大多数设备,因此上手起来非常容易。
 
我已经每天使用Home Assistant已有3年了,它仍处于测试阶段,但这是我尝试过的所有平台中最好的平台。它能够集成和控制各种设备和协议,并且都是免费和开源的。
如果您有兴趣将房屋自动化,请确保有机会!如果您想了解更多,请访问他们的官方网站。如果可以,请将其安装在Raspberry Pi上。到目前为止,这是最简单,最安全的入门方法。我将其安装在Docker容器内功能更强大的服务器上。
14. Flask
Flask是我的入门库,用于创建快速的Web服务或简单的网站。这是一个微框架,这意味着Flask旨在使核心保持简单但可扩展。有700多个官方和社区扩展。
如果您知道自己将开发一个大型的Web应用程序,则可能需要研究一个更完整的框架。该类别中最受欢迎的是Django。
15. BeautifulSoup
如果您从网站上提取了一些HTML,则需要对其进行解析以获取实际所需的内容。Beautiful Soup是一个Python库,用于从HTML和XML文件中提取数据。它提供了导航,搜索和修改解析树的简单方法。它非常强大,即使损坏了,也能够处理各种HTML。相信我,HTML经常被破坏,所以这是一个非常强大的功能。
它的一些主要功能:
- 
Beautiful Soup会自动将传入文档转换为Unicode,将传出文档转换为UTF-8。您无需考虑编码。
 - 
Beautiful Soup位于流行的Python解析器(如lxml和html5lib)的顶部,使您可以尝试不同的解析策略或提高灵活性。
 - 
BeautifulSoup会解析您提供的任何内容,并为您做遍历树的工作。您可以将其告诉“查找所有链接”,或“查找带有粗体的表格标题,然后给我该文字。”
 
原文链接:https://medium.com/tech-explained/top-15-python-packages-you-must-try-c6a877ed3cd0
– EOF –
转自:https://mp.weixin.qq.com/s/iDW7ycuzoTM72B09wHI-8w