问题:如何在Python中创建命名空间包?
在Python中,命名空间包可让您在多个项目中传播Python代码。当您要将相关的库作为单独的下载发布时,这很有用。例如,目录Package-1
和Package-2
中PYTHONPATH
,
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产品可以在该命名空间中定义模块?
回答 0
TL; DR:
在Python 3.3上,您无需执行任何操作,只需将任何内容都不放在__init__.py
命名空间包目录中就可以了。在3.3之前的版本中,请选择一种pkgutil.extend_path()
解决方案pkg_resources.declare_namespace()
,因为它是面向未来的并且已经与隐式命名空间包兼容。
Python 3.3引入了隐式命名空间包,请参阅PEP 420。
这意味着一个对象现在可以创建三种类型的对象import foo
:
foo.py
文件代表的模块- 常规软件包,由
foo
包含__init__.py
文件的目录表示 - 一个命名空间包,由一个或多个目录表示,
foo
没有任何__init__.py
文件
包也是模块,但是当我说“模块”时,我的意思是“非包模块”。
首先,它扫描sys.path
模块或常规软件包。如果成功,它将停止搜索并创建并初始化模块或程序包。如果没有找到模块或常规包,但是找到了至少一个目录,它将创建并初始化一个命名空间包。
模块和常规软件包已__file__
设置.py
为创建它们的文件。常规和命名空间包已__path__
设置为创建它们的目录。
完成此操作后import foo.bar
,将首先针对进行上述搜索foo
,然后如果找到了软件包,bar
则使用foo.__path__
搜索路径而不是进行搜索sys.path
。如果foo.bar
找到,foo
并foo.bar
创建和初始化。
那么常规软件包和命名空间软件包如何混合使用?通常它们不会,但是旧的pkgutil
显式命名空间包方法已扩展为包括隐式命名空间包。
如果您已有这样的常规软件包__init__.py
:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
…遗留行为是在搜索到的路径中将其他任何常规软件包添加到其__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__)
尚未更新为包括隐式命名空间包。
回答 1
有一个称为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__)
(*由于-除非您声明它们之间的依赖关系-您不知道将首先识别其中的哪个- 有关更多信息,请参见PEP 420)
如文档所述:
这将添加到以包命名
__path__
的目录的所有目录子目录中sys.path
。
从现在开始,您应该能够独立分发这两个软件包。
回答 2
简而言之,将命名空间代码放入中__init__.py
,进行更新setup.py
以声明一个命名空间,您可以随意使用。
回答 3
这是一个古老的问题,但是最近有人在我的博客上评论说,我有关命名空间包的帖子仍然有意义,因此我想在这里链接到它,因为它提供了如何实现它的实用示例:
链接到本文以了解发生的主要事情:
http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package
该__import__("pkg_resources").declare_namespace(__name__)
技巧是相当多的驱动器在插件管理TiddlyWeb和迄今似乎是工作了。
回答 4
您已经将Python命名空间概念放到了最前面,在python中无法将包放入模块中。软件包中包含模块,而不是相反。
Python包只是包含__init__.py
文件的文件夹。模块是包中(或直接在上PYTHONPATH
)具有.py
扩展名的任何其他文件。因此,在您的示例中,您有两个包,但未定义任何模块。如果您认为软件包是文件系统文件夹,而模块是文件,那么您会明白为什么软件包包含模块,而不是相反。
因此,在示例中,假设Package-1和Package-2是放在Python路径上的文件系统上的文件夹,则可以具有以下内容:
Package-1/
namespace/
__init__.py
module1.py
Package-2/
namespace/
__init__.py
module2.py
你现在有一个包namespace
有两个模块module1
和module2
。除非您有充分的理由,否则您应该将模块放在文件夹中,并且仅将其放在python路径中,如下所示:
Package-1/
namespace/
__init__.py
module1.py
module2.py