在Python中查找模块路径而不导入模块

57

我见过几种通过首先导入模块来查找其路径的方法。是否有一种方法可以在不导入模块的情况下完成这个任务?


1
为什么?你为什么不想导入它? - user225312
9
因为在程序需要路径的那一点上导入是无用的,并且可能会导致循环依赖和其他问题。导入发生在后面一个更合适的时间。 - jeffcook2150
1
您可能只想查看模块的源代码,而不实际运行任何代码。 - BrockLee
4个回答

71

使用pkgutil模块:

>>> import pkgutil
>>> package = pkgutil.get_loader("pip")
>>> package.filename
'/usr/local/lib/python2.6/dist-packages/pip-0.7.1-py2.6.egg/pip'
>>> package = pkgutil.get_loader("threading")
>>> package.filename
'/usr/lib/python2.6/threading.py'
>>> package = pkgutil.get_loader("sqlalchemy.orm")
>>> package.filename
'/usr/lib/pymodules/python2.6/sqlalchemy/orm'

在Python 3中,使用pkgutil.get_loader("模块名").get_filename()代替。

使用imp模块:

>>> import imp
>>> imp.find_module('sqlalchemy')
(None, '/usr/lib/pymodules/python2.6/sqlalchemy', ('', '', 5))
>>> imp.find_module('pip')
(None, '/usr/local/lib/python2.6/dist-packages/pip-0.7.1-py2.6.egg/pip', ('', '', 5))
>>> imp.find_module('threading')
(<open file '/usr/lib/python2.6/threading.py', mode 'U' at 0x7fb708573db0>, '/usr/lib/python2.6/threading.py', ('.py', 'U', 1))

注意:使用imp模块,你无法像这样做:imp.find_module('sqlalchmy.orm')


@cgohlke:实际上没有__pkutil__包,应该是__pkgutil__,这是一个打字错误,感谢您指出 :) - mouad
1
@mouad 我其实也在寻找同样的东西。使用 pkgutil 的上述实现并不能在所有情况下满足要求。pkgutil.get_laoder("sqlalchemy.orm") 会执行 sqlalchemy/__init__.py,但不会执行 sqlalchemy/orm.py - Prody
2
@Prody:是的,在包的情况下,它将执行“init.py”包,正如文档(http://docs.python.org/release/3.1.5/library/pkgutil.html#pkgutil.get_loader)所说:“如果命名模块尚未导入,则导入其包含的包(如果有),以建立包__path__”,这完全有意义 :) - mouad
12
我发现在 Python 3.5.1 中,package.filename 不存在,但 package.get_filename() 存在。 - Oddthinking
有时候它在Python3.11上无法正常工作。例如,我该如何在Python3.11中获取os.py(os标准库)的位置? - tamuhey

16

对于Python3,imp已经被弃用。请使用pkgutil(如上所示)或者对于Python 3.4+,请使用importlib.util.find_spec

>>> import importlib
>>> spec = importlib.util.find_spec("threading")
>>> spec.origin
'/usr/lib64/python3.6/threading.py'

模块文件夹中包含 __init__.py,因此 spec.origin 属性不存在。请改用以下代码:>>> spec.submodule_search_locations[0] - Cyril Waechter
2
不幸的是 find_spec 可能会尝试导入模块 https://github.com/python/cpython/blob/ec2385e315fc6b28d92dfb4c97fefcbb1e7daa68/Lib/importlib/util.py#L94 - karlicoss
只有当不使用find_spec来查找子模块时,这才有效:find_spec("foo")不会导入任何内容,但是find_spec("foo.bar")会导入foo(从而评估foo.__init__.py)。 - JustinBull
在Python3.11中,"os"模块的spec.origin返回值为"frozen"。 - tamuhey
@tamuhey 这意味着该模块已经编译成字节码并包含在一个独立的 Python 解释器中,即它没有位置。 - Bryce Guinta

1

对于大多数用例,实际上您不需要第三方的帮助。importlib.util.find_spec自Python3.4以来就存在,并解决了顶级导入的问题:

>>> import importlib.util
>>> spec = importlib.util.find_spec("foo")
>>> spec.origin
/home/me/my_venv/python3.11/site-packages/foo/__init__.py

如果需要一个可移植的变体来获取父文件夹:

[...]
>>> from pathlib import Path
>>> Path(spec.origin).parent
/home/me/my_venv/python3.11/site-packages/foo

注意事项:

  • 对于子包(例如foo.bar),将实际导入父包。由于Python的动态性质,包括导入的解析方式,没有正确的解决方案不进行实际导入(尝试在不导入os的情况下查找os.path的位置作为真实世界示例)。
  • 如果包不存在,则spec将为None
  • 如果包实际上是一个命名空间,则spec.origin将为None

0
你可能想在你的解释器中尝试运行这段代码:
>>> import sys
>>> sys.modules['codecs'].__file__ # codecs is just an example
'/usr/lib/python2.7/codecs.pyc'

+1 我本来会建议使用DFS或者os.system之类的东西。但是这个太棒了——我不知道这可以做到。 - inspectorG4dget
这只适用于一些来自标准库的模块。您可以尝试使用SQLAlchemy。虽然感谢您的回复。 - jeffcook2150
15
这将无法适用于尚未被加载的软件包。 - mouad
问题明确要求“不导入它”。 - Karl Knechtel

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