__path__的用途是什么?
今天我才注意到一些包上有一个叫做 __path__
的属性。根据文档的说明:
包还支持一个特别的属性,叫做
__path__
。这个属性在执行包里面的__init__.py
文件之前,会被初始化为一个列表,列表里包含了存放这个包的__init__.py
文件的目录名。这个变量是可以修改的;如果你修改了它,会影响到以后查找包里面的模块和子包。虽然这个功能不常用,但它可以用来扩展一个包中找到的模块集合。
有人能给我解释一下这到底是什么意思,以及我为什么会想要使用它吗?
4 个回答
除了根据运行时条件选择不同版本的模块,正如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
。如果没有安装,那就不存在这个功能。
如果你改变了 __path__
的值,就可以让解释器在其他文件夹里寻找属于那个包的模块。
这样做的好处是,比如说你可以根据运行时的情况加载同一个模块的不同版本。如果你想在不同的平台上使用同样功能的不同实现,就可以用这种方法。
这通常和 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...