如何正确地确定当前脚本目录?

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

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

以下是一些问题:

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

用例:

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

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

最常用的方法是os.path.dirname(os.path.abspath(__file__)),但如果你用exec()从另一个脚本执行脚本,这就行不通了。

警告

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

166769 次浏览

import os
cwd = os.getcwd()

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

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

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

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

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

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

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))

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

记住Python的Zen #8,如果你认为有一个好的用例,它必须为exec工作,那么请让我们知道有关问题背景的更多细节。

第一. .如果我们在讨论注入匿名代码的方法,这里会遗漏一些用例。

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

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

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

这个模块包含RExec类, 支持r_eval(), r_execfile(), R_exec()和r_import()方法 是标准的限制版本吗 Python函数eval(), execfile()和 exec和import语句。代码 在此受限环境中执行 只能访问模块和 被认为安全的功能;你可以 子类RExec添加或删除功能为 满意。< / p >

然而,如果这更多的是一种学术追求。这里有一些愚蠢的方法 也许能更深入地挖掘…< / p >

示例脚本:

。/ 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
当然,这是一种资源密集型的方式,你会跟踪 你所有的代码..不是很有效率。但是,我认为这是一种新颖的方法 因为即使你在巢穴里越陷越深,它也会继续工作。 你不能重写'eval'。虽然你可以覆盖execfile().

注意,这个方法只适用于exec/execfile,不适用于import。 对于更高级别的“模块”加载挂钩,你可能会使用use sys.path_hooks(由PyMOTW提供).

这就是我所能想到的。

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

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]将不起作用,如果你使用python -c "execfile('path-tester.py')"执行脚本,将返回-c
  • 我在https://gist.github.com/1385555发布了一个完整的测试,欢迎您改进它。
#!/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文件(/一个鸡蛋)中,则有效。如果脚本是从zip文件中“导入”(PYTHONPATH=/path/to/library.zip python -mscript_to_run),则有效;在本例中,它返回存档路径。如果脚本被编译成一个独立的可执行文件(sys.frozen),它就可以工作。它适用于符号链接(realpath消除了符号链接)。它在交互式解释器中工作;在本例中,它返回当前工作目录。

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

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
希望这有助于:- 如果你在任何地方运行一个脚本/模块,你将能够访问__file__变量,这是一个表示脚本位置的模块变量。< / p >

另一方面,如果你正在使用解释器,你无法访问该变量,在那里你将得到一个名称NameErroros.getcwd()将给你不正确的目录,如果你从其他地方运行文件。

解决方案应该给你什么你正在寻找在所有情况下:

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

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

在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

os.path...方法是Python 2中的“done thing”。

在Python 3中,你可以找到script的目录,如下所示:

from pathlib import Path
script_path = Path(__file__).parent

注意:这个答案现在是一个包(也具有安全的相对导入功能)

https://github.com/heetbeet/locate

$ pip install locate


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

对于.py脚本和交互使用:

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

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

Python 3.4(及以上版本):

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

Python 2(及以上版本):

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

解释:

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

选择:

如果你确定__file__可用于周围的代码,你可以简化为:

  • Python 3.4: this_dir = Path(__file__).absolute().parent
  • 2 . Python: this_dir = os.path.dirname(os.path.abspath(__file__))

如果__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
print(__import__("pathlib").Path(__file__).parent)

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

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

请注意,.resolve()调用是必需的,因为它使路径成为绝对路径。如果没有resolve(),你将得到类似'.'的东西。

这个解决方案使用pathlib,它是自v3.4(2014)以来Python的stdlib的一部分。与使用os的其他解决方案相比,这是更好的。

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

因为以前的答案需要你导入一些模块,我想我会写一个答案,不。如果您不想导入任何东西,请使用下面的代码。

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

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