如何可靠地在当前运行脚本的目录中打开文件。

255

我曾经通过类似下面这样的命令来打开与当前运行Python脚本在同一目录下的文件:

open("Some file.txt", "r")

然而,我发现当通过双击在Windows上运行脚本时,它会尝试从错误的目录打开文件。

自那以后,我使用的命令形式如下:

open(os.path.join(sys.path[0], "Some file.txt"), "r")

每当我想要打开一个文件时,我的特定用法是有效的,但我不确定 sys.path[0] 在其他用例中是否会失败。

因此我的问题是:打开与当前运行的Python脚本位于同一目录中的文件的最佳和最可靠方式是什么?

以下是我已经能够弄清楚的内容:

  • os.getcwd()os.path.abspath('') 返回的是“当前工作目录”,而不是脚本目录。

  • os.path.dirname(sys.argv[0])os.path.dirname(__file__) 返回用于调用脚本的路径,该路径可能是相对路径,甚至可能为空(如果脚本在cwd中)。此外,当在IDLE或PythonWin中运行脚本时,__file__ 不存在。

  • sys.path[0]os.path.abspath(os.path.dirname(sys.argv[0])) 似乎返回脚本目录。我不确定这两者之间是否有任何区别。

编辑:

我刚意识到,更好地描述我想做的事情是“在包含模块的同一目录中打开文件”。换句话说,如果我导入了一个在另一个目录中的模块,并且该模块打开了一个文件,则希望它在模块的目录中查找该文件。我不认为我找到的任何内容都能够做到这一点...


2
“错误的目录”是不正确的分析。无论是双击还是不双击,脚本都在您当前的工作目录中运行,而不是您保存脚本的目录。 - tripleee
如果你认为“当前工作目录”是指你的脚本存储的位置,那就错了;这两个概念是不同的。也许可以参考什么是当前工作目录? - tripleee
9个回答

297

我总是使用:

__location__ = os.path.realpath(
    os.path.join(os.getcwd(), os.path.dirname(__file__)))
join()调用会将当前工作目录添加到路径前面,但文档说明如果某个路径是绝对路径,则其左侧的所有其他路径都将被删除。因此,当dirname(__file__)返回绝对路径时,getcwd()将被删除。

此外,realpath调用会解析符号链接(如果有),这可以避免在Linux系统上使用setuptools进行部署时出现问题(脚本被符号链接到/usr/bin/——至少在Debian上是这样)。

您可以使用以下方法在同一文件夹中打开文件:

f = open(os.path.join(__location__, 'bundled-resource.jpg'))
# ...

我在Windows和Linux上使用这个工具来打包多个Django应用程序的资源,它非常好用!


6
如果无法使用__file__,则使用sys.argv[0]代替dirname(__file__)。其余部分应按预期工作。我喜欢使用__file__,因为在库代码中,sys.argv[0]可能根本不指向您的代码,特别是如果通过某些第三方脚本导入。 - André Caron
1
这个问题在于,如果运行的文件是直接从解释器运行的还是被导入的,结果会有所不同。请参考我的答案,了解__file__和sys.argv[0]之间的区别。 - Zimm3r
那么可以这样说,Zimm3r的回答中描述的变化可以通过使用realpath(join(getcwd(), dirname(__file__)))来解决,就像这里描述的那样吗? - pianoJames
1
不需要使用 getcwd(),因为 os.path.abspath() 函数已经为您做了这个。os.path.realpath() 调用 os.path.abspath() - Martijn Pieters
1
更现代的方法是通过 open(pathlib.Path(__file__).parent / 'Some file.txt') 进行操作。 - martineau
如果我要从位置的子文件夹中打开文件,例如“Images/bundled-resource.jpg”,我应该如何调整打开文件的指令? - 123456frank

83
在 Python 3.4 版本中,增加了 pathlib 模块。以下代码可靠地打开与当前脚本位于同一目录下的文件:
from pathlib import Path

p = Path(__file__).with_name('file.txt')
with p.open('r') as f:
    print(f.read())

如果你需要文件路径作为字符串,以供某些类似open的API使用,你可以使用absolute()函数来获取:

p = Path(__file__).with_name('file.txt')
filename = p.absolute()

注意: 运行没有指定目标的python命令或者ipython等Python REPL环境,都无法获取__file__属性。


52

引用自Python文档:

在程序启动时初始化后,列表的第一项path[0]是包含脚本的目录,该脚本用于调用Python解释器。如果脚本目录不可用(例如,如果以交互方式调用解释器或从标准输入读取脚本),则path[0]为空字符串,这将指示Python首先在当前目录中搜索模块。请注意,在PYTHONPATH的结果被插入之前,脚本目录被插入。

如果您正在终端上运行脚本,则sys.path [0]就是您要找的内容。

但是,如果您有:

barpath/bar.py
    import foopath.foo

foopath/foo.py
    print sys.path[0]  # you get barpath

所以要小心!


17
获取文件的完整路径:os.path.join(sys.path[0], 'some file.txt')。这样可以在所有系统上正确处理空格和斜杠。 - Jacktose
这是对第一个问题的回答,而不是编辑后的回答。 - mcoolive
sys.argv[0]被设置为父进程告诉操作系统要设置的内容。在名为test.py的脚本中使用#!/usr/env python作为第一行,使文件可执行,然后使用alias foo test.py。或者创建一个符号链接到该文件。无论哪种方式,现在sys.argv[0]都将是错误的。或者使用其中一个os.exec*()函数来运行脚本并选择自己的第一个参数值。不要依赖于sys.argv来告诉您脚本的名称!在确定脚本目录时,请使用__file__ - Martijn Pieters

21

好的,这是我的步骤

sys.argv 始终是您在终端中键入或在使用 python.exe 或 pythonw.exe 执行时用作文件路径的内容。

例如,您可以以多种方式运行文件 text.py,它们每个都会给您不同的答案,但始终会提供输入 python 的路径。

    C:\Documents and Settings\Admin>python test.py
    sys.argv[0]: test.py
    C:\Documents and Settings\Admin>python "C:\Documents and Settings\Admin\test.py"
    sys.argv[0]: C:\Documents and Settings\Admin\test.py

好的,现在你知道如何获取文件名了,很重要,但是接下来要获取应用程序目录,你可以使用 os.path,具体地说是 abspath 和 dirname。

    import sys, os
    print os.path.dirname(os.path.abspath(sys.argv[0]))

这将会输出:

   C:\Documents and Settings\Admin\

无论您键入 python test.py 还是 python "C:\Documents and Settings\Admin\test.py",它始终会输出这个。

使用 __file__ 的问题 考虑这两个文件 test.py

import sys
import os

def paths():
        print "__file__: %s" % __file__
        print "sys.argv: %s" % sys.argv[0]

        a_f = os.path.abspath(__file__)
        a_s = os.path.abspath(sys.argv[0])

        print "abs __file__: %s" % a_f
        print "abs sys.argv: %s" % a_s

if __name__ == "__main__":
    paths()

import_test.py

import test
import sys

test.paths()

print "--------"
print __file__
print sys.argv[0]

"python test.py" 的输出结果

C:\Documents and Settings\Admin>python test.py
__file__: test.py
sys.argv: test.py
abs __file__: C:\Documents and Settings\Admin\test.py
abs sys.argv: C:\Documents and Settings\Admin\test.py

"python test_import.py"的输出

C:\Documents and Settings\Admin>python test_import.py
__file__: C:\Documents and Settings\Admin\test.pyc
sys.argv: test_import.py
abs __file__: C:\Documents and Settings\Admin\test.pyc
abs sys.argv: C:\Documents and Settings\Admin\test_import.py
--------
test_import.py
test_import.py

因此,正如您所看到的,file总是会给出运行它的Python文件,而sys.argv[0]总是会给出从解释器运行的文件。根据您的需求,您需要选择最适合您需求的选项。


4
这是一个详细的证明,表明实现反映了文档。 __file__ 应该“始终给出当前文件的路径”,而 sys.argv[0] 应该“始终给出启动进程的脚本的路径”。无论如何,在被调用的脚本中使用 __file__ 总是会给出精确的结果。 - André Caron
如果在脚本的顶层有对__file__的引用,它将按预期工作。 - Matthew Schinckel
1
sys.argv[0]被设置为父进程告诉操作系统要设置的内容。在test.py文件的第一行使用#!/usr/env python,将文件设为可执行文件,然后使用alias foo test.py或创建一个符号链接到该文件。无论哪种方式,现在sys.argv[0]都是错误的。或者使用其中一个os.exec*()函数来运行脚本并选择自己想要的第一个参数值。不要依赖sys.argv来告诉你脚本的名称!在确定脚本目录时,请使用__file__ - Martijn Pieters

3
我通常使用以下方法。它适用于测试以及可能的其他用例。 with open(os.path.join(os.path.dirname(__file__), 'some_file.txt'), 'r') as f: 这个答案在https://dev59.com/Mmkw5IYBdhLWcg3wDWPL
中被推荐。

1

你可以尝试这个简单的方法,就像这样:

    import os

    my_local_file = os.path.join(os.path.dirname(__file__), 'some_file.txt')

    f = open(my_local_file,  "r")
    my_local_data = f.read()

0
由于我在emacs中尝试使用__file__sys.argv[0]时遇到错误,因此我是这样做的:
from inspect import getfile
from pathlib import Path


script_path = getfile(lambda: None)
print(script_path)
parent_path = Path(script_path).parent
print(parent_path)

with open(parent_path/'Some file.txt', 'r') as obFile:
    print(obFile.read())

-1

尝试了所有这些解决方案后,我仍然遇到了不同的问题。所以我发现最简单的方法是创建一个名为config.py的Python文件,其中包含文件的绝对路径字典,并将其导入脚本中。 类似于:

import config as cfg 
import pandas as pd 
pd.read_csv(cfg.paths['myfilepath'])

其中config.py文件内包含:

paths = {'myfilepath': 'home/docs/...'}

虽然不是自动的,但当你需要在不同的目录或不同的机器上工作时,它是一个很好的解决方案。


问题明确要求Python脚本的目录,而不是硬编码的值,不管你如何“导入它”。 - OneCricketeer

-2
我会这样做:
from os.path import abspath, exists

f_path = abspath("fooabar.txt")

if exists(f_path):
    with open(f_path) as f:
        print f.read()

以上代码使用abspath构建了文件的绝对路径,相当于使用normpath(join(os.getcwd(), path)) [这是来自pydocs的]。然后它检查该文件是否存在,然后使用上下文管理器打开它,这样您就不必记得调用文件句柄上的close方法。在我看来,以这种方式进行操作将在长期运行中为您节省很多麻烦。

这并没有回答发帖者的问题。dln385明确表示,如果脚本不在当前目录中,则os.path.abspath无法解析到与脚本位于同一文件夹中的文件路径。 - André Caron
啊!我假设用户在运行此脚本时与他们想要读取的文件位于同一目录中,而不是在PYTHONPATH中的某个模块目录中。这会教训我不要做假设... - dcolish
abspath 不起作用,因为 Python 运行时无法使用此类函数在操作系统文件系统上搜索。 - akshat thakar

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