__path__有什么用处?

80

直到今天我才注意到一些包上定义的__path__特殊属性。根据文档:

包支持一个或多个特殊属性,__path__就是其中之一。它被初始化为一个列表,其中包含在执行文件中的代码之前,持有包的__init__.py文件的目录名称。这个变量可以被修改,并影响对于包中包含模块和子包的未来搜索。

虽然这个功能并不经常需要使用,但它可以用来扩展找到的模块集合。

有人能向我解释一下这是什么意思,以及为什么我需要使用它吗?

4个回答

50

通常与pkgutil一起使用,以使软件包在磁盘上分布。例如,zope.interface和zope.schema是单独的发行版(zope是一个“命名空间包”)。您可能已经在/usr/lib/python2.6/site-packages/zope/interface/中安装了zope.interface,而在/home/me/src/myproject/lib/python2.6/site-packages/zope/schema中更局部地使用zope.schema。

如果将pkgutil.extend_path(__path__, __name__)放置在/usr/lib/python2.6/site-packages/zope/__init__.py中,则可以导入zope.interface和zope.schema,因为pkgutil将更改__path__['/usr/lib/python2.6/site-packages/zope', '/home/me/src/myproject/lib/python2.6/site-packages/zope']

pkg_resources.declare_namespace(Setuptools的一部分)类似于pkgutil.extend_path,但它更加了解路径上的zip文件。

手动更改__path__不常见,也可能不必要,尽管查看命名空间包时调试导入问题非常有用。

您还可以通过创建一个文件distutils/__init__.py并将其提前添加到sys.path中进行猴子补丁,例如,我有时会对distutils进行猴子补丁:

import os
stdlib_dir = os.path.dirname(os.__file__)
real_distutils_path = os.path.join(stdlib_dir, 'distutils')
__path__.append(real_distutils_path)
execfile(os.path.join(real_distutils_path, '__init__.py'))
# and then apply some monkeypatching here...

2
我有一种感觉它与命名空间包有关,但是我在拼凑它的工作原理时遇到了问题。谢谢! - Jason Baker

35

如果你改变 __path__,你可以强制解释器在不同的目录中查找属于该包的模块。

这将允许您根据运行时条件加载相同模块的不同版本。如果您想在不同平台上使用相同功能的不同实现,可以这样做。


Django 使用这个方法在启动时动态加载 django-admin.py 命令。commands = {name: 'django.core' for name in find_commands(__path__[0])} - Kevin S Lin

9
除了根据运行时条件选择模块的不同版本,如Syntactic所说,这个功能还可以让您将包拆分为多个部分/下载/安装,同时保持单个逻辑包的外观。
考虑以下情况。
- 我有两个包:mypkg_mypkg_foo。 - _mypkg_foo 包含可选模块foo.pymypkg中。 - 在下载和安装时,mypkg不包含foo.pymypkg__init__.py可以像这样做:
try:
    import _mypkg_foo
    __path__.append(os.path.abspath(os.path.dirname(_mypkg_foo.__file__)))
    import mypkg.foo
except ImportError:
    pass

如果有人安装了包_mypkg_foo,那么mypkg.foo对他们是可用的。如果没有安装,则不存在。

2
在这里,import mypkg.foo 的意义是什么? - codeforester

6
我遇到的一个特殊情况是当一个包变得足够大,我想将其部分拆分为子目录,而不必更改任何引用它的代码。例如,我有一个名为“views”的包,其中收集了一些支持实用函数,这些函数与包的主要顶级目的混淆在一起。我能够将这些支持函数移动到子目录“utils”中,并在“views”包的“__init__.py”中添加以下行:
__path__.append(os.path.join(os.path.dirname(__file__), "utils"))

通过对 views/__init__.py 文件的更改,我可以在不对其他文件进行进一步更改的情况下,使用新的文件结构运行软件的其余部分。
(我曾尝试在 views/__init__.py 文件中使用import语句进行类似操作,但是子包模块仍然无法通过view包的导入可见 - 我不确定是否有遗漏;欢迎对此进行评论!)
(此响应基于Python 2.7安装)

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