cython 嵌入后出现导入错误

r2e*_*ans 7 python module cython

我无法获得已编译的 python 脚本所看到的其他可用模块。我需要如何更改以下流程才能接受基于 venv 的模块或全局模块?

脚步:

$ python3 -m venv sometest
$ cd sometest
$ . bin/activate
(sometest) $ pip3 install PyCrypto Cython
Run Code Online (Sandbox Code Playgroud)

使用非标准模块的基本脚本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))
Run Code Online (Sandbox Code Playgroud)
(sometest) $ python3 hello.py
The answer is no
b'1oONZCFWVJKqYEEF4JuL8Q=='
Run Code Online (Sandbox Code Playgroud)

编译它:

(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'
Run Code Online (Sandbox Code Playgroud)

我不认为从 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
Run Code Online (Sandbox Code Playgroud)

系统:

(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"
Run Code Online (Sandbox Code Playgroud)

ead*_*ead 5

通常,Python 解释器不是“独立的”,为了工作它需要其标准库(例如ctypes(编译)或(解释)),并且还必须设置site.py其他站点包的路径(例如)。numpy

尽管可以通过冻结 py 模块并将所有 c 扩展(例如参见此 SO-post)合并到生成的可执行文件中来使 Python 解释器完全独立,但向嵌入式解释器提供所需的安装会更容易。人们可以从 python-homepage 下载“标准”安装所需的文件(至少对于Windows而言),另请参阅此SO-question)。

有时,找到标准模块/站点包并不能开箱即用:必须通过设置 Python 路径来帮助解释器,即通过以编程方式在 pyx 文件中添加<..>/sometest/lib/python3.5/site-packagessometest作为虚拟环境根文件夹)或通过在启动之前sys.path设置环境变量。PYTHONPATH

请继续阅读,了解更多血淋淋的细节和替代解决方案。


这个答案是针对Linux和Python3(Python 3.7)的,Windows/MacOS的基本思想是相同的,但一些细节可能会有所不同。

由于venv已使用,我们有以下替代方案来解决该问题:

  • 以编程方式添加到 pyx 文件中或通过在启动之前设置 -environment 变量来添加<..>/sometest/lib/python3.5/site-packagessometest作为虚拟环境根文件夹) 。sys.pathPYTHONPATH
  • 将带有嵌入式 python 的可执行文件放置在 的子目录中sometest(例如bin或创建自己的)。
  • 使用virtualenv而不是venv.

注意:对于嵌入 python 的可执行文件,无论虚拟环境(或哪个)激活与否,它都不起任何作用。


为什么上述可以解决您的场景中的问题?

问题是,(嵌入式)Python 解释器需要弄清楚以下内容在哪里:

  • 与平台无关的目录/文件,例如os.pyargparse.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(它可以被解释器找到,因为sys.prefix是已知的),或更准确地说site.main(),将虚拟环境的站点包添加到sys.path. 这样做的同时,site.py查找pyvenv.cfg并解析它。但是,site-packages仅在以下情况下才将 local 添加到 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有 key 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'
Run Code Online (Sandbox Code Playgroud)

在这种情况下,Py_SetPythonHome或者设置环境变量$PYTHONHOME是可能的解决方案。

B:在使用 virtualenv 创建的虚拟环境中可执行

假设虚拟环境和嵌入式 python 的 Python 版本相同(否则我们会遇到上述情况),emebeded 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 上方的本地主目录中以符号链接形式呈现。

venvC:环境深处的两个可执行文件夹

在 -environment 中向下移动两个而不是一个文件夹(工作的地方)venv会导致情况 A:pyvenv.cfg搜索 home 时未读取文件(上方太远),“venv`-environments 缺少指向“landmarkers”的符号链接(仅限本地)存在 side-packages),这样的第 3 步将失败,第 4 步是唯一的希望。


推论:如果没有正确的 Python 安装,嵌入式 Python 将无法工作,除非有其他可能性:

  • 所需的文件被打包到lib\pythonX.Y\*嵌入可执行文件旁边或上面的某个位置(并且没有任何pyvenv.cfg地方可以搞乱搜索)。

  • pyvenv.cfg用于将口译员指向正确的位置。