PEP 549 – 实例描述符
- 作者:
- Larry Hastings <larry at hastings.org>
- 讨论组:
- Python-Dev 列表
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建:
- 2017-09-04
- Python 版本:
- 3.7
- 发布历史:
- 2017-09-04
拒绝通知
https://mail.python.org/pipermail/python-dev/2017-November/150528.html
摘要
Python 的描述符协议要求描述符是对象类型的成员。此 PEP 提出对描述符协议的扩展,允许使用描述符协议用于实例的成员。这将允许在模块中使用属性。
理由
Python 的描述符协议引导程序员进行优雅的 API 设计。如果你的类支持类似数据的成员,并且你可能在将来需要在更改成员的值时运行代码,建议你现在简单地将其声明为类的简单数据成员。如果将来你确实需要运行代码,可以将其更改为“属性”,并且 API 不会改变。
但是考虑一下 Python API 设计的第二条最佳实践:如果你正在编写单例,不要编写类,而是直接将代码构建到模块中。不要让你的用户实例化单例类,不要让你的用户必须通过存储在模块中的单例对象进行解引用,而是直接使用模块级函数和模块级数据。
不幸的是,这两条最佳实践相互冲突。问题在于属性不支持模块。模块是单个通用 module
类型的实例,修改或子类化此类型以在模块中添加属性是不可行的。这意味着面对这种 API 设计决策的程序员(其中类似数据的成员是存储在模块中的单例),必须预先为数据添加难看的“getter”和“setter”。
在纯 Python 中添加对模块属性的支持最近已成为可能;从 Python 3.5 开始,Python 允许为模块对象的 __class__
属性赋值,专门用于此目的。以下是如何使用此功能在模块中添加属性的示例
import sys, types
class _MyModuleType(types.ModuleType):
@property
def prop(self, instance, owner):
...
sys.modules[__name__].__class__ = _MyModuleType
这可以工作,并且是支持的行为,但它很笨拙且晦涩。
此 PEP 提出了一种针对类型的可选加入扩展到描述符协议,专门设计用于在模块中启用属性。该机制是一种在实例的成员中支持描述符协议的方法,而无需将该成员声明为类变量。
虽然这被提议为一种通用机制,但作者目前只预见到它对模块对象有用。
实现
基本思想很简单:修改 tp_descr_get
和 tp_descr_set
函数(由 PyModule_Type
公开),以检查与之交互的属性,如果它支持描述符协议,则调用相关的公开函数。
我们的实现面临着两项挑战
- 由于此代码将在每次在方法上查找属性时运行,因此它需要在一般情况下(属性中存储的对象不是描述符)添加很少的开销。
- 由于函数是描述符,我们必须注意不要对所有对象都支持描述符协议。否则,所有模块级函数将突然绑定到模块实例,就像它们是模块对象上的方法调用一样。模块句柄将作为“self”参数传递给所有模块级函数。
这两个挑战都可以通过相同的方法解决:我们定义了一个新的“快速子类”标志,这意味着“此对象是一个描述符,当此对象作为实例的属性查找时,应该直接支持它”。到目前为止,此标志只在两种类型上设置:property
和 collections.abc.InstanceDescriptor
。后者是一个抽象基类,其唯一目的是允许用户类继承此“快速子类”标志。
原型
此功能的原型正在 GitHub 上开发 [github].
致谢
Armin Rigo 在被提出“模块属性”的想法时基本上提出了这种机制,并教育了作者关于问题复杂性和正确解决方案的知识。Nathaniel J. Smith 指出了 3.5 关于在模块对象上为 __class__
赋值的扩展,并提供了示例。
参考资料
- 该分支位于此处
- https://github.com/larryhastings/cpython/tree/module-properties
- 针对主 CPython 存储库的拉取请求位于此处
- https://github.com/python/cpython/pull/3534
版权
本文档已置于公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0549.rst