如何获取模块的路径?

1093

我希望检测模块是否发生了变化。现在,使用inotify很简单,你只需要知道想要从中获取通知的目录。

在Python中,如何获取模块的路径?


1
请查看modulefinder:http://docs.python.org/library/modulefinder.html - user1256374
19
如果你仍在这个网站上寻找答案,请更新正确的答案至此处。该答案比提出的解决方案更加简洁,而且它也适用于__file__未设置的情况。 - erikbstack
3
不仅如此,基于inspect的解决方案还适用于execfile()情况下__file__静默产生错误名称的情况。 - jfs
2
如何在Python中正确确定当前脚本目录? - jfs
4
import pathlib, module; pathlib.Path(module.__file__).resolve().parent 这行代码是跨平台的,作用是导入pathlib和module模块,获取module模块所在文件的绝对路径,并返回其父目录的路径。 - MortenB
显示剩余3条评论
26个回答

1280
import a_module
print(a_module.__file__)

实际上会给你加载的 .pyc 文件路径,至少在 Mac OS X 上是这样。所以我猜你可以这样做:

import os
path = os.path.abspath(a_module.__file__)

你也可以尝试:

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

获取模块的目录。


69
这里回答了如何获取导入的模块路径,但不包括你所在的模块/脚本路径(对于正在运行的脚本,__file__ 不是一个完整的路径,而是相对路径)。对于我所在的文件,我不得不从同一目录中导入另一个模块并执行如此所示的操作。有没有更方便的方法? - Ben Bryant
9
@hbdgaf,我很确定没有内置的self.__file__ - Dan Passaro
36
os.path.dirname(__file__) 可以正常工作,返回模块所在目录的绝对路径。 - Niccolò
21
我尝试做这个,但是出现了回溯:AttributeError: 'module' object has no attribute '__file__' - Dorian Dore
7
@DorianDore 我正在尝试使用模块,找到了解决方案 path = module.__path__.__dict__["_path"][0],但我不确定它是否具有可移植性或是否会因 Python 版本的不同而产生差异。这个方法对我有效,不像这个答案给出了相同的错误,并且 inspect 的答案会引发一个 TypeError:<module 'module'(namespace)>是一个内置模块 的错误…… - Jezor
显示剩余6条评论

379

Python自带有一个inspect模块。

官方文档

该模块提供了几个有用的函数,以帮助获取关于活动对象(如模块、类、方法、函数、回溯、帧对象和代码对象)的信息。例如,它可以帮助您检查类的内容,检索方法的源代码,提取和格式化函数的参数列表,或获取显示详细回溯所需的所有信息。

示例:

>>> import os
>>> import inspect
>>> inspect.getfile(os)
'/usr/lib64/python2.7/os.pyc'
>>> inspect.getfile(inspect)
'/usr/lib64/python2.7/inspect.pyc'
>>> os.path.dirname(inspect.getfile(inspect))
'/usr/lib64/python2.7'

14
你可以使用 inspect 获取当前文件的名称;请参考 https://dev59.com/T3VD5IYBdhLWcg3wNY1Z#50905。 - z0r
9
我已经多次在谷歌上搜索这个问题,而这个答案是我见过的最合理的!请更新关于inspect.currentframe()的信息。 - erikbstack
inspect.getfile()方法无法使用在模块_io上,但可以用于模块io - smwikipedia
1
请注意,inspect.getfile 对于内置模块可能会抛出 TypeError - Alex Reinking

81

正如其他答案所说,最好的方法是使用__file__(如下所示)。但是,有一个重要的警告,即如果您单独运行模块(即作为__main__),则__file__不存在。

例如,假设您有两个文件(两个文件都在PYTHONPATH上):

#/path1/foo.py
import bar
print(bar.__file__)

并且

#/path2/bar.py
import os
print(os.getcwd())
print(__file__)

运行foo.py将会输出:

/path1        # "import bar" causes the line "print(os.getcwd())" to run
/path2/bar.py # then "print(__file__)" runs
/path2/bar.py # then the import statement finishes and "print(bar.__file__)" runs

然而,如果您尝试单独运行bar.py,则会收到以下错误:

/path2                              # "print(os.getcwd())" still works fine
Traceback (most recent call last):  # but __file__ doesn't exist if bar.py is running as main
  File "/path2/bar.py", line 3, in <module>
    print(__file__)
NameError: name '__file__' is not defined 

希望这能有所帮助。在测试其他解决方案时,这个注意事项花费了我很多时间和困惑。

7
在这种情况下,您可以使用sys.argv[0]代替__file__。 - Jimothy
4
这是一个特定于版本的警告吗?在2.6和2.7中,我成功地依赖于__file__,当__name__=='main'时它可以正常工作。唯一失败的情况是"python -c 'print file'"。有时__file__可能是'<stdin>',例如当像Emacs这样的IDE执行当前缓冲区时会出现这种情况。 - Paul Du Bois
1
请注意,前导和尾随的“__”字符会将单词加粗,因此在阅读先前的注释时请记住这一点 :-P - Paul Du Bois
1
@PaulDuBois 你可以用反引号包围它:`__file__` 变成 __file__ - fncomp
2
你如何获得 NameError 错误?@Paul Du Bois:我尝试过 Python 2.3-3.4 版本,无论是通过 python a.pypython -ma 还是 ./a.py 运行 Python 文件,__file__ 都已定义。 - jfs
显示剩余2条评论

75

我还将尝试解决一些与此问题有关的变化:

  1. 查找被调用脚本的路径
  2. 查找当前正在执行脚本的路径
  3. 查找被调用脚本所在的目录

(这些问题中的一些已经在SO上提出,但已被关闭并重定向到这里。)

使用__file__的注意事项

对于您导入的模块:

import something
something.__file__ 

将返回模块的绝对路径。然而,考虑到以下脚本foo.py:

#foo.py
print '__file__', __file__

当你使用“python foo.py”调用它时,将仅返回“foo.py”。如果你添加一个shebang:

#!/usr/bin/python 
#foo.py
print '__file__', __file__

如果使用 "./foo.py" 调用它,它将返回 "./foo.py"。如果从不同的目录 (例如将 foo.py 放在目录 bar 中),然后调用以下任一命令:

python bar/foo.py

或者添加一个 shebang 并直接执行该文件:

bar/foo.py

将返回'bar/foo.py'(相对路径)。

查找目录

现在从这里获取目录,os.path.dirname(__file__) 也可能会有些棘手。至少在我的系统上,如果您从与文件相同的目录调用它,则会返回一个空字符串。例如:

# foo.py
import os
print '__file__ is:', __file__
print 'os.path.dirname(__file__) is:', os.path.dirname(__file__)

将输出:

__file__ is: foo.py
os.path.dirname(__file__) is: 

换句话说,它返回一个空字符串,因此如果您要将其用于当前文件(而不是导入模块的文件),则似乎不可靠。 为了避免这种情况,您可以将其包装在对abspath的调用中:

# foo.py
import os
print 'os.path.abspath(__file__) is:', os.path.abspath(__file__)
print 'os.path.dirname(os.path.abspath(__file__)) is:', os.path.dirname(os.path.abspath(__file__))

它会输出类似于以下内容:

os.path.abspath(__file__) is: /home/user/bar/foo.py
os.path.dirname(os.path.abspath(__file__)) is: /home/user/bar

请注意,abspath()函数不会解析符号链接。如果您想要解析符号链接,请使用realpath()函数。例如,创建一个符号链接文件file_import_testing_link,指向file_import_testing.py文件,并包含以下内容:

import os
print 'abspath(__file__)',os.path.abspath(__file__)
print 'realpath(__file__)',os.path.realpath(__file__)

执行将打印类似于绝对路径的内容:

abspath(__file__) /home/user/file_test_link
realpath(__file__) /home/user/file_test.py

file_import_testing_link -> file_import_testing.py

使用 inspect 模块

@SummerBreeze 提到了使用 inspect 模块。

这个方法对已导入的模块非常有效和简洁:

import os
import inspect
print 'inspect.getfile(os) is:', inspect.getfile(os)

顺从地返回绝对路径。要查找当前正在执行的脚本的路径:

inspect.getfile(inspect.currentframe())

(感谢@jbochi)

inspect.getabsfile(inspect.currentframe()) 

给出当前正在执行的脚本的绝对路径(感谢 @Sadman_Sakib)。


1
从代码中可以看出,inspect.getfile(os)与os.__file__是相同的。代码如下:def getfile(object): """找出对象定义所在的源文件或编译文件。""" if ismodule(object): if hasattr(object, 'file'): return object.file - idanzalz
2
这应该是被接受的答案。感谢您提供这个详尽的答案。 - YSC
1
在这里补充说明:inspect.getabsfile(inspect.currentframe()) 可以获得当前执行脚本的绝对路径。 - Sadman Sakib

51

我不明白为什么没有人谈论这个,但对我来说最简单的解决方法是使用imp.find_module("modulename")(文档在这里):

import imp
imp.find_module("os")

它返回一个元组,在第二个位置包含路径:

(<open file '/usr/lib/python2.7/os.py', mode 'U' at 0x7f44528d7540>,
'/usr/lib/python2.7/os.py',
('.py', 'U', 1))
此方法优于“inspect”方法的原因在于您无需导入模块即可使其工作,并且可以使用输入中的字符串。例如,当检查在另一个脚本中调用的模块时非常有用。
编辑:
在Python3中,可以使用importlib模块: importlib.util.find_spec文档:
返回指定模块的规范。首先,会检查sys.modules是否已经导入了该模块。如果是,则返回sys.modules [name] .spec。如果该值恰好设置为None,则引发ValueError。如果模块不在sys.modules中,则在sys.meta_path中搜索具有给定搜索器的'path'值的合适规范。如果找不到规范,则返回None。
如果名称是子模块的名称(包含点),则将自动导入其父模块。
名称和包参数与importlib.import_module()相同。换句话说,相对模块名称(以前导点开头)有效。

2
在Python 2中(当前版本为2.7.13),imp并未被弃用。但是,在Python 3中,自3.4版本起,imp已经被弃用。应该使用importlib代替imp。我喜欢这个解决方案,因为即使实际导入失败(例如,因为64位模块适用于32位引擎),它仍然可以工作。 - mdew
当尝试查找64位sqlite3的路径并且导入失败时,这真的很好。完美。 - rahvin_t
3
这段文字是关于Python3+中一个名为importlib.util.find_spec的函数,用来代替旧版的imp.find_module函数。如果有人需要使用它,可以尝试这个代码片段:importlib.machinery.PathFinder().find_module("os").get_filename(),它能够找到并返回一个模块的文件名。 - Torxed
这种方法可以在不导入实际模块的情况下工作,非常适合我用来确定从共享计算机导入了哪个版本的模块。 - Gouda
DeprecationWarning: imp 模块已被废弃,建议使用 importlib。以及 "AttributeError: module 'importlib' has no attribute 'find_module"。 - Peter Krauss

22

这很简单。

每个模块都有一个__file__变量,显示它相对于您当前位置的路径。

因此,获取通知其目录的模块非常简单:

os.path.dirname(__file__)

16
几乎正确但并非完全正确——__file__不是“相对于你当前所在的位置”的;当它是相对路径(仅当sys.path中存在相对路径时)时,它是相对于模块被加载时所在的位置 - Charles Duffy
5
从Python 3.9开始,__file__总是一个绝对路径。 - user3064538

17
import os
path = os.path.abspath(__file__)
dir_path = os.path.dirname(path)

1
由于__file__只是dir/test.py,所以在我的Linux python 2.6上无法工作。abspath涉及cwd以完成路径名,这不是期望的结果。但如果您导入一个模块,则m.__file__会给出期望的结果。 - Ben Bryant

16
import module
print module.__path__

包支持一个特殊的属性__path__。它被初始化为一个列表,其中包含在执行该文件中的代码之前,包含包的__init__.py的目录的名称。可以修改此变量;这样做会影响将来搜索包中包含的模块和子包。

虽然这个特性不经常使用,但它可以用于扩展在一个包中找到的模块集。

来源


14

如果您想在不加载模块的情况下检索模块路径:

import importlib.util

print(importlib.util.find_spec("requests").origin)

示例输出:

/usr/lib64/python3.9/site-packages/requests/__init__.py

一个被低估的答案。正是我在寻找的。但要注意的是,该模块尚未导入。当您以编程方式运行pytest进行覆盖率测试时,如果在调用pytest之前导入正在测试的模块,则所有导入、类/函数定义等都将被标记为未覆盖,因为它已经被导入了。 - Kashyap

11

命令行实用程序

您可以将其调整为命令行实用程序,

python-which <package name>

enter image description here


创建 /usr/local/bin/python-which
#!/usr/bin/env python

import importlib
import os
import sys

args = sys.argv[1:]
if len(args) > 0:
    module = importlib.import_module(args[0])
    print os.path.dirname(module.__file__)

使其可执行
sudo chmod +x /usr/local/bin/python-which

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