在Ubuntu 14.04上PYTHONPATH的顺序

14

我有两台运行Ubuntu 14.04服务器的计算机(让我们称它们为A和B)。 B最初是10.04,但已接受了两个升级:12.04和14.04。 我不明白为什么两台计算机上的Python路径不同。

正如您下面看到的两个路径所示,在Ubuntu A上,pip安装路径/usr/local/lib/python2.7/dist-packages在apt python软件包路径/usr/lib/python2.7/dist-packages之前,但在Ubuntu B上则相反。

如果通过apt和pip都安装了Python软件包,则会出现几个问题。 如下所示,如果同时安装了python-six apt包和six pip包,则它们可能是两个不同的库版本。

软件包的安装系统并不总是我的选择,但可能是安装的其他软件包的某些依赖关系。

这个问题可能可以通过virtualenv解决,但由于某些原因,我不能在这里使用virtualenv,必须在整个系统中安装pip软件包。

Ubuntu A

>>> import sys, six
>>> sys.path
['',
 '/usr/local/bin',
 '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-x86_64-linux-gnu',
 '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old',
 '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PILcompat',
 '/usr/local/lib/python2.7/dist-packages/IPython/extensions']
>>> six
<module 'six' from '/usr/local/lib/python2.7/dist-packages/six.pyc'>

Ubuntu B

>>> import sys, six
>>> sys.path
['',
 '/usr/local/bin',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-x86_64-linux-gnu',
 '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old',
 '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/PILcompat',
 '/usr/local/lib/python2.7/dist-packages/IPython/extensions']
>>> six
>>> <module 'six' from '/usr/lib/python2.7/dist-packages/six.pyc'>

对于这两台机器来说,$PATH是相同的,而$PYTHONPATH是空的。

  • 为什么这些 PYTHONPATH 不同?

  • 我该如何在“Ubuntu B”中修复 pythonpath 的顺序,以系统范围内的方式优先加载 pip 包而不是系统包?是否有一个apt包我应该重新安装或重新配置,以便 PYTHONPATH 会优先选择pip软件包?


1
最好使用Python虚拟环境,并仅安装所需内容。这样,通过系统软件包安装的任何程序都将被忽略,不会引起问题。 - Graham Dumpleton
为什么不永久更改路径顺序呢? - Padraic Cunningham
5个回答

6
由于我们无法探索您的系统,我尝试通过说明如何初始化 sys.path 来分析您的第一个问题。 可用的参考文献是 where-does-sys-path-startspyco-reverse-engineering(python2.6)。 sys.path 来自以下变量(按顺序):
  1. $PYTHONPATH(最高优先级)
  2. sys.prefix -ed stdlib
  3. sys.exec_prefix -ed stdlib
  4. site-packages
  5. *.pth 在 site-packages 中(最低优先级)
现在让我们描述每个变量:
  1. $PYTHONPATH,这只是一个系统环境变量。
  2. & 3. sys.prefixsys.exec_prefix 在执行任何 python 脚本之前确定。 它实际上是在源代码中编码的 Module/getpath.c
逻辑如下:
IF $PYTHONHOME IS set:
    RETURN sys.prefix AND sys.exec_prefix as $PYTHONHOME
ELSE:
    current_dir = directory of python executable;
    DO:
        current_dir = parent(current_dir)
        IF FILE 'lib/pythonX.Y/os.py' EXSITS:
            sys.prefix = current_dir
        IF FILE 'lib/pythonX.Y/lib-dynload' EXSITS:
            sys.exec_prefix = current_dir
        IF current_dir IS '/':
            BREAK
    WHILE(TRUE)
    IF sys.prefix IS NOT SET:
       sys.prefix = BUILD_PREFIX
    IF sys.exec_prefix IS NOT SET:
       sys.exec_prefix = BUILD_PREFIX
  1. site.py 模块通过导入 site-packages*.pth,将它们添加到模块搜索路径中。 在此模块中,您可以找到以下文档:

    这将在特定于站点的路径中增加模块搜索路径。 在Unix(包括Mac OSX)上,它从 sys.prefix 和 sys.exec_prefix(如果不同)开始,并附加 lib/python/site-packages 以及 lib/site-python。

    对于 Debian 及其衍生版本,此 sys.path 会被增强为分布在整个系统中的包目录。本地插件存放在 /usr/local/lib/python/dist-packages 中,Debian 插件安装到 /usr/{lib,share}/python/dist-packages。/usr/lib/python/site-packages 不使用。

    路径配置文件是一种名为 .pth 的文件,其内容为要添加到 sys.path 中的其他目录(每行一个)。

以下是重要函数 getsitepackages 的代码段:

sitepackages.append(os.path.join(prefix, "local/lib",
                            "python" + sys.version[:3],
                            "dist-packages"))
sitepackages.append(os.path.join(prefix, "lib",
                            "python" + sys.version[:3],
                            "dist-packages"))

现在我正试图找出这个奇怪问题的根源:

  1. $PYTHONPATH不可能,因为A和B都是空的。
  2. sys.prefixsys.exec_prefix可能是问题所在,请检查它们以及$PYTHONHOME
  3. site.py也可能是问题所在,请检查该文件。

机器B的sys.path输出非常奇怪,dist-package(site-package)在sys.exec_prefix(lib-dynload)之前。请尝试调查机器B中sys.path初始化的每一步骤,可能会发现某些有用信息。

非常抱歉我无法复制你的问题。顺便说一下,关于你的问题标题,我认为SYS.PATHPYTHONPATH更好,因为让我误解成了$PYTHONPATH


2
如果你查看Python的site.py文件,你可以通过在文本编辑器中打开/usr/lib/python2.7/site.py来实现。
sys.path会增加用于分发在分发中的包的目录。本地插件放在/usr/local/lib/python/dist-packages中,全局插件安装在/usr/{lib,share}/python/dist-packages中。
你可以通过重写此方法来改变顺序:
def getsitepackages():
    """Returns a list containing all global site-packages directories
    (and possibly site-python).

    For each directory present in the global ``PREFIXES``, this function
    will find its `site-packages` subdirectory depending on the system
    environment, and will return a list of full paths.
    """
    sitepackages = []
    seen = set()

    for prefix in PREFIXES:
        if not prefix or prefix in seen:
            continue
        seen.add(prefix)

        if sys.platform in ('os2emx', 'riscos'):
            sitepackages.append(os.path.join(prefix, "Lib", "site-packages"))
        elif os.sep == '/':
            sitepackages.append(os.path.join(prefix, "local/lib",
                                        "python" + sys.version[:3],
                                        "dist-packages"))
            sitepackages.append(os.path.join(prefix, "lib",
                                        "python" + sys.version[:3],
                                        "dist-packages"))
        else:
            sitepackages.append(prefix)
            sitepackages.append(os.path.join(prefix, "lib", "site-packages"))
        if sys.platform == "darwin":
            # for framework builds *only* we add the standard Apple
            # locations.
            from sysconfig import get_config_var
            framework = get_config_var("PYTHONFRAMEWORK")
            if framework:
                sitepackages.append(
                        os.path.join("/Library", framework,
                            sys.version[:3], "site-packages"))
    return sitepackages

这似乎比玩弄PYTHONPATHsys.path更像是hacky的。 - edwinksl
这是为了让提问者深入了解为什么在两个Ubuntu版本上会出现不同的行为,以便他可以选择最适合他的解决方案。Virtualenv和PYTHONPATH是推荐的方法。 - Anant Bhardwaj
但是你的回答实际上没有解释为什么在两个具有相同操作系统的系统上,/usr/local/lib/python2.7/dist-packages/usr/lib/python2.7/dist-packages的顺序在其他完全相同的sys.path中不同。 - edwinksl

1
这里要提一点的是,有时候 *.pth 文件会完全弄乱 sys.path 的优先级。
例如,在我的 14.04 ubuntu 上,/usr/local/lib/python2.7/dist-packages/easy-install.pth 看起来像这样:
import sys; sys.__plen = len(sys.path)
/usr/lib/python2.7/dist-packages
import sys; new = sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p = getattr(sys, '__egginsert', 0); sys.path[p:p] = new; sys.__egginsert = p + len(new)

请注意,/usr/lib/python2.7/dist-packages被预置 - 即使在PYTHONPATH之前 - 这意味着您无法真正影响sys.path。
要使您的/usr/local/lib/python2.7/dist-packages库优先,请编辑/usr/local/lib/python2.7/dist-packages/easy-install.pth文件,如下所示:
import sys; sys.__plen = len(sys.path)
/usr/local/lib/python2.7/dist-packages
/usr/lib/python2.7/dist-packages
import sys; new = sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p = getattr(sys, '__egginsert', 0); sys.path[p:p] = new; sys.__egginsert = p + len(new)

-1

最简单的方法是使用sys.path,以确保您添加的路径顺序正确。 sys.path会输出可用于PYHTONPATH环境变量的路径列表,并按正确的顺序排列。如果您想让任何路径优先于其他路径,请将其添加到列表的开头。

您还可以在官方文档中找到这个:

程序可以自由修改此列表以适应自己的目的。

警告:尽管这样可以更好地控制优先级,但请确保您添加的任何库不会干扰系统库。否则,您的库将首先被搜索,因为它位于列表的开头,它们可能会替换系统库。举个例子,如果您编写了一个名为os的库,并将其添加到sys.path中,在导入时该库将被导入而不是Python内置的库。因此,在进行此操作之前,请谨慎行事并多加注意。


如果有需要,我很乐意进行编辑/添加。 - thiruvenkadam

-2

PYTHONPATH 是一个环境变量,您可以根据需要设置它来添加其他目录。您不应该手动安装 Python 包,而应该使用 pip。在旧版 Ubuntu 上,可能在升级之前手动安装了模块。


我编辑了我的问题,使其更加精确。有时候软件包的安装并不是我的选择,而只是其他软件包的依赖关系。 - azmeuk

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