模块的__file__属性是绝对路径还是相对路径?

126

我对__file__的理解有些困难。据我所知,__file__返回模块被加载时的绝对路径。

我遇到了如下问题:我有一个abc.py文件,其中只包含一条语句print __file__,当在/d/projects/目录下运行python abc.py时,会返回abc.py;而在/d/目录下运行则返回projects/abc.py。这是什么原因呢?


10
这是Guido对此发表的看法:http://mail.python.org/pipermail/python-dev/2010-February/097461.html。 - kindall
请参阅以下链接:https://dev59.com/4Gox5IYBdhLWcg3wVS4J - 0 _
5个回答

102

__file__ 在 Python 3.9+ 中保证是绝对路径。

在 Python 3.4 中 (changelog)

现在,模块的 __file__ 属性(以及相关值)默认情况下应该始终包含绝对路径,唯一的例外是当脚本使用相对路径直接执行时的 __main__.__file__

在 Python 3.9 中 (changelog)

... __main__ 模块的 __file__ 属性变为绝对路径


文档中:
引入模块的文件路径,如果是从文件中加载的话。对于某些类型的模块,例如静态链接到解释器中的C模块,可能会缺少__file__属性。对于从共享库动态加载的扩展模块,它是共享库文件的路径名。
从@kindall在评论中链接的邮件列表线程中:
我还没有尝试复现这个特定的例子,但原因是我们不想在每次导入时都调用getpwd(),也不想有一种在进程中缓存当前目录的变量。(getpwd()相对较慢,有时可能完全失败,并且尝试缓存它存在一定的错误风险。)
我们所做的是在site.py中编写代码,遍历sys.path的元素并将它们转换为绝对路径。然而,这段代码在''插入到sys.path的前面之前运行,因此sys.path的初始值是''。
在接下来的内容中,请将sys.path视为不包括''。
因此,如果你在不包含该模块的sys.path部分之外,你将得到一个绝对路径。如果你在包含该模块的sys.path部分之内,你将得到一个相对路径。
如果你在当前目录中加载一个模块,并且当前目录不在sys.path中,你将得到一个绝对路径。
如果你在当前目录中加载一个模块,并且当前目录在sys.path中,你将得到一个相对路径。

这是否意味着,如果从''到模块存在路径,则使用相对路径,否则将使用绝对路径,因为sys.path的其余部分是绝对路径。 - goh
4
如果你在当前目录下加载一个模块,且当前目录__不在__sys.path中,将会得到一个绝对路径。如果你在当前目录下加载一个模块,且当前目录__在__sys.path中,将会得到一个相对路径。 - agf
请记住,为此目的,sys.path 不包括 '' - agf
明白了,但是 @agf,如果我从 /home 使用 python /foo/abc.py,我想 sys.path 中包含模块的部分应该是 /home/foo,而我的当前目录是 /home/,为什么 print file 给出相对路径? - goh

62

__file__ 是绝对路径自 Python 3.4 开始, 但在使用相对路径直接执行脚本时除外:

默认情况下,模块 __file__ 属性(及相关值)现在应始终包含绝对路径,唯一的例外是当使用相对路径直接执行脚本时,__main__.__file__。 (由 Brett Cannon 在 bpo-18416 中贡献。)

不确定它是否解析符号链接。

传递相对路径的示例:

$ python script.py

4
对于Python 3.4.0(Python 3.4.0(默认版本,Apr 11 2014,13:05:11)[GCC 4.8.2] on linux),这不是真实情况。在我的测试中,符号链接未被解析。 - Frozen Flame
@FrozenFlame,如果3.4.1不能解决问题,请随时向https://bugs.python.org/报告。 - anatoly techtonik
2
自 Python 3.9 开始,它不再总是使用绝对路径。https://docs.python.org/3/whatsnew/3.9.html#other-language-changes - colinfang

20

简单的延迟示例:

from os import path, getcwd, chdir

def print_my_path():
    print('cwd:     {}'.format(getcwd()))
    print('__file__:{}'.format(__file__))
    print('abspath: {}'.format(path.abspath(__file__)))

print_my_path()

chdir('..')

print_my_path()

在Python-2.*版本中,第二个调用会基于当前目录不正确地确定path.abspath(__file__)的路径:

cwd:     C:\codes\py
__file__:cwd_mayhem.py
abspath: C:\codes\py\cwd_mayhem.py
cwd:     C:\codes
__file__:cwd_mayhem.py
abspath: C:\codes\cwd_mayhem.py

正如@techtonik所指出的,在Python 3.4+中,这将能够正常工作,因为__file__返回绝对路径。


1
除了 __main__ 模块,其中 __file__ 可能是相对路径。 - 0xC0000022L
即使在__main__中,现在在3.9+版本中也将是绝对的,这在changelog中已经提到了。 - undefined

5
借助@kindall提供的Guido邮件的帮助,我们可以理解标准导入过程是尝试在sys.path的每个成员中找到模块,并将文件作为此查找的结果(有关详细信息,请参阅PyMOTW Modules and Imports)。因此,如果模块位于sys.path的绝对路径中,则结果是绝对的,但如果它位于sys.path的相对路径中,则结果是相对的。

现在,site.py启动文件负责仅传递sys.path中的绝对路径,除了初始值''之外,因此,如果您不通过设置PYTHONPATH(其路径也会在加上sys.path之前变为绝对路径)而改变它,您将始终获得绝对路径,但当通过当前目录访问模块时。

现在,如果您以一种有趣的方式欺骗sys.path,则可以获得任何内容。

例如,如果您在/tmp/中有一个名为foo.py的样本模块,其中包含以下代码:

import sys
print(sys.path)
print (__file__)

如果您进入 /tmp 目录,您将会看到:
>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
./foo.py

当在/home/user目录下时,如果将/tmp添加到PYTHONPATH中,则会得到以下结果:

>>> import foo
['', '/tmp', '/usr/lib/python3.3', ...]
/tmp/foo.py

即使您添加了../../tmp,它也会被规范化,结果是相同的。

但是,如果您不使用PYTHONPATH,而是直接使用一些有趣的路径,那么您将得到一个与原因一样有趣的结果。

>>> import sys
>>> sys.path.append('../../tmp')
>>> import foo
['', '/usr/lib/python3.3', .... , '../../tmp']
../../tmp/foo.py

Guido在上述帖子中解释了为什么Python不会尝试将所有条目转换为绝对路径:

我们不想在每次导入时都调用getpwd() .... getpwd()相对较慢,有时可能完全失败。

因此,您的路径将 按原样使用


4

__file__ 可以是相对路径或绝对路径,这取决于使用的 Python 版本以及模块是否直接执行。

简而言之:

  • 从 Python 3.5 到 3.8,如果直接调用模块,则 __file__ 设置为相对于当前工作目录的模块路径。否则,它将被设置为绝对路径。
  • 自 Python 3.9 开始,即使对应的模块直接执行,__file__ 也会被设置为绝对路径。

例如,有以下设置(受此 answer 的启发):

# x.py:
from pathlib import Path
import y
print(__file__)
print(Path(__file__))
print(Path(__file__).resolve())

# y.py:
from pathlib import Path
print(__file__)
print(Path(__file__))

在同一目录下有 x.pyy.py 。进入该目录并执行后,会得到不同的输出结果。
python x.py

是:

Python 3.5 - 3.8:

D:\py_tests\y.py
D:\py_tests\y.py
x.py
x.py
D:\py_tests\x.py

Python 3.9 - 3.11

D:\py_tests\y.py
D:\py_tests\y.py
D:\py_tests\x.py
D:\py_tests\x.py
D:\py_tests\x.py

所以从Python 3.9开始,__file__始终是一个绝对路径,永远不会是相对路径了吗? - undefined
@user0 是的,截至今天(3.11 是最新的 Python 版本),__file__ 总是一个绝对路径。 - undefined
1
官方的Python文档中有没有提到这个?我能找到的最接近的是这个链接 - undefined
@user0 你的链接是一个很好的参考。它还解释了一个结果,即在Python 3.9之前,如果你使用os.chdir()改变了工作目录(即调用__main__模块的目录,如上面的例子中的D:\py_tests),__main__模块的__file__属性将会无效。这是因为__file__存储了一个常量相对路径,不受os.chdir()产生的变化影响。在Python 3.9之后,由于__file__包含了一个绝对路径,它的值在os.chdir()产生的变化之后仍然有效。 - undefined

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