如何在Python中创build一个名称空间包?
在Python中,命名空间包允许您在多个项目中传播Python代码。 当您想要将相关库作为单独的下载发布时,这非常有用。 例如,在PYTHONPATH
的目录Package-1
和Package-2
中,
Package-1/namespace/__init__.py Package-1/namespace/module1/__init__.py Package-2/namespace/__init__.py Package-2/namespace/module2/__init__.py
最终用户可以import namespace.module1
并import namespace.module2
。
定义一个名称空间包的最好方法是什么,以便多个Python产品可以在该名称空间中定义模块?
有一个标准的模块叫做pkgutil ,你可以把模块“追加”到一个给定的命名空间。
用你提供的目录结构:
Package-1/namespace/__init__.py Package-1/namespace/module1/__init__.py Package-2/namespace/__init__.py Package-2/namespace/module2/__init__.py
你应该把这两行放在Package-1/namespace/__init__.py
和Package-2/namespace/__init__.py
(*)中:
from pkgutil import extend_path __path__ = extend_path(__path__, __name__)
(*因为-unless你声明他们之间的依赖关系 – 你不知道哪个会被首先识别 – 请参阅PEP 420了解更多信息)
正如文档所述 :
这将添加到程序包的
__path__
所有以该程序包命名的sys.path
上的所有子目录。
从现在起,你应该能够独立分发这两个软件包。
TL; DR:
在Python 3.3中,你不需要做任何事情,只要不把任何__init__.py
放到你的命名空间的软件包目录中,它就可以工作。 在3.3之前的版本中,selectpkgutil.extend_path()
方法的pkgutil.extend_path()
解决scheme,因为它具有前瞻性,并且已经与隐式名称空间包兼容。
Python 3.3引入了隐式名称空间包,参见PEP 420 。
这意味着现在有三种types的对象可以通过import foo
创build:
- 由
foo.py
文件表示的模块 - 常规包,由包含
__init__.py
文件的目录foo
表示 - 名称空间包,由一个或多个目录
foo
表示,没有任何__init__.py
文件
软件包也是模块,但是在我说“模块”的时候,我的意思是“非软件包模块”。
首先它扫描一个模块或常规软件包的sys.path
。 如果成功,则停止search并创build和初始化模块或包。 如果找不到模块或常规软件包,但至lessfind一个目录,则会创build并初始化一个名称空间包。
模块和常规软件包将__file__
设置为从其创build的.py
文件。 常规和名称空间包的__path__
设置为__path__
创build的目录。
当你import foo.bar
,上面的search首先发生在foo
,那么如果find了一个包,searchbar
就是用foo.__path__
作为searchpath而不是sys.path
。 如果findfoo.bar
则会创build并初始化foo
和foo.bar
。
那么常规包和名称空间包怎么混合呢? 通常他们不这样做,但旧的pkgutil
显式名称空间包方法已被扩展为包含隐式名称空间包。
如果你有一个现有的常规软件包,像这样有一个__init__.py
:
from pkgutil import extend_path __path__ = extend_path(__path__, __name__)
…传统的行为是添加任何其他常规软件包searchpath到__path__
。 但在Python 3.3中,它也添加了命名空间包。
所以你可以有以下目录结构:
├── path1 │ └── package │ ├── __init__.py │ └── foo.py ├── path2 │ └── package │ └── bar.py └── path3 └── package ├── __init__.py └── baz.py
…只要两个__init__.py
具有extend_path
行(并且path1
, path2
和path3
在你的sys.path
) import package.foo
, import package.bar
和import package.baz
都将工作。
pkg_resources.declare_namespace(__name__)
尚未更新为包含隐式名称空间包。
这部分应该是不言自明的。
简而言之,将名称空间代码放在__init__.py
,更新setup.py
来声明一个名称空间,然后就可以自由地进行了。
这是一个古老的问题,但最近有人在我的博客上评论说,我关于命名空间包的发布仍然是相关的,所以我想在这里链接到它,因为它提供了一个实际的例子:
http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb
那链接到这篇文章为什么发生的主要胆量:
http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package
__import__("pkg_resources").declare_namespace(__name__)
技巧几乎是驱动TiddlyWeb中插件的pipe理,因此似乎正在努力。
你有你的Python命名空间的概念,python不可能将包放入模块。 包不包含模块。
Python包只是一个包含__init__.py
文件的文件夹。 模块是包中的任何其他文件(或直接在PYTHONPATH
),其扩展名为.py
。 所以在你的例子中你有两个包但没有定义模块。 如果你认为一个软件包是一个文件系统文件夹,而一个模块是文件,那么你就会明白为什么软件包包含模块,而不是相反。
因此,在您的示例中,假设Package-1和Package-2是放在Pythonpath上的文件系统上的文件夹,您可以使用以下命令:
Package-1/ namespace/ __init__.py module1.py Package-2/ namespace/ __init__.py module2.py
现在您有一个包含两个模块module1
和module2
包namespace
。 除非你有一个很好的理由,否则你应该把这些模块放在这个文件夹中,并且只有像下面这样的pythonpath:
Package-1/ namespace/ __init__.py module1.py module2.py