cython 嵌入后导入错误
- 2025-04-15 09:21:00
- admin 原创
- 28
问题描述:
我无法让已编译的 Python 脚本识别其他可用的模块。我需要如何更改以下流程才能接受基于 venv 或全局的模块?
步骤:
$ python3 -m venv sometest
$ cd sometest
$ . bin/activate
(sometest) $ pip3 install PyCrypto Cython
基本脚本,使用非标准模块Crypto
:
# hello.py
from Crypto.Cipher import AES
import base64
obj = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')
msg = "The answer is no"
ciphertext = obj.encrypt(msg)
print(msg)
print(base64.b64encode(ciphertext))
(sometest) $ python3 hello.py
The answer is no
b'1oONZCFWVJKqYEEF4JuL8Q=='
编译它:
(sometest) $ cython -3 --embed hello.py
(sometest) $ gcc -Os -I /usr/include/python3.5m -o hello hello.c -lpython3.5m -lpthread -lm -lutil -ldl
(sometest) $ $ ./hello
Traceback (most recent call last):
File "hello.py", line 1, in init hello
from Crypto.Cipher import AES
ImportError: No module named 'Crypto'
我认为使用 cython 嵌入编译脚本中的 venv 没有问题:该脚本可以在系统中没有 venv 的其他地方运行(也就是说,python3 -c 'from Crypto.Cipher import AES'
不会失败)。
否则,该过程运行正常:
(sometest) $ echo 'print("hello world")' > hello2.py
(sometest) $ cython -3 --embed hello2.py
(sometest) $ gcc -Os -I /usr/include/python3.5m -o hello2 hello2.c -lpython3.5m -lpthread -lm -lutil -ldl
(sometest) $ ./hello2
hello world
系统:
(sometest) $ python3 --version
Python 3.5.2
(sometest) $ pip3 freeze
Cython==0.29.11
pkg-resources==0.0.0
pycrypto==2.6.1
(sometest) $ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.6 LTS"
解决方案 1:
通常,Python 解释器不是“独立的”,为了工作,它需要其标准库(例如ctypes
(编译)或(解释)),并且还必须设置site.py
到其他站点包的路径(例如)。numpy
虽然可以通过冻结 py 模块并将所有 c 扩展(例如,参见此 SO-post)合并到生成的可执行文件中来使 Python 解释器完全独立,但为嵌入式解释器提供所需的安装更为容易。可以从 python-homepage 下载“标准”安装所需的文件(至少对于Windows 系统而言),另请参阅此SO- question。
有时,找到标准模块/站点包并不能立即使用:必须通过设置 Python 路径来帮助解释器,即通过以编程方式在 pyx 文件中添加<..>/sometest/lib/python3.5/site-packages
(sometest
作为虚拟环境根文件夹)或在启动前设置环境变量。sys.path
`PYTHONPATH`
请继续阅读以了解更多详细信息和替代解决方案。
这个答案针对的是Linux和Python3(Python 3.7),对于Windows / MacOS,基本思想是相同的,但一些细节可能有所不同。
因为venv
使用了,所以我们有以下替代方法来解决这个问题:
以编程方式在 pyx 文件中添加
<..>/sometest/lib/python3.5/site-packages
(sometest
作为虚拟环境根文件夹)或在启动之前设置环境变量。sys.path
`PYTHONPATH`将嵌入 Python 的可执行文件放在子目录中
sometest
(例如bin
或创建自己的子目录)。使用
virtualenv
而不是venv
。
注意:对于嵌入python的可执行文件,虚拟环境(或哪个)是否激活都不起作用。
为什么上述方法可以解决您场景中的问题?
问题是,(嵌入式)Python 解释器需要弄清楚以下内容在哪里:
独立于平台的目录/文件,例如
os.py
(argparse.py
几乎所有 .py/ .pyc)。给定sys.prefix
,解释器可以确定在哪里找到它们(即在 中prefix/lib/pythonX.Y
)。平台相关的目录/文件,例如共享库。假设
sys.exec_prefix
解释器能够找到它们(例如,可以在 中找到共享库exec_prefix/lib/pythonX.Y/lib-dynload
)。
执行 时,可以在此处找到算法并执行搜索Py_Initialize
。找到这些目录后,sys.path
即可构建 。
但是,使用时, exe 旁边或父目录中venv
有一个pyvenv.cfg
-file ,可确保找到正确的 Python-Home - 一个好的起点是此文件中的 -key。home
如果Py_NoSiteFlag
未设置 ,Py_Initialize
则会利用(因为已知site.py
,所以解释器可以找到它),或者更准确地说,将虚拟环境的 site-packages 添加到。在执行此操作时,会查找并解析它。但是,仅在以下情况下,本地文件才会添加到 python-path 中:sys.prefix
`site.main()sys.path
site.pypyvenv.cfg
site-packages`
如果名为“pyvenv.cfg”的文件存在于 sys.executable 上方的一个目录中,则 sys.prefix 和 sys.exec_prefix 将设置为该目录,并且还会检查站点包(sys.base_prefix 和 sys.base_exec_prefix 将始终是 Python 安装的“真实”前缀)。
在您的情况下pyvenv.cfg
, 不在上述目录中,而是与 exe 文件位于同一目录中 - 因此,通过 pip 安装库的本地站点包未包含在内。全局站点包由于pyvenv.cfg
has 键而不包含在内include-system-site-packages = false
。因此,不允许使用站点包,并且找不到已安装的库。
但是,将 exe 向下移动一个目录会导致本地站点包被包含到路径中。
还可能存在其他情况,重要的是可执行文件的位置而不是激活了哪个环境。
答:可执行文件位于某个地方,但不在虚拟环境中
这种搜索启发式方法对于已安装的 python 解释器或多或少是可靠的,但对于嵌入式解释器或虚拟环境可能会失效(有关更多信息,请参阅此问题)。
如果使用通常或类似的方式安装了 python apt install
,那么它将被找到(由于搜索算法中的第 4 步)并且嵌入式解释器将使用系统安装。
但是,如果文件被移动或者 python 是从源代码构建的但未安装,则嵌入式解释器无法启动:
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Fatal Python error: initfsencoding: unable to load the file system codec
ModuleNotFoundError: No module named 'encodings'
在这种情况下,Py_SetPythonHome
或者设置环境变量$PYTHONHOME
是可能的解决方案。
B:可在使用 virtualenv 创建的虚拟环境中执行
假设虚拟环境和嵌入式 Python 的 Python 版本相同(否则会出现上述情况),嵌入式 exe 将使用本地侧包。主目录搜索算法总是会找到本地主目录,原因如下:
步骤 3. 尝试查找相对于 argv0_path 的 prefix 和 exec_prefix,并沿路径回溯直至遍历完毕。这是最常见的成功步骤。请注意,如果 prefix 和 exec_prefix 不同,则更有可能找到 exec_prefix;但是,如果 exec_prefix 是 prefix 的子目录,则两者都会找到。
在这种情况下 argv0_path
是 exe 的路径(没有pyvenv.cfg
文件!),并且会找到“地标”(lib/python$VERSION/os.py 和 lib/python$VERSION/lib-dynload),因为它们在 exe 上方的 local-home 中显示为符号链接。
C:可执行文件位于venv
环境深处的两个文件夹中
在 -environment中向下移动两个文件夹而不是一个文件夹(它可以工作)venv
会导致情况 A:pyvenv.cfg
在搜索 home(太靠上)时未读取文件,'venv'-environments 缺少指向“landmarkers”的符号链接(本地仅存在 side-packages),因此步骤 3 将失败,步骤 4是唯一的希望。
推论:如果没有正确安装 Python,嵌入式 Python 将无法工作,除非有其他可能性:
所需的文件被打包到
libpythonX.Y*
嵌入可执行文件的旁边或上面的某个地方(并且不会pyvenv.cfg
弄乱搜索)。或
pyvenv.cfg
用于将解释器指向正确的位置。
扫码咨询,免费领取项目管理大礼包!