为什么PYTHONPATH末尾带有冒号会将当前目录添加到sys.path中?

6

考虑一个像这样的 Python 项目:

foo/
    __init__.py
scripts/
    run.py
demo.sh

通常情况下,如果您从项目根目录运行脚本,则尝试从foo包导入将失败,因为默认的Python行为是将调用Python解释器的脚本所在目录(而不一定是当前目录)添加到sys.path。(文档):

python scripts/run.py

然而,最近我注意到这样的导入在我的电脑上可以正常工作,经过追踪后发现与PYTHONPATH相关的一些令人惊讶的行为。在我的Bash配置文件中,我已经将一个目录添加到了PYTHONPATH中:

export PYTHONPATH="/some/path:$PYTHONPATH"

如果PYTHONPATH最初为空,则该命令的形式(有些松散,但通常可见)将留下一个尾随冒号:
echo $PYTHONPATH
/some/path:

我曾经认为这个结尾标点符号没有影响,但事实上这个冒号是神秘成功导入的原因。前置或后置的冒号(甚至是定义但为空的PYTHONPATH)会导致在site模块加载之前,sys.path包含一个空字符串,从而将当前工作目录添加到sys.path中。以下是Python脚本和Bash脚本示例。我在Python 2.7和Python 3.3中都得到了相同的结果。
Python脚本:run.py:
import sys, os

pp = os.environ.get('PYTHONPATH')

try:
    import foo
    print 'OK'
    assert os.getcwd() in sys.path
    assert pp == '' or pp.startswith(':') or pp.endswith(':')

except Exception:
    print 'FAIL'
    assert os.getcwd() not in sys.path
    assert pp is None

Bash脚本:demo.sh

# Import fails.
unset PYTHONPATH;  python scripts/run.py

# Import succeeds -- to my surprise.
PYTHONPATH=''      python scripts/run.py
PYTHONPATH='/tmp:' python scripts/run.py
PYTHONPATH=':/tmp' python scripts/run.py

我的问题:

  • 我是否正确理解了情况,还是我走错了路?

  • 这有文件记录吗?我没有找到任何信息。至少,我在这里发布这些信息,以防对他人有所帮助。

  • 我一个人认为这种行为出乎意料吗?


无法在Windows上复制。 - Sebastian Wozny
你对发生的事情是正确的,我也觉得这种行为是意外和不可取的。在我看来,这值得向 Python 提交一个错误报告,但是长期存在的奇怪问题在修复时可能会破坏一些东西。 - tdelaney
1
或许我撤回之前的说法。export "PATH=$PATH:" 允许在 Linux 上执行本地路径下的命令。Python 的工作方式也是如此。 - tdelaney
@tdelaney 谢谢 - 这是一个好的测试。我猜Python的行为在Unix的行为下是有意义的...但它们都似乎不符合直觉。也许这里有一个深层次的逻辑。 - FMc
是的-只是重复了你的发现,然后在谷歌上搜索了"python路径尾部冒号"并偶然间找到了你的答案。至少可以说这种方法不太理想。 - avloss
1个回答

6
当修改使用冒号分隔的环境变量时,例如PYTHONPATH、PATH、CPATH、MANPATH、LD_LIBRARY_PATH、PKG_CONFIG_PATH等等...其中一些变量对尾部的冒号有特殊含义,而其他变量则没有。
对于PYTHONPATH和PATH,我建议在未设置变量的情况下,在不意外引入尾随(或前导)冒号的情况下添加(或追加)新目录:
export PYTHONPATH="/some/path${PYTHONPATH+":"}${PYTHONPATH-}"

(对于MANPATH和INFOPATH,您确实希望引入一个尾随冒号,以便maninfo将包含它们的默认搜索目录。)

解释:

  • ${PYTHONPATH+":"}会在PYTHONPATH设置的情况下扩展为:,无论PYTHONPATH是否为空。
  • ${PYTHONPATH-}将根据其是否设置,将扩展为PYTHONPATH的内容,但如果PYTHONPATH未设置,则${PYTHONPATH-}将扩展为什么都没有——就像通常的${PYTHONPATH}一样。

    • ${PYTHONPATH-}${PYTHONPATH-""}相同,表示在PYTHONPATH未设置时替换为""(空)。
    • 我推荐在这里使用${PYTHONPATH-}而不是${PYTHONPATH}的原因是,在PYTHONPATH未设置且脚本已执行set -u以在未设置变量时引发错误时,${PYTHONPATH-}不会创建错误。

有关 ${parameter+[word]}${parameter-[word]} 机制的详细信息,请参见http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02中的“参数扩展”。

有关set -u的详细信息,请参见http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set中的“set”描述。


1
如果PYTHONPATH未绑定,那么/some/path${PYTHONPATH+:${PYTHONPATH}}不会引发错误,因为右侧的${PYTHONPATH}只有在PYTHONPATH被绑定时才会被评估。此外,如果您不想在PYTHONPATH最初设置为空的情况下以尾随冒号结束,则可能需要/some/path${PYTHONPATH:+:${PYTHONPATH} - Philippe Carphin

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