关于python:如何正确确定当前脚本目录?

How to properly determine current script directory?

我想看看在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()从另一个脚本执行脚本,那么这种方法实际上不起作用。

警告

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


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

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

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

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


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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/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文件(/egg)中,它就可以工作。如果脚本是从zip文件"导入"(PYTHONPATH=/path/to/library.zip python -mscript_to_run)的话,它就可以工作;在这种情况下,它返回归档路径。如果脚本被编译成独立的可执行文件(sys.frozen,它就会工作。它适用于符号链接(realpath消除了符号链接)。它在交互式解释器中工作;在这种情况下,它返回当前的工作目录。


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

1
2
3
4
5
from inspect import currentframe, getframeinfo
from pathlib import Path

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


只需使用os.path.dirname(os.path.abspath(__file__))并仔细检查是否确实需要使用exec的情况。如果您不能将脚本用作模块,那么这可能是设计问题的迹象。

请记住python 8的zen,如果您认为对于一个必须适用于exec的用例有一个很好的理由,那么请让我们了解关于问题背景的更多细节。


在python 2中,os.path...方法是"完成的事情"。

在python 3中,可以找到如下脚本目录:

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


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

1
2
3
4
5
6
7
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,它提供以下功能:

This module contains the RExec class,
which supports r_eval(), r_execfile(),
r_exec(), and r_import() methods, which
are restricted versions of the standard
Python functions eval(), execfile() and
the exec and import statements. Code
executed in this restricted environment
will only have access to modules and
functions that are deemed safe; you can
subclass RExec add or remove capabilities as
desired.

然而,如果这更像是一种学术追求……以下是一些愚蠢的方法也许能更深入地挖掘……

示例脚本:

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

/深度

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

/tMP/DePiSt.Py

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

/CODESPY.PY

1
2
3
4
5
6
7
8
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)

产量

1
2
3
4
5
6
7
8
9
10
11
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"。对于更高级别的"模块"负载挂钩,您可能可以使用系统路径挂钩(由Pymotw提供)。

这就是我头上的所有东西。


1
2
3
4
5
6
7
8
9
import os
import sys

def get_script_path():
    return os.path.dirname(os.path.realpath(sys.argv[0]))


my_script_dir = get_script_path()
print my_script_dir

这为您提供了位于堆栈顶部的脚本目录(即正在执行的脚本,而不是通常第一次执行的python的,返回C:/)。


这在大多数情况下都会起作用:

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


这里有一个部分的解决方案,仍然比目前所有已发布的解决方案都好。

1
2
3
4
5
6
7
8
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()来更改当前目录,这也会失败。

笔记:

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

希望这有助于:如果从任何地方运行脚本/模块,则可以访问__file__变量,该变量是表示脚本位置的模块变量。

另一方面,如果您使用的是解释器,那么您没有访问该变量的权限,在该变量中,您将得到一个名称NameError,如果您从其他地方运行文件,os.getcwd()将给您提供不正确的目录。

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

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

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


1
2
import os
cwd = os.getcwd()

做你想做的?我不知道"当前脚本目录"究竟是什么意思。对于您提供的用例,预期的输出是什么?


就1号[6号]在Jupyter笔记本电脑上:pwd+shift+enter在Spyder:pwd+f9