cython 嵌入后的 ImportError
Posted
技术标签:
【中文标题】cython 嵌入后的 ImportError【英文标题】:ImportError after cython embed 【发布时间】:2019-07-02 17:28:42 【问题描述】:我无法通过已编译的 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-embedded-compiled 脚本中的 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"
【问题讨论】:
嵌入的python可能没有相同的pythonpath 就是这样,谢谢@ead。我曾假设它会使用相同的模块源,否则会起作用。现在,我看到PYTHONPATH=/usr/lib/python3/dist-packages/ ./hello
有效。虽然我觉得有点尴尬,就这么简单,但我觉得不删除它就足够了。如果你回答,我会接受。再次感谢您!
我希望您很清楚,在您的解决方案中,嵌入式 iterpreter 使用系统安装,而不是来自虚拟环境。
【参考方案1】:
通常,Python 解释器不是“独立的”,为了工作,它需要其标准库(例如 ctypes
(已编译)或 site.py
(已解释))以及其他站点包的路径(例如numpy
)必须设置。
虽然可以通过冻结 py 模块并将所有 c 扩展(参见例如 this SO-post)合并到生成的可执行文件中来使 Python 解释器完全独立,但更容易为嵌入interpeter。可以从 python-homepage 下载“标准”安装所需的文件(至少对于windows),另请参见SO-question)。
有时查找标准模块/站点包不能开箱即用:必须通过设置 Python 路径来帮助解释器,即通过添加 <..>/sometest/lib/python3.5/site-packages
(sometest
是一个虚拟环境根文件夹)到sys.path
以编程方式在 pyx 文件中或通过在启动前设置 PYTHONPATH
-环境变量。
继续阅读以了解更多详细信息和替代解决方案。
此答案适用于 Linux 和 Python3(Python 3.7),基本思想与 Windows/MacOS 相同,但某些细节可能有所不同。
因为使用了venv
,所以我们有以下替代方案来解决问题:
<..>/sometest/lib/python3.5/site-packages
(sometest
是一个虚拟环境根文件夹)添加到sys.path
,可以在 pyx 文件中以编程方式进行,也可以在开始之前设置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
中找到)。
算法可以是found here,并在执行Py_Initialize
时执行搜索。找到这些目录后,就可以构造sys.path
了。
但是,当使用venv
时,有一个pyvenv.cfg
-文件next to exe or in the parent directory,它可以确保找到正确的Python-Home - 一个很好的起点是此文件中的home
-key。
如果Py_NoSiteFlag
没有设置,Py_Initialize
将使用site.py
(它可以被解释器找到,因为sys.prefix
是已知的),或者更精确的site.main()
,添加站点包虚拟环境到sys.path
。这样做时,site.py
会查找 pyvenv.cfg
并对其进行解析。但是,只有在以下情况下才会将本地 site-packages
添加到 python-path:
如果上一个目录存在名为“pyvenv.cfg”的文件 sys.executable、sys.prefix 和 sys.exec_prefix 设置为 目录,并且还检查站点包(sys.base_prefix 并且 sys.base_exec_prefix 将始终是 Python 安装)。
在您的情况下,pyvenv.cfg
不在上面的目录中,但与 exe 相同 - 因此不包括通过 pip 安装库的本地站点包。不包括全局站点包,因为pyvenv.cfg
具有密钥include-system-site-packages = false
。因此,不允许使用任何站点包,并且无法找到已安装的库。
但是,将 exe 下移一个目录,会导致将本地站点包包含到路径中。
还有其他可能的情况,重要的是可执行文件的位置,而不是激活的环境。
答:可执行文件在某处,但不在虚拟环境中
这种搜索启发式对已安装的 python 解释器或多或少可靠,但可能适用于嵌入式解释器或虚拟环境(有关更多信息,请参阅this issue)。
如果 python 是使用通常的apt install
或类似方法安装的,那么它将被找到(由于搜索算法中的4. step)并且嵌入式解释器将使用系统安装。
但是,如果文件被移动或 python 是从源代码构建但未安装,则嵌入的 interperter 无法启动:
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 将使用本地侧包。由于this rule,家庭搜索算法总是会找到本地家庭:
步骤 3. 尝试找到相对于 argv0_path 的前缀和 exec_prefix,回溯路径直到用尽。这是成功的最常见步骤。请注意,如果 prefix 和 exec_prefix 是 不同的是,exec_prefix 更有可能被找到;但是,如果 exec_prefix 是prefix的子目录,都会找到。
在这种情况下,argv0_path
是 exe 的路径(没有 pyvenv.cfg
文件!),"landmarks"(lib/python$VERSION/os.py 和 lib/python$VERSION/lib- dynload) 将被找到,因为它们在 exe 上方的本地主页中显示为符号链接。
C:venv
-environment 深处的两个可执行文件夹
在venv
-环境中将两个而不是一个文件夹(它工作的地方)向下移动会导致情况A:在搜索主页(太远)时未读取pyvenv.cfg
文件,'venv`-environments缺少指向“地标”的符号链接(本地仅存在侧包),这样的第 3 步将失败,4. step 是唯一的希望。
推论:如果没有正确的 Python 安装,嵌入式 Python 将无法工作,除非有其他可能性:
所需的文件被打包到嵌入可执行文件旁边的lib\pythonX.Y\*
或上面的某个地方(并且没有pyvenv.cfg
周围会弄乱搜索)。
或pyvenv.cfg
用于将解释器指向正确的位置。
【讨论】:
这需要考虑很多。我不知道venv
和virtualenv
之间存在(显着)差异。当我尝试 outside 中的任何一个步骤时(假设模块全局可用,没有设置PYTHONPATH
),它可以正常工作;这有助于我理解一些事情。您的回答我需要花一些时间才能接受,感谢您抽出宝贵的时间进行详尽的回答!
我知道嵌入式可执行文件仍然需要“完整”的 python 安装,但是当我运行 ./hello
时它失败了,尽管在可执行文件旁边有 lib/python3.5/site-packages/Crypto
。假设可执行文件必须位于./lib/
旁边的子目录中,是对“嵌入可执行文件旁边” 的更安全解释吗? (我没有意识到 venv 的细微差别,但现在我看到了行为差异。)
@r2evans 如果周围没有 pyvenv.cfg
来搞乱搜索,那么“exe 旁边”就足够了(至少对于 Python3.7 - 不能用 Python3.5 测试它),但是您的建议更安全(将 exe 放在子文件夹中),因为它在 pyenv.cfg
附近时也有效。以上是关于cython 嵌入后的 ImportError的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 程序中调用 Cython (Python3.6)