将多个软件包组合成一个Python名称空间

有时有必要沿着不同的物理路径将位于同一个名称空间中的几个包分开。 例如,如果您希望能够转移插件的不同布局,可以在不控制插件位置的情况下随后添加它们,并同时通过一个命名空间访问它们。

该备忘单更适合初学者,专门用于Python名称空间。

让我们看一下如何在不同版本的Python中完成此操作,因为尽管很快不再支持Python2,但我们当中的许多人现在正处于两次大火之间,这只是过渡中的重要细微差别之一。

图片

考虑以下示例:

我们要获取包的结构:

namespace1 package1 module1 package2 module2 

Module1的文件内容

 print('package 1') var1 = 1 

Module2文件内容

 print('package 2') var2 = 2 

同时,程序包以以下文件夹结构分发:

  path1 namespace1 package1 module1 path2 namespace1 package2 module2 

假设路径1和路径2已经以某种方式添加到sys.path中。 我们需要访问module1和module2:

  from namespace1.package1 import module1 from namespace1.package2 import module2 

执行此代码在Python 3.7中会发生什么? 一切工作都很棒:

 package 1 package 2 

使用Python 3.3中的PEP-420,已经出现了对隐式名称空间的支持。 另外,从py33导入软件包时,不需要创建__init__.py文件。 并且在导入名称空间时,它只是_ forbidden_​​。 如果__init__.py文件存在于名称为name1的一个或两个目录中,则导入第二个软件包时将发生错误。

 ModuleNotFoundError: No module named 'namespace1.package2' 

因此,内部人员的存在明确确定了程序包,并且程序包无法组合,它是一个单独的实体。 如果您要启动一个独立于旧开发项目的新项目,并且将使用pip安装软件包,则需要坚持使用此方法。 但是,有时我们会继承旧代码,至少还需要保留一段时间,或者将其移植到新版本中。

让我们继续Python 2.7 。 在此版本中,它已经变得更加有趣,您首先需要在每个目录中添加__init__.py来创建软件包,否则解释器根本无法识别这组文件中的软件包。 然后在与namespace1相关的__init__文件中编写显式的命名空间声明,否则,将仅导入第一个包。

 from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

这会发生什么? 当解释器到达第一次导入时,将在sys.path中搜索具有该名称的程序包,该程序包位于path1 / namespace1中,并且解释器执行path1 / namespace1 / __ init__.py。 不进行进一步搜索。 但是,extend_path函数本身会在整个sys.path中执行搜索,查找所有具有名称空间1和内部名称的软件包,并将它们添加到软件包名称空间1的__path__变量中,该变量用于在此命名空间中搜索子软件包。

在官方指南中,建议每次放置命名空间1时都应使用相同的缩写。 实际上,除了第一个变量,它们都可以为空,这是在sys.path的搜索过程中发现的,应在其中调用pkgutil.extend_path,因为其余的都不执行。 但是,当然,最好在每个团队中都进行真正的通话,以免束缚您的逻辑,以防万一,因为搜索顺序可以更改,因此不要猜测哪个团队是第一个执行的团队。 出于相同的原因,您不应在变量区域中放置任何其他逻辑__init__文件。

这将在将来的版本中使用,并且此代码可用于编写兼容的代码 ,但请记住,必须在每个分布式程序包中遵守所选的方法。 如果在版本3上,您在对pkgutil.extend_path的调用中将某些收件箱放入了一个收件箱,而使某些收件箱没有收件箱,则此操作将无效。
此外,此选项也适用于计划使用python setup.py install安装的情况。

另一种方法,现在被认为有些过时了,但仍然可以在以下位置找到很多方法:

 #namespace1/__init__.py __import__('pkg_resources').declare_namespace(__name__) 

pkg_resources模块与setuptools软件包一起提供。 此处的含义与pkgutil中的含义相同-每个__init__文件都必须在namespace1的每个位置包含相同的名称空间声明,并且不存在其他代码。 同时,有必要在setup.py中注册名称空间namespace_packages = ['namespace1']。 创建软件包的更详细描述超出了本文的范围。

另外,您经常可以找到这样的代码

 try: __import__('pkg_resources').declare_namespace(__name__) except: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

这里的逻辑很简单-如果未安装setuptools,则我们使用pkgutil,它包含在标准库中。

如果以下列方式之一配置名称空间,则可以从一个模块调用另一个名称空间。 例如,更改命名空间1 / package2 / module2

 import namespace1.package1.module1 print(var1) 

然后,我们将看到如果我们错误地命名一个新程序包和一个现有程序包,并用相同的名称空间包装它会发生什么。 例如,在不同位置将有两个名称为package1的软件包。

 namespace1 package1 module1 package1 module2 

在这种情况下,将仅导入第一个,并且将无法访问module2。 套餐不能合并。

 from namespace1.package1 import module1 from namespace1.package1 import module2 #>>ImportError: cannot import name module2 

总结:

  1. 对于早于3.3并使用pip进行安装的Python,建议您使用隐式命名空间声明。
  2. 如果支持版本2和3,以及同时使用pip和python setup.py install进行安装,则建议使用pkgutil选项。
  3. 如果需要使用此方法支持较旧的软件包,或者需要该软件包具有zip安全性,则建议使用pkg_resources选项。

资料来源:


示例可以在这里找到。

Source: https://habr.com/ru/post/zh-CN458432/


All Articles