在cython嵌入后导入恐怖

2024-09-28 22:22:29 发布

您现在位置:Python中文网/ 问答频道 /正文

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

Tags: 模块frompyimport脚本hellovenvis
1条回答
网友
1楼 · 发布于 2024-09-28 22:22:29

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

尽管可以通过冻结py模块并将所有c扩展(例如this SO-post)合并到生成的可执行文件中,使Python interpeter完全独立,但为嵌入式interpeter提供所需的安装更容易。您可以从python主页(至少对于windows)下载“标准”安装所需的文件,另请参见此SO-question

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

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


这个答案适用于Linux和Python3(Python3.7),基本思想对于Windows/MacOS是相同的,但是一些细节可能会有所不同

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

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

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


在您的场景中,为什么上述方法可以解决问题

问题是,(嵌入式)Python解释器需要找出以下内容:

  • 独立于平台的目录/文件,例如os.pyargparse.py(大部分都是*.py/*.pyc)。给定^{},解释器可以找到它们的位置(即在prefix/lib/pythonX.Y
  • 平台相关目录/文件,例如共享库。给定^{},解释器可以找到它们的位置(例如,共享库可以在exec_prefix/lib/pythonX.Y/lib-dynload中找到)

算法可以是found here,在执行^{}时执行搜索。一旦找到这些目录,就可以构建sys.path

但是,当使用venv时,有一个pyvenv.cfg-file next to exe or in the parent directory,它确保找到正确的Python主目录-一个好的起点是该文件中的home-键

如果未设置^{},则Py_Initialize将利用^{}(解释器可以找到它,因为sys.prefix是已知的),或者更准确地说^{},将虚拟环境的站点包添加到sys.path。执行此操作时,site.py查找pyvenv.cfg并对其进行解析。但是,只有在以下情况下,才会将本地site-packages添加到python路径:

If a file named "pyvenv.cfg" exists one directory above sys.executable, sys.prefix and sys.exec_prefix are set to that directory and it is also checked for site-packages (sys.base_prefix and sys.base_exec_prefix will always be the "real" prefixes of the Python installation).

在您的情况下pyvenv.cfg不在上面的目录中,但与exe相同-因此不包括通过pip安装库的本地站点包。不包括全局站点包,因为pyvenv.cfg具有键include-system-site-packages = false。因此,不允许使用站点包,并且无法找到已安装的库

但是,向下移动exe one目录将导致将本地站点包包含到路径中


还有其他可能的场景,重要的是可执行文件的位置,而不是激活的环境

A:可执行文件在某个地方,但不在虚拟环境中

对于已安装的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'

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

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

假设虚拟环境的Python版本和嵌入的Python版本相同(否则我们会使用上面的情况),Emebed exe将使用本地端包。由于this rule,家庭搜索算法将始终查找本地家庭:

Step 3. Try to find prefix and exec_prefix relative to argv0_path, backtracking up the path until it is exhausted. This is the most common step to succeed. Note that if prefix and exec_prefix are different, exec_prefix is more likely to be found; however if exec_prefix is a subdirectory of prefix, both will be found.

在本例中argv0_path是exe的路径(没有pyvenv.cfg文件!),并且"landmarks"(lib/python$VERSION/os.py和lib/python$VERSION/lib dynload)将被找到,因为它们在exe上方的本地主目录中显示为符号链接

C:avenv-环境中的两个可执行文件夹

在一个venv环境中运行两个而不是一个文件夹(它工作的地方)会导致在搜索home时无法读取:pyvenv.cfg文件(上面太远),'venv`-环境缺少指向“landmarkers”的符号链接(仅存在本地端包),这样的步骤3将失败,而4. step是唯一的希望


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

  • 所需的文件被打包到嵌入可执行文件旁边的lib\pythonX.Y\*或上面的某个地方(并且周围没有pyvenv.cfg来扰乱搜索)

  • pyvenv.cfg用于将解释器指向正确的位置

相关问题 更多 >