344

我想看看在 Python 中确定当前脚本目录的最佳方法是什么。

我发现,由于调用Python代码的方式很多,很难找到一个好的解决方案。

这里有一些问题:

  • __file__如果脚本使用 执行,则未定义execexecfile
  • __module__仅在模块中定义

用例:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py')(来自另一个脚本,它可以位于另一个目录中并且可以有另一个当前目录。

我知道没有完美的解决方案,但我正在寻找解决大多数情况的最佳方法。

最常用的方法是os.path.dirname(os.path.abspath(__file__)),但如果您从另一个使用exec().

警告

任何使用当前目录的解决方案都会失败,这可能会根据脚本的调用方式而有所不同,也可以在正在运行的脚本中进行更改。

4

16 回答 16

291
os.path.dirname(os.path.abspath(__file__))

确实是你会得到的最好的。

exec使用/执行脚本是不寻常的execfile;通常你应该使用模块基础设施来加载脚本。如果您必须使用这些方法,我建议__file__globals您传递给脚本中进行设置,以便它可以读取该文件名。

没有其他方法可以在执行代码中获取文件名:正如您所注意到的,CWD 可能位于完全不同的位置。

于 2010-09-15T15:01:21.187 回答
147

如果您真的想涵盖通过调用脚本的情况execfile(...),您可以使用该inspect模块来推断文件名(包括路径)。据我所知,这适用于您列出的所有情况:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
于 2011-06-02T02:49:46.077 回答
50
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

它适用于 CPython、Jython、Pypy。execfile()如果使用(sys.argv[0]和 -__file__基于解决方案的解决方案将在此处失败)执行脚本,它会起作用。如果脚本在可执行的 zip 文件 (/an egg)中,它就可以工作。如果脚本是PYTHONPATH=/path/to/library.zip python -mscript_to_run从 zip 文件中“导入”(),则它可以工作;在这种情况下,它返回存档路径。如果脚本被编译成独立的可执行文件 ( sys.frozen),它就可以工作。它适用于符号链接(realpath消除符号链接)。它在交互式解释器中工作;在这种情况下,它返回当前工作目录。

于 2014-04-05T14:01:08.260 回答
45

在 Python 3.4+ 中,您可以使用更简单的pathlib模块:

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent

您还可以使用__file__(当它可用时)完全避免使用该inspect模块:

from pathlib import Path
parent = Path(__file__).resolve().parent
于 2017-05-25T19:33:44.753 回答
29

os.path...方法是 Python 2 中的“完成的事情”。

在 Python 3 中,您可以找到脚本的目录,如下所示:

from pathlib import Path
script_path = Path(__file__).parent
于 2018-02-22T15:34:37.723 回答
6

import os
cwd = os.getcwd()

做你想做的事?我不确定“当前脚本目录”到底是什么意思。您给出的用例的预期输出是什么?

于 2010-09-15T14:59:29.167 回答
5

只需使用os.path.dirname(os.path.abspath(__file__))并非常仔细地检查是否真的需要使用的情况exec。如果您无法将脚本用作模块,这可能是设计问题的标志。

请记住Python #8 的 Zen,如果您认为对于必须适用的用例有一个很好的论据exec,那么请让我们了解有关问题背景的更多详细信息。

于 2011-06-04T10:53:03.680 回答
4

注意:这个答案现在是一个包

https://github.com/heetbeet/locate

$ pip install locate

$ python
>>> from locate import this_dir
>>> print(this_dir())
C:/Users/simon

对于.py脚本以及交互式使用:

我经常使用我的脚本目录(用于访问与它们一起存储的文件),但我也经常在交互式 shell 中运行这些脚本以进行调试。我定义__dirpath__为:

  • 运行或导入.py文件时,文件的基目录。这始终是正确的道路。
  • 运行.ipyn笔记本时,当前工作目录。这始终是正确的路径,因为 Jupyter 将工作目录设置为.ipynb基本目录。
  • 在 REPL 中运行时,当前工作目录。嗯,当代码从文件中分离出来时,实际的“正确路径”是什么?相反,让您有责任在调用 REPL 之前更改为“正确的路径”。

Python 3.4(及更高版本):

from pathlib import Path
__dirpath__ = Path(globals().get("__file__", "./_")).absolute().parent

Python 2(及更高版本):

import os
__dirpath__ = os.path.dirname(os.path.abspath(globals().get("__file__", "./_")))

解释:

  • globals()将所有全局变量作为字典返回。
  • .get("__file__", "./_")"__file__"如果它存在于 中,则返回该键的值globals(),否则返回提供的默认值"./_"
  • 其余的代码只是展开__file__(或"./_")成一个绝对文件路径,然后返回文件路径的基目录。
于 2020-10-25T10:43:24.000 回答
3

首先.. 如果我们正在讨论注入匿名代码的方法,这里有几个缺少的用例..

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

但是,真正的问题是,你的目标是什么——你是否试图强制执行某种安全措施?或者您只是对正在加载的内容感兴趣。

如果您对security感兴趣,通过 exec/execfile 导入的文件名是无关紧要的 - 您应该使用rexec,它提供以下内容:

该模块包含 RExec 类,它支持 r_eval()、r_execfile()、r_exec() 和 r_import() 方法,它们是标准 Python 函数 eval()、execfile() 以及 exec 和 import 语句的受限版本。在这个受限环境中执行的代码只能访问被认为是安全的模块和功能;您可以根据需要对 RExec 添加或删除功能进行子类化。

但是,如果这更像是一种学术追求..这里有一些愚蠢的方法,您可能可以更深入地挖掘..

示例脚本:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

输出

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

当然,这是一种资源密集型的方法,您将跟踪所有代码.. 效率不高。但是,我认为这是一种新颖的方法,因为即使您深入巢穴,它也会继续工作。您不能覆盖“评估”。尽管您可以覆盖 execfile()。

请注意,这种方法仅涵盖 exec/execfile,而不是“导入”。对于更高级别的“模块”负载挂钩,您可以使用 sys.path_hooks(由 PyMOTW 提供)。

这就是我想不到的。

于 2011-06-04T20:17:21.100 回答
2

这是一个部分解决方案,仍然比迄今为止所有已发布的解决方案都要好。

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

现在这对所有调用都有效,但如果有人chdir()用来更改当前目录,这也会失败。

笔记:

  • sys.argv[0]不起作用,-c如果您执行脚本将返回python -c "execfile('path-tester.py')"
  • 我在https://gist.github.com/1385555上发布了一个完整的测试,欢迎您改进它。
于 2011-11-22T12:26:08.367 回答
1

这应该在大多数情况下工作:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
于 2015-06-14T16:52:11.133 回答
1

希望这会有所帮助:- 如果您从任何地方运行脚本/模块,您将能够访问__file__变量,该变量是表示脚本位置的模块变量。

另一方面,如果您使用的是解释器,则您无权访问该变量,如果您从其他地方运行文件,您将获得一个名称NameError,并且os.getcwd()会给您错误的目录。

在所有情况下,此解决方案都应为您提供所需的内容:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

我还没有彻底测试它,但它解决了我的问题。

于 2017-03-24T13:55:30.367 回答
1

如果__file__可用:

# -- script1.py --
import os
file_path = os.path.abspath(__file__)
print(os.path.dirname(file_path))

对于那些我们希望能够从解释器运行命令或获取运行脚本的位置的路径的人:

# -- script2.py --
import os
print(os.path.abspath(''))

这适用于口译员。但是当在脚本中运行(或导入)时,它会给出运行脚本的位置的路径,而不是包含脚本的目录路径。

例子:

如果你的目录结构是

test_dir (in the home dir)
├── main.py
└── test_subdir
    ├── script1.py
    └── script2.py

# -- main.py --
import script1.py
import script2.py

输出是:

~/test_dir/test_subdir
~/test_dir
于 2020-12-04T11:08:22.670 回答
1

要获取包含当前脚本的目录的绝对路径,您可以使用:

from pathlib import Path
absDir = Path(__file__).parent.resolve()

请注意.resolve()调用是必需的,因为这是使路径成为绝对路径的调用。没有resolve(),您将获得类似'.'.

此解决方案使用pathlib自 v3.4 (2014) 以来的 Python 标准库的一部分。与其他使用os.

官方pathlib文档有一个有用的表格,将旧os功能映射到新功能:https ://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module

于 2021-05-11T08:33:54.033 回答
0

由于以前的答案要求您导入一些模块,我想我会写一个不需要的答案。如果您不想导入任何内容,请使用下面的代码。

dir = '/'.join(__file__.split('/')[:-1])
print(dir)

如果脚本打开,/path/to/script.py那么这将打印/path/to. 请注意,这将在终端上引发错误,因为没有执行任何文件。__file__这基本上从删除目录的最后一部分来解析目录。在这种情况下/script.py被删除以产生输出/path/to

于 2022-01-03T17:08:15.773 回答
-2
print(__import__("pathlib").Path(__file__).parent)
于 2021-03-15T09:06:08.777 回答