模块 __file__ 属性是绝对的还是相对的?
- 2025-03-05 09:16:00
- admin 原创
- 75
问题描述:
我很难理解__file__
。据我了解,__file__
返回加载模块的绝对路径。
我在生成此代码时遇到了问题:我有一个abc.py
带有一条语句的语句print __file__
,从/d/projects/
python abc.py
returns运行。从returnsabc.py
运行。有什么原因吗?/d/
`projects/abc.py`
解决方案 1:
__file__
在 Python 3.9+ 中保证是绝对路径。
在 Python 3.4 中(更新日志)
模块
__file__
属性(和相关值)现在应该始终默认包含绝对路径,唯一的例外是__main__.__file__
直接使用相对路径执行脚本时。
在 Python 3.9 (更新日志)中:
...模块
__file__
的属性__main__
变成了绝对路径
来自文档:
如果模块是从文件加载的,则为加载模块的文件的路径名。
__file__
某些类型的模块(例如静态链接到解释器的 C 模块)可能缺少此属性。对于从共享库动态加载的扩展模块,它是共享库文件的路径名。
来自@kindall在问题评论中链接的邮件列表主题:
我没有尝试重现这个特定的例子,但原因是我们不想在每次导入时都调用 getpwd(),也不想使用某种进程内变量来缓存当前目录。(getpwd() 相对较慢,有时会直接失败,尝试缓存它有一定的错误风险。)
我们所做的是,在 site.py 中遍历 sys.path 的元素并将它们转换为绝对路径。但是,此代码在 sys.path 前面插入 '' 之前运行,因此 sys.path 的初始值为 ''。
对于其余部分,考虑sys.path
不包括''
。
因此,如果您位于sys.path
包含模块的部分之外,您将获得绝对路径。如果您位于sys.path
包含模块的部分之内,您将获得相对路径。
如果您在当前目录中加载模块,并且当前目录不在,sys.path
那么您将获得绝对路径。
如果您在当前目录中加载模块,并且当前目录位于,那么sys.path
您将获得相对路径。
解决方案 2:
__file__
自 Python 3.4 以来是绝对的,除非直接使用相对路径执行脚本:
模块
__file__
属性(和相关值)现在应始终默认包含绝对路径,唯一的例外是__main__.__file__
使用相对路径直接执行脚本时。(由 Brett Cannon 在bpo-18416中贡献。)
但不确定它是否能解析符号链接。
传递相对路径的示例:
$ python script.py
解决方案 3:
后期简单示例:
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:codespy
__file__:cwd_mayhem.py
abspath: C:codespycwd_mayhem.py
cwd: C:codes
__file__:cwd_mayhem.py
abspath: C:codescwd_mayhem.py
正如@techtonik 所指出的,在 Python 3.4+ 中,这将正常工作,因为__file__
它返回一个绝对路径。
解决方案 4:
__file__
可以是相对的也可以是绝对的,这取决于所使用的python版本以及是否直接执行模块。
总结:
从 python 3.5 到 3.8,
__file__
如果直接调用模块,则设置为模块相对于当前工作目录的相对路径。否则设置为绝对路径。从python3.9开始,
__file__
即使直接执行相应模块也设置为绝对路径。
这种行为在 Python 3.9 的新功能中进行了解释(感谢@user0 的评论):
Python 现在获取命令行上指定的脚本文件名的绝对路径(例如
python3 script.py
:):模块__file__
的属性__main__
变为绝对路径,而不是相对路径。在当前目录被更改后,这些路径现在仍然有效os.chdir()
。作为副作用,__main__
在这种情况下,回溯还会显示模块框架的绝对路径。
玩具示例:
例如,具有以下设置(受此答案启发):
# 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.py
位于y.py
同一目录中。进入此目录并执行后的不同输出:
python x.py
是:
Python 3.5 – 3.8:
D:py_testsy.py
D:py_testsy.py
x.py
x.py
D:py_testsx.py
Python 3.9 – 3.11
D:py_testsy.py
D:py_testsy.py
D:py_testsx.py
D:py_testsx.py
D:py_testsx.py
解决方案 5:
借助@kindall 提供的 Guido 邮件,我们可以将标准导入过程理解为尝试在 的每个成员中找到模块sys.path
,并将文件作为此查找的结果(更多详细信息请参阅PyMOTW 模块和导入)。因此,如果模块位于 中的绝对路径中,则sys.path
结果为绝对路径,但如果它位于 中的相对路径中,sys.path
则结果为相对路径。
现在,site.py
启动文件负责仅提供 中的绝对路径sys.path
,除了初始''
,所以如果您不通过设置 PYTHONPATH(其路径在前缀 之前也是绝对的sys.path
)的其他方式更改它,您将始终获得一个绝对路径,但是当通过当前目录访问模块时。
现在,如果您以一种有趣的方式欺骗 sys.path,您就可以得到任何东西。
例如如果你有一个foo.py
包含/tmp/
代码的示例模块:
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() 相对较慢,有时甚至会直接失败,
因此您的路径将按原样使用。
扫码咨询,免费领取项目管理大礼包!