python - cython嵌入后的ImportError

标签 python module cython

我无法通过编译的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"

最佳答案

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

尽管可以通过冻结py模块并将所有c扩展名(例如参见this SO-post)合并到生成的可执行文件中来使Python插入器完全独立,但为嵌入的插入器提供所需的安装更为容易。

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

继续阅读以获取更多详细信息和替代解决方案。



这个答案适用于Linux和Python3(Python 3.7),基本思想与Windows / MacOS相同,但是某些细节可能有所不同。

因为使用了venv,所以我们有以下替代方法可以解决此问题:


在pyx文件中以编程方式将<..>/sometest/lib/python3.5/site-packagessometest是虚拟环境的根文件夹)添加到sys.path或通过在启动前设置PYTHONPATH -environment变量。
将带有嵌入式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中找到)。


执行found here时,算法可以为Py_Initialize并执行搜索。一旦找到这些目录,就可以构造sys.path

但是,使用venv时,会有一个pyvenv.cfg文件next to exe or in the parent directory,该文件可确保找到正确的Python-Home-一个好的起点是此文件中的home键。

如果未设置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)。

如果使用通常的apt install或类似方法安装了python,则会找到它(由于搜索算法中的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嵌入的exe将使用本地包。归因于this rule,归位搜索算法将始终找到本地归宿:


  步骤3.尝试找到相对于argv0_path的前缀和exec_prefix,回溯路径,直到耗尽为止。这是成功的最常见步骤。请注意,如果prefix和exec_prefix为
  不同的是,更有可能找到exec_prefix;但是如果
  exec_prefix是前缀的子目录,两个都可以找到。


在这种情况下,argv0_path是exe的路径(没有pyvenv.cfg文件!),而"landmarks"(lib / python $ VERSION / os.py和lib / python $ VERSION / lib-dynload)将被发现,因为它们在exe上方的本地首页中以符号链接的形式呈现。

C:在venv环境内部的可执行两个文件夹

在以下情况下,在venv环境中向下移动两个文件夹而不是一个文件夹(在该文件夹中可以工作):情况A:在搜索主目录(太远)时未读取pyvenv.cfg文件,'venv`环境缺少指向以下链接的符号链接“地标”(仅在本地提供侧包),第3步将失败,只有4. step是希望。



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


所需的文件打包在嵌入可执行文件旁边或上方的lib\pythonX.Y\*中(并且周围没有pyvenv.cfg会使搜索混乱)。
pyvenv.cfg用于将解释器指向正确的位置。

关于python - cython嵌入后的ImportError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56857449/

相关文章:

PHP fatal error : Call to undefined function imagecreatefrompng()

python - Python 3.7中使用工厂函数生成注释类型时出现 “typing.ClassVar”问题

python - 如何随机化查询集的顺序

python - Django 模型查询最接近的整数匹配

python - Cython 通过数组广播加速循环

python - Cython 性能基准

c++ - 文档示例中的 Cython 崩溃

python - 在 python 中使用与实例和类方法相同的函数

Ruby:C类包含模块M;在 M 中包含模块 N 不会影响 C。什么给了?

c - 从用户空间与内核模块哈希表交互