cython

cython是针对python和cython的的优化静态编译器。 主要作用是对python进行c扩展,其采用的方式是将python代码转换为c代码,然后将c代码编译为动态链接库(.so格式)。然后通过python或者c代码调用。 适用场景:

  • 结构简单的原生python代码编译为静态链接库
  • 使用cython编写代码 不适用场景:
  • 已经编写好的python工程 > 完成度比较高的工程实用cython编译会有功能不支持,打包失败等问题。如果如数解决这些问题,则需要修改/重构代码。

pyinstaller

可以轻松被反编译,所以放弃该选择

nuitka

实现原理和cython基本一致,循序流程:python代码->c代码->动态连接文件->可执行程序。 安装

pip install nuitka

一些经验

对于桌面程序,打包一个文件

python -m nuitka main.py --standalone --onefile --follow-imports

对于后端应用,过程相对复杂了一些。主要有以下几个原因

  • standalone方式
    • 后端程序依赖关系复杂,打包过程较慢
    • 有以下库需要包含metadata,需要指定–include-distribution-metadata=packagename来包含metadata
  • 非standalone
    • 依赖关系复杂,有一些包需要手动添加。确定的方式基本上就是每次添加完成后验证当前二进制程序是否可以直接执行,否则继续查找问题,添加/删除依赖的package
    • 注意使用的python版本和nuitka版本匹配。有时需要调整python、nuitka、使用的三方库版本,最好让三者保持稳定。
  • fastapi和asyncio是大部分问题的源头

过程简要记录

假设当前项目结构:

    - package1
    - package2
    - package3
    - main.py

则对应的命令为:

python -m nuitka main.py --include-package=package1 \
    --include-package=package2 \
    --include-package=package3

打包完成后会有main.bin文件输出。但根据项目的复杂程度,可能一次成功,也可能运行失败。

  • 大部分问题都是fastapi和asyncio引起的。 比如运行时遇到如下问题:
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpx/_client.py", line 1674, in send
    response = await self._send_handling_auth(
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpx/_client.py", line 1702, in _send_handling_auth
    response = await self._send_handling_redirects(
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpx/_client.py", line 1739, in _send_handling_redirects
    response = await self._send_single_request(request)
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpx/_client.py", line 1776, in _send_single_request
    response = await transport.handle_async_request(request)
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpx/_transports/default.py", line 377, in handle_async_request
    resp = await self._pool.handle_async_request(req)
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
    raise exc from None
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
    response = await connection.handle_async_request(
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_async/connection.py", line 99, in handle_async_request
    raise exc
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_async/connection.py", line 76, in handle_async_request
    stream = await self._connect(request)
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_async/connection.py", line 122, in _connect
    stream = await self._network_backend.connect_tcp(**kwargs)
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_backends/auto.py", line 30, in connect_tcp
    return await self._backend.connect_tcp(
  File "/opt/anaconda3/envs/packagetest/lib/python3.10/site-packages/httpcore/_backends/anyio.py", line 116, in connect_tcp
    with anyio.fail_after(timeout):

从日志中可以看出,是调用anyio出了问题,说以这里将anyio添加到打包内容中:

python -m nuitka main.py --include-package=package1 \
    --include-package=package2 \
    --include-package=package3 \
    --include-package=anyio

注意

  • 这里其实不能确定是httpx还是anyio。需要不同的组合尝试。
  • 实际上后续运行中发现还存在httpcore调用问题,所以需要加上httpcore。
python -m nuitka main.py --include-package=package1 \
    --include-package=package2 \
    --include-package=package3 \
    --include-package=anyio \
    --include-package=httpcore
  • 同时将错误日志路径中的所有包全部包含的方式是不可行的。在项目中使用了prometheus,所有包都添加进以后会导致prometheus的python sdk运行异常。 # 备注 async调用redis,需要将redis添加到打包内容中。