介绍

因为 Python 有功能丰富的第三方库,因此 Python 是非常适合持续集成的一门语言。

因为持续集成与普通的 Python 项目需求不同,导致无法使用 Python 社区的最佳实践。本文简介一下如何在持续集成中正确地使用 Python 第三库。

需求

Unity 项目持续集成的需求:

  • 支持 Windows macOS 同时使用
  • 使用命令行直接拉起打包脚本
  • 减少无用的步骤
  • 去除平台无关的依赖,平台相关的依赖需要提前安装好(如 Unity、Python)
  • 支持离线打包

由此引出的对 Python 的需求

  • 需要引入许多第三方库来支持很多功能
  • 第三方库多了之后就需要支持包管理
  • 所有依赖库提交到仓库中,去除安装依赖的步骤
  • 直接运行 Python 脚本,简化使用流程

环境

  • Windows 7
  • Python 3.7

虽然 Python 3.8 是支持 Windows 7 的最后版本,但是项目中已经使用了 Python 3.7,暂时不作升级。

Python 虚拟环境

为了隔离不同项目的依赖,否则一个项目的依赖库升级后会将其他项目破坏掉。

Virtualenv

venv

Since Python 3.3, a subset of it has been integrated into the standard library under the venv module.

Python 3.3 及以上版本自带模块

虚拟环境原理

详细解释了各个部分的原理,比较全面。

通过阅读脚本代码了解 virtualenv 原理,其实主要是设置几个关键的 Python 环境变量

在之前,我一直有如下几个疑惑,在此先抛出来

  1. python是怎么决定默认包搜索路径?
  2. 激活虚拟环境与直接调用虚拟环境中的python有什么区别?
  3. 虚拟环境是如何修改终端提示的?
  4. conda和pip安装的包在同一位置吗?
  5. conda与virtualenv虚拟环境的优先级?

简要地介绍原理:

虚拟环境版本控制

Python 最佳实践不建议将依赖提交到仓库中,而是只提交 pip freeze 后的依赖版本信息文件 requirements.txt

gitignore 默认是忽略 .venv 的,因此环境相关文件可以不用提交。

平台相关问题

在 Windows 下使用 Virtualenv 创建的环境只有 Windows 平台相关文件。而 macOS 是创建对应的符号文件,路径与本机绑定,不可移植。

绝对路径问题

activate 脚本中有当前项目的绝对路径,这意味着不易移植(在其他机器的不同目录上执行)

1
VIRTUAL_ENV='C:\Test\TestXXXX\.venv'

需要考虑使用 --relocatable,但是这个选项本身是实验性的,后续可能会有改动。

Note: this option is somewhat experimental, and there are probably caveats that have not yet been identified.

使用 relocatable 修改 env 目录位置

Python 包管理

虚拟环境是为了开发方便,因为项目中会使用第三方库,库又有复杂的依赖关系,需要使用合理的包管理工具进行处理。

Pipenv

Pipenv 在官网写明了 Windows 平台是一等公民,也就是说对 Windows 平台有良好的支持。

Windows is a first-class citizen, in our world.

Pyenv

Pyenv does not officially support Windows and does not work in Windows outside the Windows Subsystem for Linux. Moreover, even there, the Pythons it installs are not native Windows versions but rather Linux versions run through a compatibility layer – so you won’t get Windows-specific functionality. If you’re in Windows, we recommend using @kirankotari’s pyenv-win fork – which does install native Windows Python versions.

PDM

PDM 作者用英文写的评测比较

On the performance perspective, Pipenv doesn’t play well due to its design choice that it integrates with other third-party tools and libraries instead of building its own. Pipenv can only wrap, combine, and do a little improvement on those upstream libraries. Moreover, Pipenv doesn’t meet the goal of reproducible environment as well. It can produce a determinsitic installation setup on the source system but it is not a good idea to deploy to a different system without a careful check. On contrast, Poetry and PDM are both doing great on performance and correctness, PDM is even better especially on the time cost and compatible dependency resolving. If you do not know this tool yet, start now.

Poetry

安装

使用 pipx 安装 poetry

1
pipx install poetry

Using pipx to install Poetry is also possible. pipx is used to install Python CLI applications globally while still isolating them in virtual environments.

配置

建议 poetry 全局配置为使用项目中的 virtualenv,对每个项目的使用环境进行隔离。

1
poetry config virtualenvs.in-project true --local

比较

每一个包管理工具的官方介绍都非常好,但实际的使用体验必须去找第三方的评价。

python 包版本吃瓜

  1. 没法单独更新包
  2. 代码未动,文档( PR )先行
  3. 卸载包更新
  4. I have no idea.
  5. 伪装官方
  6. 喜欢烂尾
  7. 速度慢 Lock 时间长
  8. 对项目发展没有信心
  9. Poetry?也不行
  10. 没有任何官方和核心开发者支持
  11. 未处理的 Issue/PR 太多

情报来源: https://v2ex.com/t/777916 https://www.zhihu.com/question/322932995/answer/672812417

Poetry 有一个很严重的问题,就是它的依赖在不同的系统会冲突。 例如,你使用 macOS 开发,通过 poetry add uvicorn 安装了这个包。 然后你到 Linux 系统上部署,如果你带上了 macOS 上面生成的.lock 文件,那么你执行 poetry install 安装以后,运行就会报错,因为它安装的这个版本是 macOS 的版本,在 Linux 上不能用。你必须先删除.lock 文件,再 poetry install 才能使用。

我现在有这样一个场景,内网不能连接互联网,需要部署一套基于 selenium 的自动化系统,不能用 pip 从网上下包,也不允许用 docker 做镜像导入,只能用安装包一个个安装部署,最多自己写写脚本自动安装。 我可以在外网弄一个虚拟机,安装一模一样的操作系统,试验部署直至整个系统稳定运行,然后把所有依赖打包至内网,然后手动或者用脚本重复整个过程。 这个部署过程还得分别在 Debian 9 和 Windows 7 上面部署两套,我准备弄 Debian 9 和 Win 7 两套虚拟机分别做,都只能单机条件下,而且不能自己内网弄个服务器建一个本地源存放所有包,有什么比较好的解决方案。最好是能自动化脚本,机器数量还比较多。 目前看了一下,本地包管理的话使用 pip 和 pyinstaller,Python 环境管理有 pyenv,pipenv 还是 virtualenv,已知的问题是 pyenv 必须使用 pyenv 管理的 python 版本,其他还有什么坑?麻烦各位支个招,有踩坑过的也来分享一下。

PDM 可以把依赖装到__pypackages__里,直接打包带走,在目标机器上只要 PYTHONPATH=path/to/pypackages/3.8/lib 即可 可以参考 https://pdm.fming.dev/usage/advanced/#use-pdm-in-a-multi-stage-dockerfile

写了很多在工程中使用的真实评价,介绍得非常全面与系统。

这不是我第一次写 Pipenv 相关的文章,也相信不是最后一次,前两篇我用的是英文,(浅陋地)分析了 Pipenv 和 Poetry 的优劣,至今仍是我博客访问量最高的文章。今天是因为在知乎上看到两位朋友写的两篇文章(链接我放在文末了),吐槽了一通以后推荐大家不要使用 Pipenv。说实话,作为核心维护者之一我是有点心酸的,因为他们说的那些问题的确都存在。在本文中我希望从一个核心维护者的角度,总结一下 Pipenv 存在的问题,作为一个告解。

看起来,Poetry 是现阶段最成熟的包管理工具。

运行脚本

激活虚拟环境后运行

这样要求驱动打包的工具支持连续执行多条命令,并且保证命令之间共享相同的 Shell,否则无法使用虚拟环境。这种方式无法使用 && 连接两条命令执行。

Running source bin/activate will set the PATH variable to point to your environment bin directory which is useful if you have other command line scripts/binaries installed (this can happen with certain python packages that add shell commands), it will also unset/set PYTHONHOME. So, if bin/python works for you then you’re fine but if some of the packages you’re using start behaving strangely (or wrong one gets imported) it’s probably because Python is getting the wrong PYTHONHOME or because a certain script is not found in PATH.

直接运行

实测可以直接将虚拟环境中的 Python 加脚本作为参数运行,可以正确地找到虚拟环境中安装的库。

如果依赖于直接执行 venv 中的 python,会因为没有设置 PATH 与 VIRTUAL_ENV 环境变量导致 env/bin 中的依赖会使用系统默认 Python 执行,导致执行的 Python 版本不一致。

If you directly run a script or the python interpreter from the virtualenv’s bin/ directory (e.g. path/to/ENV/bin/pip or /path/to/ENV/bin/python-script.py) then sys.path will automatically be set to use the Python libraries associated with the virtualenv. But, unlike the activation scripts, the environment variables PATH and VIRTUAL_ENV will not be modified. This means that if your Python script uses e.g. subprocess to run another Python script (e.g. via a #!/usr/bin/env python shebang line) the second script may not be executed with the same Python binary as the first nor have the same libraries available to it. To avoid this happening your first script will need to modify the environment variables in the same manner as the activation scripts, before the second script is executed.

Python 环境变量

Python 默认支持一些路径相关的环境变量,例如可以设置 PYTHONPATH 来修改 sys.path 路径,但是使用起来比较麻烦:

1
set PYTHONPATH=.venv\\Lib\\site-packages && python test.py

PYTHONPATH Augment the default search path for module files. The format is the same as the shell’s PATH: one or more directory pathnames separated by os.pathsep (e.g. colons on Unix or semicolons on Windows). Non-existent directories are silently ignored. In addition to normal directories, individual PYTHONPATH entries may refer to zipfiles containing pure Python modules (in either source or compiled form). Extension modules cannot be imported from zipfiles. The default search path is installation dependent, but generally begins with prefix/lib/pythonversion (see PYTHONHOME above). It is always appended to PYTHONPATH. An additional directory will be inserted in the search path in front of PYTHONPATH as described above under Interface options. The search path can be manipulated from within a Python program as the variable sys.path.

Python 库搜索方式

上面提到的虚拟环境与包管理器,核心都是使用虚拟环境进行版本的隔离,而虚拟环境的工作原理是通过修改库搜索路径的方式修改行为,因此最终可以跳过这些工具,直接修改搜索路径实现想要的结果。

可以直接修改 sys.path 数组来修改搜索路径。

1
2
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))

管理库

需求

包管理器安装后的包需要移动到项目中的库目录中,有几个需求:

  • 所有平台共用相同的库目录
  • 不同平台的库内容不同,例如 Windows 版本的库只会带 Windows 的二进制依赖库
  • 支持离线安装部署
  • 虚拟环境的最佳实践是在部署时动态安装库,与减少安装依赖的需求不符。Python 包管理会选择在打包时再安装依赖,这其实隐含了两条:
    • 依赖网络存在且条件良好(网络差的时候会不断超时,或者国外地址访问困难)
    • 依赖包在库中(有可能会被恶意删除)

方案总结

  • Poetry 安装库后将其移动到项目中的 site-packages 目录
  • Python 脚本中将 site-packages 添加到 sys.path 数组最前面,提高优先级
  • 命令行中运行 python project/build.py

PyCharm 支持

可以将库目录设置为 Source Root,这样 PyCharm 可以自动索引其中的代码,可以在编辑代码时提供代码提示。