__path__的用途是什么?

81 投票
4 回答
53877 浏览
提问于 2025-04-15 21:56

今天我才注意到一些包上有一个叫做 __path__ 的属性。根据文档的说明:

包还支持一个特别的属性,叫做 __path__。这个属性在执行包里面的 __init__.py 文件之前,会被初始化为一个列表,列表里包含了存放这个包的 __init__.py 文件的目录名。这个变量是可以修改的;如果你修改了它,会影响到以后查找包里面的模块和子包。

虽然这个功能不常用,但它可以用来扩展一个包中找到的模块集合。

有人能给我解释一下这到底是什么意思,以及我为什么会想要使用它吗?

4 个回答

9

除了根据运行时条件选择不同版本的模块,正如Syntactic所说,这个功能还可以让你把一个包拆分成多个部分/下载/安装,同时保持看起来像一个完整的逻辑包。

想象一下以下情况:

  • 我有两个包,mypkg_mypkg_foo
  • _mypkg_foo是一个可选的模块,里面有mypkg需要的东西,叫做foo.py
  • mypkg被下载和安装时,它里面并没有foo.py

mypkg__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。如果没有安装,那就不存在这个功能。

35

如果你改变了 __path__ 的值,就可以让解释器在其他文件夹里寻找属于那个包的模块。

这样做的好处是,比如说你可以根据运行时的情况加载同一个模块的不同版本。如果你想在不同的平台上使用同样功能的不同实现,就可以用这种方法。

51

这通常和 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。

如果你在 /usr/lib/python2.6/site-packages/zope/__init__.py 文件中放入 pkgutil.extend_path(__path__, __name__),那么 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__ 是不常见的,可能也不必要,不过在调试命名空间包的导入问题时,查看这个变量是有用的。

你也可以用 __path__ 来进行猴子补丁(monkeypatching),例如,我有时通过创建一个在 sys.path 前面的位置的文件 distutils/__init__.py 来对 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...

撰写回答