通常情况下,Python解释器并不是“独立的”,为了正常工作,它需要其标准库(例如编译库
ctypes
或解释库
site.py
)以及其他站点包的路径(例如
numpy
)必须设置好。
虽然可以通过冻结py模块并合并所有c扩展(例如
this SO-post)到生成的可执行文件中,使Python解释器完全独立,但更容易提供所需的安装程序给嵌入式解释器。可以从Python主页下载所需的“标准”安装文件(至少对于
windows),也可以参考这个
SO-question。
有时找到标准模块/站点包并不能立即使用:必须通过设置Python-path来帮助解释器,例如向sys.path中添加
<..>/sometest/lib/python3.5/site-packages
(其中
sometest
是虚拟环境根文件夹),要么在pyx文件中以编程方式添加,要么在启动之前通过设置PYTHONPATH环境变量来添加。更多详细信息和替代方案请接着阅读。
本答案适用于Linux和Python3(Python 3.7),基本思路对于Windows/MacOS也是相同的,但某些细节可能会有所不同。
因为使用了venv
,我们有以下解决方法:
- 在pyx文件中编程或在启动前设置
PYTHONPATH
环境变量,将<..>/sometest/lib/python3.5/site-packages
(其中sometest
是虚拟环境根目录)添加到sys.path
中。
- 将带有嵌入式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
。
然而,使用venv
时,有一个pyvenv.cfg
文件在exe旁边或父目录中, 确保找到正确的Python-Home - 这个文件中的home
键是一个很好的起点。
如果未设置
Py_NoSiteFlag
,
Py_Initialize
将利用
site.py
(它可以通过解释器找到,因为已知
sys.prefix
),或更精确地说,利用
site.main()
,将虚拟环境的 site-packages 添加到
sys.path
。在此过程中,
site.py
查找
pyvenv.cfg
并解析它。但是,仅当以下情况时,本地的
site-packages
才会添加到 python-path 中:
如果存在一个名为“pyvenv.cfg”的文件在sys.executable的上一级目录中,那么sys.prefix和sys.exec_prefix将被设置为该目录,并且还会检查其中是否包含site-packages(sys.base_prefix和sys.base_exec_prefix始终是Python安装的“真实”前缀)。
在您的情况下,pyvenv.cfg不在上面的目录中,而是与exe文件在同一目录中 - 因此通过pip安装的库所在的本地site-packages未包括在内。全局site-packages未包括在内,因为pyvenv.cfg具有键include-system-site-packages = false。因此,不允许使用site-packages,因此找不到安装的库。
但是,将exe文件移动到下一个目录中,将导致包含本地site-packages到路径中。
有其他可能的情况,重要的是可执行文件的位置,而不是哪个环境被激活。
A: 可执行文件在某个地方,但不在虚拟环境中
这种搜索启发式算法对于已安装的python解释器来说比较可靠,但对于嵌入式解释器或虚拟环境可能会出现问题(请参见this issue获取更多信息)。
如果使用常规的apt install
或类似方法安装了Python,则会被找到(由于搜索算法中的第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 版本相同(否则我们就有了上面的情况),嵌入式可执行文件将使用本地侧包。由于
此规则,主目录搜索算法将始终找到本地主目录:
第 3 步。尝试相对于 argv0_path 找到前缀和 exec_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`-environments缺乏到“landmarkers”的符号链接(仅在本地存在边缘包),因此步骤3将失败,
第4步是唯一的希望。
推论:除非满足以下情况之一,否则嵌入式Python将无法正常工作:
PYTHONPATH=/usr/lib/python3/dist-packages/ ./hello
可以工作。虽然我感到有点尴尬,因为它是如此简单,但我认为它足够有价值,不应该删除。如果您回答,我会接受。再次感谢! - r2evans