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


260

我想看看确定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()

警告

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


1
您能否更具体地说明需要知道文件的来源?-在导入文件的代码中(包括感知主机)或在导入文件的代码中?(自我意识的奴隶)
synthesizerpatel

3
pathlib如果您使用的是python 3.4或更高版本,请参阅Ron Kalian的解决方案:stackoverflow.com/a/48931294/1011724
Dan

因此,解决方案不是在代码中使用任何当前目录,而是使用一些配置文件?
赵刚

我刚刚做python myfile.py了一个有趣的发现:从shell进行时,它可以工作,但是在vim中:!python %:!python myfile.pyvim中都失败,系统找不到指定的路径。这很烦人。任何人都可以评论此背后的原因以及潜在的解决方法吗?
inVader

Answers:


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

确实是您将获得的最好的。

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

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


2
永远不要把话说绝了?据此: stackoverflow.com/a/18489147 回答跨平台的解决方案是abspath(getsourcefile(lambda:0))吗?还是我还缺少其他东西?
杰夫·埃伦

131

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

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

4
我认为这确实是最可靠的方法,但我质疑OP对此提出的要求。我经常看到开发人员在相对于执行模块的位置中使用数据文件时执行此操作,但是IMO数据文件应放在已知位置。
Ryan Ginstrom 2011年

14
@Ryan LOL,如果您能够定义一个多平台且模块附带的“已知位置”,那就更好了。我敢打赌,唯一安全的位置是脚本位置。请注意,这并不意味着脚本应该将脚本写入此位置,但是对于读取数据来说是安全的。
sorin 2011年

1
尽管如此,解决方案还是不好的,只是尝试chdir()在函数之前调用,它将改变结果。另外,从另一个目录调用python脚本也会改变结果,因此这不是一个好的解决方案。
sorin 2011年

2
os.path.expanduser("~")是获取用户目录的跨平台方法。不幸的是,这不是Windows最佳做法,用于粘贴应用程序数据。
瑞安·金斯特罗姆

6
@sorin:我chdir()在运行脚本之前已经尝试过;它产生正确的结果。我尝试从另一个目录调用脚本,它也可以工作。结果与inspect.getabsfile()基于解决方案的结果相同。
jfs 2014年

43
#!/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文件(/卵)中,则该脚本有效。如果脚本是PYTHONPATH=/path/to/library.zip python -mscript_to_run从zip文件“导入”()的,则该脚本有效。在这种情况下,它将返回存档路径。如果脚本被编译成独立的可执行文件(sys.frozen),它将起作用。它适用于符号链接(realpath消除了符号链接)。它在交互式解释器中工作;在这种情况下,它将返回当前的工作目录。


与PyInstaller完美配合。
2015年

1
有什么原因getabsfile(..)文档中inspect未提及?它出现在与该页面链接的源代码中。
Evgeni Sergeev 2015年

@EvgeniSergeev可能是一个错误。这是一个简单的包装器getsourcefile()getfile()已记录在案。
jfs 2015年

24

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

from inspect import currentframe, getframeinfo
from pathlib import Path

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

2
极好的简单性!
Cometsong

3
您可能可以使用Path(__file__)(不需要该inspect模块)。
Peque

@Peque这样会生成一个包含当前文件名而不是父目录名的路径。如果我试图获取当前脚本目录以指向同一目录中的文件,例如,希望将配置文件加载到与脚本相同的目录中,则Path(__file__)给出/path/to/script/currentscript.pyOP想要获取的时间/path/to/script/
Davos

8
哦,我误会了,您的意思是避免使用inspect模块,而只使用类似parent = Path(__file__).resolve().parent 这样的东西就更好了。
达沃斯

3
@DutA。您应该为此使用.joinpath()(或/运算符),而不是+
尤金·雅玛什

13

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

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

from pathlib import Path
cwd = Path(__file__).parents[0]

11
或者只是Path(__file__).parent。但这cwd是用词不当,不是当前的工作目录,而是文件的目录。它们可能是相同的,但通常并非如此。
NunoAndré19年

5

只需使用os.path.dirname(os.path.abspath(__file__))并非常仔细地检查使用情况下是否真的需要exec。如果您不能将脚本用作模块,则可能是设计麻烦的迹象。

请记住,Python#8的Zen,如果您认为有一个必须在其中使用的用例exec,那么请告诉我们有关问题背景的更多详细信息。


2
如果不使用exec()运行,则将丢失调试器上下文。而且exec()的速度比开始新进程的速度要快得多。
sorin 2011年

@sorin这不是exec与开始新流程的问题,所以这是一个稻草人的论点。这是exec与使用import或function调用的问题。
WIM

4

import os
cwd = os.getcwd()

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


3
这没有帮助。我相信@bogdan正在寻找位于调用堆栈顶部的脚本目录。即,在所有情况下,都应打印出“ myfile.py”所在的目录。但是,您的方法只会打印exec('myfile.py')__file__和相同的被调用文件的目录sys.argv[0]
18年

是的,那很有道理。我只是想确保@bogdan不会忽略一些简单的事情,并且我无法确切地说出他们想要什么。
Will McCutchen 2010年

3

首先..如果我们正在讨论注入匿名代码的方式,那么这里有几个用例。

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子类添加或删除功能。

但是,如果这更多是学术上的追求,那么您可以通过以下两种愚蠢的方法来进行深入研究。

示例脚本:

./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,而不覆盖“ import”。对于更高级别的“模块”负载挂钩,您可能可以使用 sys.path_hooks(由PyMOTW致谢)。

多数民众赞成在我的头上。


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发布了完整的测试,欢迎您进行改进。

1

这在大多数情况下应该有效:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))

5
该解决方案使用当前目录,并且在问题中明确指出该解决方案将失败。
2013年

1

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

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

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

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

我尚未对其进行全面测试,但是它解决了我的问题。


这将提供文件,而不是目录
Shital Shah,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.