Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 382 – 命名空间包

作者:
Martin von Löwis <martin at v.loewis.de>
状态:
已拒绝
类型:
标准跟踪
创建日期:
2009年4月2日
Python 版本:
3.2
发布历史:


目录

拒绝通知

在2012年美国PyCon冲刺周的第一天,我们对PEP 382PEP 402进行了长时间且富有成效的讨论。我们最终拒绝了这两个PEP,但将编写一个新的PEP,以继承PEP 402的精神。Martin von Löwis写了一份摘要:[2]

摘要

命名空间包是一种将单个Python包分解为磁盘上多个目录的机制。在当前的Python版本中,必须制定一个计算包的__path__的算法。通过此处提出的增强,导入机制本身将构建组成包的目录列表。此PEP的实现可在[1]处获取。

术语

在本PEP中,术语“包”指的是由Python导入语句定义的Python包。术语“分发”指的是存储在Python包索引中并由distutils或setuptools安装的可单独安装的Python模块集。术语“供应商包”指的是由操作系统打包机制(例如,Debian或Redhat包安装在Linux系统上)安装的文件组。

术语“部分”指的是单个目录(可能存储在zip文件中)中为命名空间包做出贡献的一组文件。

今日命名空间包

Python目前提供 pkgutil.extend_path 来将一个包标记为命名空间包。推荐的使用方法是在包的 __init__.py 中放入

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

每个分发都需要在其 __init__.py 中提供相同的内容,以便无论哪个包的部分首先被导入,extend_path 都会被调用。因此,包的 __init__.py 实际上无法定义任何名称,因为它取决于sys.path上包片段的顺序,以及哪个部分首先被导入。作为一项特殊功能,extend_path 会读取名为 <packagename>.pkg 的文件,这些文件允许声明额外的部分。

setuptools 提供了一个类似的函数 pkg_resources.declare_namespace,其使用形式为

import pkg_resources
pkg_resources.declare_namespace(__name__)

在部分的 __init__.py 中,不需要对 __path__ 进行赋值,因为 declare_namespace 通过 sys.modules 修改包的 __path__。作为一项特殊功能,declare_namespace 还支持 zip 文件,并在内部注册包名,以便 setuptools 未来对 sys.path 的添加可以正确地为每个包添加额外的部分。

setuptools 允许在分发的 setup.py 中声明命名空间包,这样分发开发人员就不必自己将神奇的 __path__ 修改放入 __init__.py 中。

基本原理

当前对命名空间包的命令式方法导致了多种略有不兼容的机制来提供命名空间包。例如,pkgutil 支持 *.pkg 文件;setuptools 不支持。同样,setuptools 支持检查 zip 文件,并支持将其 _namespace_packages 变量添加部分,而 pkgutil 不支持。

此外,当前的方法给系统供应商带来了问题。供应商包通常不得提供重叠的文件,并且尝试安装一个包含磁盘上已存在文件的供应商包将失败或导致不可预测的行为。由于供应商可能会选择以这样的方式打包分发,即它们最终都位于命名空间包的单个目录中,所有部分都将贡献冲突的 __init__.py 文件。

规范

这里不使用导入包的命令式机制,而是提出一种声明式方法:一个名称以 .pyp(表示Python包)结尾的目录包含包的一部分。

导入语句得到扩展,以便计算名为 P 的包的 __path__ 属性,该属性由可选的单个目录名 P(包含文件 __init__.py)以及所有名为 P.pyp 的目录组成,这些目录按照它们在父包的 __path__(或 sys.path)中找到的顺序排列。如果找到其中任何一个,则继续搜索包的其他部分。

一个目录可以同时包含 P/__init__.py 形式和 P.pyp 形式的包。

导入机制没有其他变化;搜索模块(包括 __init__.py)将继续在遇到的第一个模块处停止。总而言之,导入包 foo 的过程如下:

  1. 搜索sys.path以查找目录foo或foo.pyp,或文件foo.。如果找到文件且没有目录,则将其视为模块并导入。
  2. 如果找到目录 foo,则检查它是否包含 __init__.py。如果是,则记住 __init__.py 的位置。否则,跳过该目录。一旦找到 __init__.py,将跳过进一步名为 foo 的目录。
  3. 对于目录 foo 和 foo.pyp,这些目录都将添加到包的 __path__ 中。
  4. 如果找到一个 __init__ 模块,它将被导入,并且 __path__ 将被初始化为所有 .pyp 目录计算出的路径。

对导入钩子的影响

PEP 302中定义的加载器和查找器都需要更改以支持命名空间包。不符合以下协议可能导致包未被识别为命名空间包;不支持此协议的加载器和查找器在访问以下函数时必须引发 AttributeError。

查找器需要在上述算法的第1步中支持查找 *.pth 文件。为此,用作路径钩子的查找器必须支持一个方法

finder.find_package_portion(fullname)

此方法将以与 find_module 相同的方式被调用,并且它必须返回一个字符串,该字符串将添加到包的 __path__ 中。如果查找器找不到包的一部分,它应返回 None。从上述调用中引发 AttributeError 将被视为不符合本PEP,并且该异常将被忽略。所有其他异常都将被报告。

查找器可以从 find_modulefind_package_portion 报告成功,允许同时包含一个包含 __init__.py 的包和同一包的一部分。

所有从 find_package_portion 返回的字符串,以及所有 .pyp 目录的路径名,都将添加到新包的 __path__ 中。

讨论

本规范的最初版本提出了添加 *.pth 文件的建议,类似于这些文件在 sys.path 上的使用方式。通过通配符标记 (*),包可以指示整个路径是通过查看父路径,搜索适当命名的子目录来派生的。

后来人们发现对完整 .pth 语法的支持是不合适的,并且 .pth 文件被更改为仅仅是标记文件,表示一个目录是一个包。Peter Tröger 建议 .pth 是一个不合适的扩展名,因为所有与 Python 相关的文件扩展名都应该以 .py 开头。因此,标记文件被重命名为 .pyp

Dinu Gherman 随后发现使用标记文件并非必需,并且目录扩展名可以很好地用作此类标记。这正是本 PEP 目前提出的方案。

Phillip Eby 在比较了Python的包语法与其他语言中的包语法后,设计了PEP 402作为本PEP的替代方法。PEP 402提议完全不使用标记文件。在2011年德国PyCon的讨论中,人们指出明确声明一个目录为一个包的贡献是一个可取的特性,而不是障碍。特别是,Jython开发人员注意到,如果没有声明Python包的需要,Jython很容易将一个Java包目录误认为是Python包。

包可以停止填充命名空间包的 __init__.py。因此,extend_path 和 declare_namespace 变得过时。

命名空间包可以开始提供非平凡的 __init__.py 实现;为此,建议单个分发仅提供一个包含命名空间包的 __init__.py(以及可能属于命名空间包本身的任何其他模块)的部分。

该机制与现有命名空间机制大部分兼容。extend_path 将根据本规范进行调整;任何其他机制都可能导致部分被两次添加到 __path__。

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0382.rst

上次修改时间:2025-02-01 08:55:40 GMT