如何在Python中获取当前执行文件的路径?

233

在Python中,是否有一种通用的方法,可以找到当前执行的文件的路径?

失败的尝试

path = os.path.abspath(os.path.dirname(sys.argv[0]))

如果您正在另一个目录中从另一个Python脚本中运行代码,例如使用2.x中的execfile,则此方法无效。

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

我发现在以下情况下这种方法不起作用:

  • py2exe没有__file__属性,但是有解决方法
  • 当使用execute()IDLE上运行代码时,此时也没有__file__属性。
  • Mac OS X v10.6(Snow Leopard)上,我遇到了NameError: global name '__file__' is not defined

测试用例

目录结构

C:.
|   a.py
\---subdir
        b.py

a.py的内容

#! /usr/bin/env python
import os, sys

print "a.py: sys.argv[0]=", sys.argv[0]
print "a.py: __file__=", __file__
print "a.py: os.getcwd()=", os.getcwd()
print

execfile("subdir/b.py")

subdir/b.py的内容

#! /usr/bin/env python
import os, sys

print "b.py: sys.argv[0]=", sys.argv[0]
print "b.py: __file__=", __file__
print "b.py: os.getcwd()=", os.getcwd()
print

在Windows上运行python a.py的输出结果

a.py: __file__= a.py
a.py: os.getcwd()= C:\zzz

b.py: sys.argv[0]= a.py
b.py: __file__= a.py
b.py: os.getcwd()= C:\zzz

相关问题(但这些答案都不完整)


execfile已经不存在了,而且我不清楚为什么测试用例中的代码应该返回任何不同的内容,或者通过让b.py能够识别其自身源代码位置来解决什么问题。就此而言,代码可以从非文件来源运行。 - Karl Knechtel
13个回答

100

首先,您需要从inspectos导入

from inspect import getsourcefile
from os.path import abspath

接下来,无论您想从哪里查找源文件,只需使用

abspath(getsourcefile(lambda:0))

1
最佳答案。谢谢。 - Devan Williams
4
好的回答,谢谢。它似乎也是最简短、最便携(可在不同的操作系统上运行)的答案,而且不会遇到诸如“NameError: global name 'file' is not defined”(另一种解决方案会引起此问题)的问题。 - Edward
2
就像我尝试过的几乎所有建议一样,这只是为我返回了 cwd,而不是我正在运行(调试)的目录文件。 - James
2
@James - 这段代码应该放在你正在运行的文件中... 如果你尝试在交互式提示符下运行getsourcefile(lambda:0),它将毫无意义地返回None(因为lambda不会在任何源文件中)。如果你想知道另一个函数或对象在交互环境中来自哪里,也许使用abspath(getsourcefile(thatFunctionOrObject))会更有帮助? - ArtOfWarfare
2
@Brōtsyorfuzthrāx - 这是因为getsourcefile需要一个函数,所以它创建了一个函数。然后,getsourcefile返回包含给定函数的文件,这将是我们键入该行的任何文件。如果它被运行,该函数只会返回0,但它永远不会被运行。 - ArtOfWarfare
显示剩余3条评论

89

您无法直接确定正在执行的主要脚本的位置。毕竟,有时该脚本根本不是来自文件。例如,它可能来自交互式解释器或仅存储在内存中的动态生成的代码。

但是,您可以可靠地确定模块的位置,因为模块始终从文件中加载。如果您使用以下代码创建一个模块,并将其放在与主要脚本相同的目录中,则主要脚本可以导入该模块并使用它来定位自身。

some_path/module_locator.py:

def we_are_frozen():
    # All of the modules are built-in to the interpreter, e.g., by py2exe
    return hasattr(sys, "frozen")

def module_path():
    encoding = sys.getfilesystemencoding()
    if we_are_frozen():
        return os.path.dirname(unicode(sys.executable, encoding))
    return os.path.dirname(unicode(__file__, encoding))

某个路径/main.py:

import module_locator
my_path = module_locator.module_path()
如果您有几个位于不同目录中的主脚本,则可能需要多个module_locator的副本。
当然,如果您的主脚本由某些其他工具加载,而这些工具不允许您导入与您的脚本共同定位的模块,则您会很遗憾。 在这种情况下,您所需的信息在程序中根本不存在。 您最好的选择是向工具的作者提交错误报告。

2
我提到在OS 10.6上使用__file__时会出现“NameError: global name 'file' is not defined”的错误,而且这不是在IDLE中发生的。我认为__file__只在模块内定义。 - sorin
2
谢谢,但实际上缺少__file__的问题与Unicode无关。我不知道为什么__file__没有定义,但我正在寻找一种通用解决方案,以便在所有情况下都能工作。 - sorin
2
很抱歉,在某些情况下这是不可能的。例如,我尝试在waf.googlecode.com中从wscript文件(Python)内部执行此操作。这些文件会被执行,但它们不是模块,也不能将它们制作成模块(它们可以位于源树的任何子目录中)。 - sorin
1
这样做不会返回 some_path/module_locator.py 的位置吗? - Casebash
1
这在Python 3上无法工作。unicode已更改为str。我尝试添加if sys.version_info[0] >= 3:unicode = str,就像https://dev59.com/DWIj5IYBdhLWcg3wy4DH#53286631中所示的那样,但仍然出现错误。我该怎么做才能使其正常工作? - Kvothe
显示剩余9条评论

31

这个解决方案即使在可执行文件中也很强大:

import inspect, os.path

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

4
这应该是正确答案。这适用于entry_point: console_script,但不适用于其他答案。 - Polv
只需获取cwd() - eric
@eric:重点是什么? - Peter Mortensen
@PeterMortensen 执行的文件通常不在cwd()中。 - eric
1
这在解释器中不起作用。我正在PyCharm解释器控制台中运行它,它给了我:文件名:<ipython-input-107-fff10ab01b8b>路径:C:\ Users \ PowerUser \ Desktop。 - Rich Lysakowski PhD
inspect.getsourcefile(inspect.currentframe())或者inspect.getfile(inspect.currentframe())不也可以吗? - aschipfl

16

我遇到了类似的问题,我认为这可能会解决问题:

def module_path(local_function):
   ''' returns the module path without the use of __file__.  Requires a function defined
   locally in the module.
   from https://dev59.com/hHRB5IYBdhLWcg3wET1J
   return os.path.abspath(inspect.getsourcefile(local_function))

它适用于常规脚本和IDLE中。我的建议是尝试在其他情况下使用它!

我通常的用法:

from toolbox import module_path
def main():
   pass # Do stuff

global __modpath__
__modpath__ = module_path(main)

现在我使用 _modpath_ 而不是 _file_


4
根据PEP8编码规范,不应创建双前导和尾随下划线的名称--因此应将__modpath__重命名。您也可能不需要global语句。否则+1! - martineau
6
实际上,您可以直接在调用module_path()时定义本地函数。例如:module_path(lambda _: None),它不依赖于所在脚本的其他内容。 - martineau
@martineau:我采纳了您的建议,使用lambda _: None将近两年时间,但现在我刚刚发现可以将其简化为lambda:0。您为什么建议使用忽略参数_的形式,而不是根本没有参数?None前缀加空格和0相比,有什么特别的优势吗?我认为它们都同样晦涩难懂,只是一个长度为8个字符,另一个长度为14个字符。 - ArtOfWarfare
@ArtOfWarfare:两者之间的区别在于lambda _:是一个带有一个参数的函数,而lambda:则不带任何参数。这并不重要,因为该函数从未被调用。同样,使用什么返回值也无关紧要。我猜我选择了None,因为当时它似乎更好地表示了一个无事可做、永远不会被调用的函数。前面的空格是可选的,只是为了提高可读性(始终遵循PEP8规范是一种习惯)。 - martineau
@martineau:显然这是对lambda的滥用,将其用于它从未被设计用于的事情。如果你遵循PEP8,我认为正确的内容应该是pass而不是None,但在lambda中放置语句是无效的,因此必须放入一个具有值的东西。有一些有效的两个字符的东西可以放进去,但我认为唯一有效的单个字符的东西是0-9(或在lambda之外分配的单个字符变量名)。我认为0最能表示0-9的空虚。 - ArtOfWarfare
显示剩余2条评论

12

你只是简单地打了个电话:

path = os.path.abspath(os.path.dirname(sys.argv[0]))

改为:

path = os.path.dirname(os.path.abspath(sys.argv[0]))

abspath() 返回 sys.argv [0] 的绝对路径(您的代码所在的文件名),dirname() 返回不带文件名的目录路径。


谢谢!只有你的答案才能将路径作为全局变量在一个单独的模块中进行管理。现在我可以随时从任何地方调用path_util.cwd_path来获取调用py文件的目录。 - CodingNinja

7

简而言之,没有确切的方法可以保证获得所需信息,但在实践中有几种启发式方法可以使用。您可以查看如何在 C 中查找可执行文件的位置?。它从 C 的角度讨论了这个问题,但提出的解决方案很容易转换为 Python。


你好,我的标记被拒绝了,所以我现在在meta上开始了一次讨论:http://meta.stackoverflow.com/questions/277272/not-an-answer-flag-declined?noredirect=1#comment119282_277272 - ArtOfWarfare
这个页面上已经有简单的可行答案了。不过我的解决方案也很好用:https://dev59.com/enE85IYBdhLWcg3wvGOm#33531619。 - Edward

6
请参考我对问题“从父文件夹导入模块”(Importing modules from parent folder)的答案,了解相关信息,包括为什么我的答案不使用不可靠的__file__变量。这个简单的解决方案应该与不同操作系统兼容,因为osinspect模块是Python的一部分。
首先,您需要导入inspectos模块的部分内容。
from inspect import getsourcefile
from os.path import abspath

接下来,在你的Python代码中任何需要的地方使用以下行:
abspath(getsourcefile(lambda:0))

工作原理:

从内置模块os(下面有描述)中导入了abspath工具。

根据我们所在的系统,提供Mac、NT或Posix的OS例程。

然后从内置模块inspect中导入了getsourcefile(下面有描述)。

从实时Python对象中获取有用的信息。

  • abspath(path)返回文件路径的绝对/完整版本。
  • getsourcefile(lambda:0)以某种方式获取lambda函数对象的内部源文件,因此在Python shell中返回'<pyshell#nn>'或返回当前正在执行的Python代码的文件路径。

getsourcefile(lambda:0)的结果上使用abspath应该确保生成的文件路径是Python文件的完整文件路径。
这个解释性的解决方案最初基于How do I get the path of the current executed file in Python?答案中的代码。


是的,我刚想到了同样的解决方案...比那些只说不能可靠地完成的答案要好得多...除非问题并不是我所认为的那样... - Grady Player
这段代码在Python命令行中可以运行,但在iPython解释器中无法运行。它会显示:当前路径:C:\ Users \ PowerUser \ Desktop <ipython-input-110-db9b4c6a7aa0>) - Rich Lysakowski PhD

5
这应该是一种跨平台的解决方法(只要您不使用解释器或类似工具):
import os, sys
non_symbolic=os.path.realpath(sys.argv[0])
program_filepath=os.path.join(sys.path[0], os.path.basename(non_symbolic))

sys.path[0]是你调用脚本所在的目录(它查找模块以供该脚本使用的第一个地方)。我们可以从sys.argv [0]的末尾删除文件本身的名称(这就是我使用os.path.basename的原因)。os.path.join会以跨平台方式将它们粘在一起。os.path.realpath只是确保如果我们得到任何与脚本本身不同命名的符号链接,我们仍然能够获取脚本的真实名称。

我没有Mac电脑,因此没有在上面测试过。如果它有效,请告诉我,因为它应该有效。我在Linux中(Xubuntu)中使用Python 3.4测试过。请注意,许多解决此问题的方法在Mac上不起作用(因为我听说__file__在Mac上不存在)。

请注意,如果您的脚本是符号链接,则它将向您提供它链接到的文件的路径(而不是符号链接的路径)。


4
你可以使用 pathlib 模块中的 Path
from pathlib import Path

# ...

Path(__file__)

您可以使用parent来进一步深入路径:
Path(__file__).parent

3
这个答案使用了__file__变量,但它并不可靠(并非总是完整的文件路径,在某些操作系统上无法工作等),正如StackOverflow用户经常提到的那样。将答案更改为不包含该变量将会导致更少的问题,并且更具跨平台兼容性。有关更多信息,请参见https://dev59.com/QXRB5IYBdhLWcg3wH0SW#33532002。 - Edward
@mrroot5 好的,请删除您的评论。 - Gavriel Cohen

1

Simply add the following:

from sys import *
path_to_current_file = sys.argv[0]
print(path_to_current_file)

或者:

from sys import *
print(sys.argv[0])

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