__file__ 变量是什么意思/做什么?

326
import os

A = os.path.join(os.path.dirname(__file__), '..')

B = os.path.dirname(os.path.realpath(__file__))

C = os.path.abspath(os.path.dirname(__file__))

我通常只是使用实际路径来硬编码这些内容。但这些确定运行时路径的语句有其原因,我真的很想了解os.path模块,以便开始使用它。

8个回答

279
当在Python中从文件加载模块时,__file__会被设置为其absolute路径。然后,您可以将其与其他函数一起使用,以找到文件所在的目录。
逐个解释您的示例:
A = os.path.join(os.path.dirname(__file__), '..')
# A is the parent directory of the directory where program resides.

B = os.path.dirname(os.path.realpath(__file__))
# B is the canonicalised (?) directory where the program resides.

C = os.path.abspath(os.path.dirname(__file__))
# C is the absolute path of the directory where the program resides.

您可以在这里看到从这些返回的各种值。
import os
print(__file__)
print(os.path.join(os.path.dirname(__file__), '..'))
print(os.path.dirname(os.path.realpath(__file__)))
print(os.path.abspath(os.path.dirname(__file__)))

确保您从不同的位置运行它(例如./text.py~/python/text.py等等),看看这会有什么不同。

13
回答不错,但是从其他答案中需要考虑一个重要细节:__file__ 在某些情况下并没有定义,比如静态链接的 C 模块。我们不能指望 __file__ 总是可用。 - Chris Johnson
11
在解释器中,所有的示例都会返回name '__file__' is not defined - user1063287
17
看一下DemoUser的答案;__file__是模块加载时文件的路径名,如果是从文件加载的话。这意味着__file__只能在脚本中运行时才有效,而不能在解释器中使用(除非你在解释器中导入了它...)。 - YOUNG
1
当Python从文件中加载模块时,__file__会被设置为其路径。是哪个路径呢?抱歉我的英语不好。我可以说__file__总是指向当前的脚本/模块/文件吗? - VimNing

84

首先,我想澄清一些混淆。__file__不是通配符,而是一个属性。按照惯例,双下划线开头的属性和方法被认为是“特殊”的,并且具有特定的用途。

http://docs.python.org/reference/datamodel.html列出了许多特殊方法和属性,如果不是全部的话。

在这种情况下,__file__是一个模块的属性(即一个模块对象)。在Python中,一个.py文件就是一个模块。因此,import amodule会有一个__file__属性,在不同情况下具有不同的含义。

来自文档的摘录:

__file__是加载模块时其源文件的路径名,如果它是从文件中加载的话。对于静态链接到解释器中的C模块,没有__file__属性;对于从共享库动态加载的扩展模块,它是共享库文件的路径名。

在您的情况下,该模块在全局命名空间中访问自己的__file__属性。

要查看此操作,请尝试以下内容:

# file: test.py

print globals()
print __file__

然后运行:

python test.py

{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__file__':
 'test_print__file__.py', '__doc__': None, '__package__': None}
test_print__file__.py

28
根据 文档

__file__ 是模块加载时所在文件的路径名,如果是从文件加载的话。对于静态链接到解释器的 C 模块,没有 __file__ 属性;对于从共享库动态加载的扩展模块,则为共享库文件的路径名。

并且 还有:

__file__ 应该是指向文件的“路径”,除非模块是内置模块(因此列在 sys.builtin_module_names 中),在这种情况下不设置该属性。


23

仅在这里添加一个快速说明(主要回答问题的标题而不是其描述),关于一项更改可能会使某些人感到困惑。从Python 3.4开始,__file__的行为有了轻微的变化:

  • 如果直接执行该模块,则将其设置为所使用模块的相对路径。
  • 否则,将其设置为文件的绝对路径。

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

示例

直接调用模块x和间接调用模块y:

# 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__))

运行python3 x.py会输出:

/home/aderchox/mytest/y.py                                                                                                                       
/home/aderchox/mytest/y.py                                                                                                                       
x.py                                                                                                                                             
x.py                                                                                                                                             
/home/aderchox/mytest/x.py

3
从Python 3.9开始,默认情况下路径是绝对的(请参见https://dev59.com/4Gox5IYBdhLWcg3wVS4J#74360012)@Neutrinoceros - michen00
Python 3.9.2 - 我发现 file 经常包含 "/./"(我在路径中有 . 作为最后一个元素,请不要给我讲课!),Path().resolve() 可以解决这个问题。 - user3481644

16

使用__file__与各种os.path模块结合使用,可以使所有路径相对于当前模块的目录位置。这样可以让您的模块/项目在其他计算机上具有可移植性。

在您的项目中,您可以执行以下操作:

A = '/Users/myname/Projects/mydevproject/somefile.txt'

如果您尝试使用像 /home/web/mydevproject/ 这样的部署目录将其部署到服务器上,那么您的代码将无法正确找到路径。


7

作为对aderchox回答的补充,__file__变量的行为在Python 3.9中再次进行了更改,现在在所有情况下它都是一个绝对路径。

运行相同的示例(但为了自我一致性,在此处复制):

# 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

$ python3.8 x.py
/private/tmp/y.py
/private/tmp/y.py
x.py
x.py
/private/tmp/x.py

$ python3.9 x.py
/private/tmp/y.py
/private/tmp/y.py
/private/tmp/x.py
/private/tmp/x.py
/private/tmp/x.py

source: https://docs.python.org/3/whatsnew/3.9.html#other-language-changes


1

召唤死灵

[仅在Python 3.7中测试过]

我使用:

os.sep.join(__file__.split(os.sep)[:-1]

而且我更喜欢它,相比于我看到的其他解决方案。这是一些我写的测试代码:

from timeit import Timer
from typing import List, Tuple

N = 100000
TIMEITOUT = [None]


def _p_timeit(stmt: str, setup: str = ""):
    tte = Timer(f"TIMEITOUT[0]={stmt}", setup=f"{setup};from __main__ import TIMEITOUT").timeit(N)
    print(f"{stmt:<54}|{tte:>17.10f}    [output]: \"{TIMEITOUT[0]}\"")
    return stmt, tte


def _p_header():
    print(f"Testing w/ number={N} iterations\n{'=' * 72}")
    print(f"{'Statement':<54}|{'Time':^17}\n{'-' * 72}")


def _p_compare(a: Tuple[str, float], b_l: List[Tuple[str, float]]):
    print(f"\n{'=' * 72}\nComparing {a[0]} to all other statements:\n{'-' * 72}")
    for b in b_l:
        d = (b[1]-a[1])/a[1]
        cmp = f"faster than" if d > 0 else f"slower than" if d < 0 else f"equally (t={a[1]}) as fast as"
        print(f"{a[0]} was {(abs(d)*100):.2f}% {cmp} {b[0]}")


_p_header()

my_suggestion = _p_timeit("os.sep.join(__file__.split(os.sep)[:-1])", setup="import os")
others = [_p_timeit("os.path.abspath(os.path.dirname(__file__))", setup="import os"),
          _p_timeit("Path(__file__).parent", setup="from pathlib import Path"),
          _p_timeit("Path(__file__).resolve().parent", setup="from pathlib import Path")]
_p_compare(my_suggestion, others)

输出:

Testing w/ number=100000 iterations
========================================================================
Statement                                             |      Time       
------------------------------------------------------------------------
os.sep.join(__file__.split(os.sep)[:-1])              |     0.0640765000    [output]: "C:\Users\abrewer\AppData\Local\Programs\Python\Python37\lib"
os.path.abspath(os.path.dirname(__file__))            |     0.6156060000    [output]: "C:\Users\abrewer\AppData\Local\Programs\Python\Python37\lib"
Path(__file__).parent                                 |     0.7117664000    [output]: "C:\Users\abrewer\AppData\Local\Programs\Python\Python37\lib"
Path(__file__).resolve().parent                       |     9.3563913000    [output]: "C:\Users\abrewer\AppData\Local\Programs\Python\Python37\Lib"

========================================================================
Comparing os.sep.join(__file__.split(os.sep)[:-1]) to all other statements:
------------------------------------------------------------------------
os.sep.join(__file__.split(os.sep)[:-1]) was 860.74% faster than os.path.abspath(os.path.dirname(__file__))
os.sep.join(__file__.split(os.sep)[:-1]) was 1010.81% faster than Path(__file__).parent
os.sep.join(__file__.split(os.sep)[:-1]) was 14501.91% faster than Path(__file__).resolve().parent

请注意,如果__file__返回一个使用任何分隔符(例如:os.altsep)而不是os.sep的字符串,则程序将失败。我还没有受到这种影响,但现在谁知道呢...
如果需要,您可以首先在字符串中找到分隔符,然后使用该分隔符而不是os.sep。根据时间复杂度标准(查找分隔符的时间基本上是恒定的),速度提高将保持不变。

你的建议中缺少一个括号。 - undefined

0
使用pathlib.Path处理这个问题的最简单、最"pythonic"的方法是:
ex1 = Path(__file__).parent/"myfilename.xyz"



ex2 = Path(__file__).parent/"whatever_folder/myfilename.xyz"


ex3 = Path(__file__).parent/"../whatever_folder/myfilename.xyz"

因为你可以直接使用“/”。

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