在Python导入之前设置LD_LIBRARY_PATH

61

Python使用PYTHONPATH环境变量来确定它应该查找哪些文件夹中的模块。 您可以通过修改sys.path进行尝试,这对于纯Python模块非常有效。 但是,当模块使用共享对象文件或静态库时,它会在LD_LIBRARY_PATH(在Linux上)中查找这些文件,但这很难更改,并且据我所知,这取决于平台。

解决此问题的快速方法当然是设置环境变量或像LD_LIBRARY_PATH =。./script.py这样调用脚本,但这样做需要为您打开的每个新的shell重新设置它。 此外,对于我来说,.so文件将始终在与.py文件相同的目录中,但是可能会移动到另一个绝对路径,因此我希望每次调用脚本时自动设置它们。

我如何在运行时独立于平台地编辑Python解释器查找库的路径?

编辑:

我已经尝试过os.environ['LD_LIBRARY_PATH'] = os.getcwd(),但没有用。


4
这个问题可能应该由模块安装程序来处理,通过将共享库安装在标准位置(尽管可能是特定于机器的)来完成。 - chepner
使用 virtualenv :) @chepner - Erik Kaplun
@chepner考虑了一下之后,安装软件时需要考虑平台相关性。此外,Windows不使用.so.a文件,而是使用.dll.lib文件,我的库将不得不以某种方式重新编译以适应它。我只是觉得一个快速而简单的解决方案会方便测试和开发。 - iFreilicht
我删除了一个相关主题的答案,并发布了一个新问题:尝试导入pypyodbc模块时出现错误“未找到ODBC库。是否设置了LD_LIBRARY_PATH?” - fedorqui
可能是在运行时更改ctypes的LD_LIBRARY_PATH的重复问题。 - sdikby
请查看此链接:https://dev59.com/B3M_5IYBdhLWcg3w43pt#16517435 - cdarlint
5个回答

28

更新:请参见下面的编辑。

我会使用:

import os

os.environ['LD_LIBRARY_PATH'] = os.getcwd()  # or whatever path you want

这将仅为当前进程的执行期/寿命设置LD_LIBRARY_PATH环境变量。

编辑:看起来需要在启动Python之前设置此项:运行时更改CTypes的LD_LIBRARY_PATH

因此,建议使用包装器.sh(或.py如果您坚持)脚本。 此外,正如@chepner指出的那样,您可能希望考虑在标准位置(virtualenv内部)安装.so文件。

另请参见从Python内部设置LD_LIBRARY_PATH


3
抱歉,我应该提到我已经尝试过那个方法,但它并不起作用。在那之后的导入语句仍然会抛出一个“ImportError”,而如果我使用“LD_LIBRARY_PATH =。./script.py”调用脚本,则不会发生这种情况。 - iFreilicht
我完全不清楚包装脚本应该包含什么。 - abcd
将库安装到virtualenv的标准位置是一个好建议。 - 0 _
这个可行吗?我在编译后的PyQt5的__init__.py中尝试添加额外的LD_LIBRARY_PATH路径到Maya 2017安装中,但是当我从Python解释器运行from PyQt5.QtWidgets import QApplication, QWidget时,它仍然显示“ImportError: libQt5Widgets.so.5:无法打开共享对象文件:没有那个文件或目录”。 - Shuman
尝试使用Qt、Python、Maya标签提出问题。 - Erik Kaplun
显示剩余2条评论

20

我解决这个问题的方法是将以下内容作为Python脚本的第一行(而不是通常的shebang):

exec env LD_LIBRARY_PATH=/some/path/to/lib /path/to/specific/python -x "$0" "$@"

以下是它的工作原理:

  • 没有shebang时,当前shell将文件视为shell脚本,
  • "exec"确保这个文件的第一行也是shell执行的最后一个命令,
  • "env"用于设置任何环境变量,例如LD_LIBRARY_PATH,
  • 可以指定Python解释器的精确路径或者"env"可以在PATH中找到一个,
  • "-x"是Python的选项,它会导致Python解释器忽略掉第一行代码,
  • "$0"是脚本名称,"$@"会被替换为定位参数。

15

Python中,当使用os.environ['LD_LIBRARY_PATH']os.environ['PATH']获取环境变量的值时,它会从父进程的环境(通常是bash)中复制这些值到一个字典中(即子进程的环境,也就是Python运行实例继承自bash进程的环境)。

你可以通过在bash中使用env命令来查看此环境变量部分的输出。

你还可以通过在修改任何环境变量后引入一个无限循环(while 1: pass)来从/proc/<pid>/environ中查看/读取此环境数据。

如果在Python脚本中修改此变量的值并从/proc/<pid>/environ中查看/读取,你会发现实际的变量数据并没有被修改,尽管Python脚本显示了修改后的字典键值。

实际上,当你在Python脚本内修改一个环境变量(如os.environ['LD_LIBRARY_PATH']='/<new_location>')时,它只是更新了本地字典中的值,而该字典并未映射到进程的环境变量区域。因此,它不会传播到当前进程的环境中,因为仅仅是修改了本地字典。

因此,如果想要反映新的环境变量值,我们应该使用execv来覆盖进程的内存镜像,以使用新的环境变量数据。

示例:

new_lib = '/<new_location>'
if not new_lib in os.environ['LD_LIBRARY_PATH']:
    os.environ['LD_LIBRARY_PATH'] += ':'+new_lib
    try:
        os.execv(sys.argv[0], sys.argv)
    except Exception as e:
        sys.exit('EXCEPTION: Failed to Execute under modified environment, '+e)

import xyz
#do something else

限制:理想情况下,Python 不应该允许修改 os.environ 变量。 但是由于没有常量字典数据类型,它允许对数据变量进行修改。 修改这些值没有任何用处,因为除非使用 execv,否则在运行过程的真实环境中不会反映任何有用信息。


关于这个问题:但是因为没有常量字典数据类型,我相信如果他们尝试将只读项放入映射代理(type(type.__dict__))中,在导入到osnt/posix模块变量中作为静态环境变量,其中os.environ可以是一个envdict实例,在__getitem__()上合并静态和非静态名称,但这不是很重要,所以他们不在意。 ;) - Tcll
也许需要找出实际用于执行程序的命令行... - Antti Haapala -- Слава Україні
2
不,这不应该是只读字典。修改os.environ很有用,因为它是传递到子进程中的内容(这就是为什么你的execve技巧有效的原因)。Python 不会将环境复制到dict中;它通过getenv/putenv访问进程的环境。您的更大的观点仍然是正确的:改变环境不会影响当前进程。特别是LD_LIBRARY_PATH已经在运行python代码之前被查询了。从man dlopen中可以看出:如果在程序启动时定义了环境变量LD_LIBRARY_PATH... - sfink

3
解决方案在环境重新初始化时运行良好。
import os

os.environ['LD_LIBRARY_PATH'] = os.getcwd()  # or whatever path you want 

需要将代码放置在......的位置。

os.execv(sys.argv[0], sys.argv)

2
自从coreutils 8.30版本以后,可以使用env -S将shebang行分割成单独的参数:
#!/usr/bin/env -S LD_LIBRARY_PATH=/path/to/lib python options

为了与旧系统兼容,您可以利用shell允许所有命令被引号包含的特点,而在python中它只是字符串:

#!/bin/sh
"export" "LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH"
"exec" "python3" "$0" "$@"
# Further python program
import somemodule

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接