# 软件包

  • Python 与其它编程语言相比的一大特色是,官方、社区提供了大量实现各种功能的软件包,可直接调用,避免自己重复造轮子。
  • Python 官方提供了一些标准库,大部分在安装 Python 解释器时就自带了,也可以通过 pip 命令下载它们。
  • 用户还可以使用其他用户发布的 Python 库,称为第三方库。
    • 目前流行的第三方库分享平台是 https://pypi.org/ (opens new window)
    • 第三方库一般安装在 Python 解释器的安装目录的 site-packages 目录下,比如 /usr/lib/Python3.6/site-packages/ 。可执行命令 python -m site 查看 site-packages 目录。
  • 第三方库举例:
    • autopep8 :用于将 Python 代码自动排版成 PEP8 风格。
    • pylint :用于检查 Python 代码的语法、代码风格。

# ♢ setuptools

:Python 的标准库,用于制作 Python 包。

  • Python 包的根目录下通常存在一个 setup.py 文件,用于打包或安装该库。
    python setup.py build     # 编译(不一定需要)
    python setup.py install   # 安装
    
  • easy_install :setuptools 库提供的一个命令行工具,用于安装 Python 包。
    • 比较老旧,不推荐使用。

# 打包格式

  • 分享 Python 软件包给别人时,通常打包成压缩文件再发送。

  • Python 软件包通常包含几种文件:

    • .py 文件:只包含 Python 源代码,通常可以跨平台运行。
    • .pyd、.so 等二进制文件:通常不能跨平台运行。
    • .json、.yaml 等配置文件

一个 Python 软件包通常包含几十、几百个文件,但为了方便网络传输,通常打包成一个压缩文件。分为两种打包格式:

  • 源代码包
    • :采用 tar、zip 等压缩格式。只包含源文件,不包含预编译文件,因此不能直接使用,需要用户解压后,运行其中的 setup.py 文件进行编译、安装。
    • 属于源代码发布(Source Distribution)。
      • 可以添加一个 MANIFEST.in 文件,声明打包时需要 include 或 exclude 的文件。
    • 对于纯 Python 库,源代码包可以直接安装使用。对于非纯 Python 库,可能需要编译之后才能安装、使用。
  • wheel 包
    • :采用 zip 压缩格式,扩展名为 .whl ,包含预编译文件,因此用户可以直接使用。
    • 属于二进制发布(Binary Distribution)。
      • 默认不会读取 MANIFEST.in 文件。
    • 一个包含预编译文件的 Python 库,通常需要对不同平台分别编译 wheel 包,按以下格式命名:
      {distribution}-{version}-{python tag}(-{build tag})?-{abi tag}-{platform tag}.whl
      # distribution  :库名
      # version       :版本
      # build tag     :内部版本号,可选
      # python tag    :支持的 Python 解释器,比如 py2、py3、py27、py35、cp35 ,取值为 none 则不限制
      # abi tag       :要求的 Python 解释器的 ABI ,取值为 none 则不限制
      # platform tag  :支持的平台,比如 manylinux1_x86_64 ,取值为 any 则不限制
      
      • 包名中的各个字段只能包含字母、数字,其它字符都要转换成下划线 _
      • wheel 包名示例:
        Django-3.1.5-py3-none-any.whl         # 它表示 Django 库的 3.1.5 版本,只支持 Python3 解释器,支持所有平台
        numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl
        PyQt6-6.0.0-6.0.0-cp36.cp37.cp38.cp39-abi3-macosx_10_15_aarch64.whl
        

# 打包步骤

通常通过 setup.py 文件制作源代码包或 wheel 包,步骤如下:

  1. 先编写一个 setup.py 文件,配置该 Python 库:

    import setuptools
    
    with open('README.md', encoding='utf-8') as f:
        readme_md = f.read()
    
    setuptools.setup(
        name='pyexiv2',                                                                   # 该库的名称。由 ASCII 字母、数字组成,不区分大小写。还可包含 -_. 三种字符,但视作相同
        version='2.1.0',                                                                  # 该库的版本
        author='LeoHsiao',
        author_email='[email protected]',
        description='Read/Write metadata of digital image, including EXIF, IPTC, XMP.',   # 简介
        long_description=readme_md,                                                       # 详细说明
        long_description_content_type='text/markdown',
        license='GPLv3',
        url='https://github.com/LeoHsiao1/pyexiv2',                                       # 项目网站
        packages=setuptools.find_packages(),                                              # 从当前目录开始,自动发现包以及子包,加入打包
        # packages=['pyexiv2','pyexiv2/tests'],                                           # 指定 Python 包目录(不会包括子目录),加入打包
        package_data={'': ['*.md', '*/*.md']},                                            # 将某个包中某些路径的文件加入打包,包名为空则匹配所有包
        python_requires='>=3.5',                                                          # 安装该库需要的 Python
        # install_requires=["pybind11==2.4.3"],                                           # 安装该库需要的依赖库
        classifiers=[                                                                     # 该库的分类标签
            'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
            'Operating System :: POSIX :: Linux',
            'Operating System :: MacOS',
            'Operating System :: Microsoft :: Windows',
            'Programming Language :: Python :: 3.5',
            'Programming Language :: Python :: 3.6',
            'Programming Language :: Python :: 3.7',
        ],
    )
    
    • Python2 通常基于标准库 distutils 编写 setup.py 文件。
    • Python3 通常基于第三方库 setuptools 编写 setup.py 文件。
  2. 然后执行以下命令进行打包:

    python -m pip install setuptools wheel twine      # 安装打包工具
    git clean -d -fx                                  # 删掉项目目录下的多余文件
    python setup.py sdist                             # 生成源代码包,默认保存到 dist 目录下
    python setup.py bdist_wheel                       # 生成 wheel 包
                              --python-tag py3        # 指定 Python 解释器标签
                              --py-limited-api cp32   # 指定 ABI 标签
                              --plat-name win-amd64   # 指定平台标签
    
  3. 可以将 dist 目录下的 Python 包上传到 pypi.org :

    twine upload dist/*
    
    • 每次执行 twine 时都需要在终端输入用户名、密码。如果嫌麻烦,用户可将凭据保存在 ~/.pypirc 文件中,可保存多个仓库的凭证:
      [distutils]
      index-servers=
          pypi
          testpypi
      
      [pypi]
      repository: https://upload.pypi.org/legacy/
      username: ***
      password: ***
      
      [testpypi]
      repository: https://test.pypi.org/legacy/
      username: ***
      password: ***
      
      然后在上传包时指定仓库:
      twine upload dist/* --repository pypi
      
    • 不支持上传平台标签为 linux 的 wheel 包,需要改成 manylinux ,强调该 wheel 包兼容不同的 Linux 发行版。
      • 比如 manylinux2010 是指大概于 2010 年之后发布的 Linux 发行版,支持 x86_64 和 i686 的 CPU 架构。
    • 上传某个版本的 Python 包文件之后,不能重复上传或修改该版本的文件,只能上传更高版本的文件。

# ♢ pip

:Python 的第三方库。相当于改进版的 easy_install ,是目前流行的 Python 包管理工具,可以下载、安装、卸载 Python 包。

# 安装

  • 在 Window 上,官方的 Python 安装包自带了 pip 工具。
  • 在 Linux 上,如果从源代码编译 Python ,则自带了 pip 工具。
  • 在 Linux 上,如果用 yum、apt 安装预编译的 Python ,则可能缺少 pip ,可执行官方的安装脚本:
    wget https://bootstrap.pypa.io/get-pip.py
    python get-pip.py
    rm -f get-pip.py
    pip install --upgrade setuptools
    
    这样默认是安装到全局,也可以单独给当前用户安装:
    python get-pip.py --user
    echo 'export PATH=$PATH:~/.local/bin' >> ~/.bash_profile
    source ~/.bash_profile
    

# 命令

pip
    install <name>...       # 安装指定的包
            --upgrade       # 安装最新的版本
            --user          # 安装到当前用户的 ~/.local/ 目录下。否则默认是安装到 Python 解释器的安装目录下,供所有用户访问
            --no-cache-dir  # 下载时禁用缓存。默认会将下载的包缓存到磁盘
            -i https://pypi.python.org/simple       # --index-url ,指定远程仓库的索引页面,默认采用 pypi 。可改用网速更快的国内镜像源,比如 https://pypi.tuna.tsinghua.edu.cn/simple
            --timeout 15                            # 设置访问远程仓库的超时时间
            --proxy [user:passwd@]proxy.server:port # 使用一个网络代理。使用 socks 代理时需要先 pip install PySocks
    uninstall <name>...     # 卸载指定的包
    show <name>...          # 显示已安装包的信息
    list                    # 显示已安装的所有包
    -V                      # 显示版本(还会显示该 pip 属于哪个 Python 解释器)

    cache
        dir                 # 显示缓存的保存目录
        info                # 显示缓存的信息
        list   [pattern]    # 列出缓存的包
        remove <pattern>    # 删除缓存的包
        purge               # 删除所有缓存的包
  • 执行 pip install <name> 时,
    • name 可以是一个 Python 包的名称,比如 'pip install django' 。
      • 还可以加上版本号,比如 'django==2.2.0' 、'django>=2.2.0' 。
      • 如果该包已安装,则 pip 会结束执行,显示相应提示,比如 Requirement already satisfied: django
      • 如果该包未安装,则 pip 会从远程仓库搜索并下载它,然后安装。
      • 如果该包需要依赖其它包,则 pip 会自动安装它们。
    • name 也可以是本机上的 Python 包的文件路径。比如 pip install /tmp/django-2.2.0-py3-none-any.whl
    • 如果远程仓库需要身份认证,则每次执行 pip install 时都需要在终端输入用户名、密码。如果嫌麻烦,用户可将凭据保存在 ~/.netrc 文件中:
      machine nexus.ops.test.com
      login root
      password ******
      
  • pip 本身也是一个 Python 的第三方库,会安装到某个 Python 解释器的 site-packages 目录下,专属于这个 Python 解释器。
    • 安装了多个 Python 解释器时,注意区分它们的 pip ,可以通过执行 python -m pip 来指定某个 Python 解释器的 pip 。
    • 可以让 pip 更新自己:pip install --upgrade pip
  • 可执行以下命令,同步 pip 安装的所有包:
    pip freeze > requirements.txt     # 将 pip 已安装的所有包及其版本导出到文本文件中
    pip install -r requirements.txt   # 读取文件文件的内容,安装其中列出的所有包
    
    不过这样并不能解决依赖库冲突的问题:假设安装包 A 时需要安装包 B==2.0C==3.0 ,而安装库 B 时又自动安装了包 C==3.1 ,则包 A 即使安装成功,也可能运行出错。

# ♢ virtualenv

:Python 的第三方库,用于创建 Python 的虚拟环境。

  • 官方文档 (opens new window)

  • 安装:pip install virtualenv

  • 常用于给每个 Python 项目创建一个 Python 虚拟环境,给它们分别安装 Python 的第三方库,隔离它们的运行环境。

    • 如果安装第三方库时,总是安装到主机上全局的 site-packages 目录,则需要 root 权限才能写入,而且多个项目安装的第三方库可能会起冲突。因此建议创建虚拟环境。
  • 用法:

    1. 创建虚拟环境:
      virtualenv .venv
                -p /usr/bin/python3.6    # 指定要拷贝的 Python 解释器(默认是安装 virtualenv 的那个 Python 解释器)
      
      它会创建一个名为 .venv 的文件夹,将 Python 解释器的 bin、lib 目录拷贝进去。
      • Python 的可执行文件只是拷贝软链接。
      • 默认不会拷贝第三方库,只会拷贝 pip、setuptools、wheel 三个包。
    2. 使用虚拟环境中的 Python 解释器:
      .venv/bin/python
      
      也可以进入虚拟环境的 Shell :
      [root@CentOS ~]# source .venv/bin/activate
      (.venv) [root@CentOS ~]# type python
      python is hashed (/root/.venv/bin/python)
      (.venv) [root@CentOS ~]# deactivate           # 退出虚拟环境的 Shell (如果采用其它方式退出,则可能不能恢复之前的 shell )
      
  • 虚拟环境的目录结构示例:

    .venv/
    ├── bin
    │   ├── activate
    │   ├── easy_install
    │   ├── easy_install3
    │   ├── easy_install-3.6
    │   ├── easy_install3.6
    │   ├── pip
    │   ├── pip3
    │   ├── pip-3.6
    │   ├── pip3.6
    │   ├── python -> /usr/bin/python3
    │   ├── python3 -> python
    │   ├── python3.6 -> python
    │   ├── wheel
    │   ├── wheel3
    │   ├── wheel-3.6
    │   └── wheel3.6
    ├── lib
    │   └── python3.6
    ├── lib64
    │   └── python3.6
    └── pyvenv.cfg
    

# ♢ pipenv

:Python 的第三方库,兼有 pip 和 virtualenv 的功能,更方便。

# 原理

  • 使用 pipenv 时,它会调用 virtualenv 为当前项目创建一个 Python 虚拟环境。
    • 默认是在用户家目录下创建一个目录来保存虚拟环境,比如 /home/leo/.local/share/virtualenvs/test-BuDEOXnJ ,该目录的命名规则为 "项目目录名-哈希值" 。
      • 可以声明环境变量 export PIPENV_VENV_IN_PROJECT=1 ,让 pipenv 将虚拟环境保存在 ${project_dir}/.venv 目录下。
    • pipenv 会记录每个项目目录与虚拟环境目录的对应关系。
      • 如果改变项目目录的路径,则 pipenv 会找不到该项目对应的虚拟环境目录。
      • 每个项目目录同时只能分配一个虚拟环境目录。如果多次创建虚拟环境,则会移除之前的虚拟环境目录。
  • 执行 pipenv installpipenv uninstall 时会自动更新 Pipfile 和 Pipfile.lock 文件,如果它们不存在则自动创建。
    • Pipfile 记录了用户执行 pipenv install 时指定的 Python 包的名称、版本、远程仓库,用于代替 pip 生成的 requirements.txt 文件。
    • Pipfile.lock 记录了实际安装的所有 Python 包,比 Pipfile 多记录了哈希值、依赖关系。
      • 例如用户执行 pipenv install requests ,Pipfile 中记录的是 requests==* ,Pipfile.lock 中记录的是实际安装的版本。

# 命令

pipenv
      --python 3.6              # 创建虚拟环境,使用指定版本的 Python 解释器(它必须已安装)
      --python /path/to/python  # 使用指定路径的 Python 解释器

      --where             # 显示项目的根目录,即 Pipfile 所在目录
      --venv              # 显示虚拟环境所在目录
      --rm                # 删除虚拟环境
      --envs              # 显示 pipenv 支持的所有环境变量

      install             # 安装 Pipfile 中的所有包
              <name>...   # 安装指定的包(兼容 pip install 的语法)
      uninstall
              <name>...   # 卸载指定的包
              --all       # 卸载所有包
      graph               # 以树形结构显示当前虚拟环境安装的所有包
      check               # 检查 Pipfile 是否存在格式错误、安全漏洞

      lock                # 生成 Pipfile.lock 文件
      sync                # 安装 Pipfile.lock 指定的所有包
      clean               # 卸载 Pipfile.lock 之外的所有包(这不会修改 Pipfile.lock )
      update              # 执行 lock 和 sync

      shell               # 进入虚拟环境的 bash shell 。这会导入相应的环境变量,执行 exit 可退出
      run <command>       # 在虚拟环境的 shell 中执行一条命令
  • 例:创建虚拟环境
    PIPENV_VENV_IN_PROJECT=1 pipenv --python 3.9
    pipenv shell