PEP 549 – 实例描述符
- 作者:
- Larry Hastings <larry at hastings.org>
- 讨论至:
- Python-Dev 列表
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 创建日期:
- 2017年9月4日
- Python 版本:
- 3.7
- 发布历史:
- 2017年9月4日
拒绝通知
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 提议对描述符协议进行每个类型选择加入的扩展,该扩展专门用于在模块中启用属性。该机制是一种在成员未声明为类变量的情况下,为类的实例的成员遵循描述符协议的方法。
尽管这被提议为一个通用机制,但作者目前只预见到它对模块对象有用。
实施
基本思想很简单:修改由 PyModule_Type 公开的 tp_descr_get 和 tp_descr_set 函数,以检查交互的属性,如果它支持描述符协议,则调用相关的公开函数。
我们的实现面临两个挑战:
- 由于每次查找方法上的属性时都会运行此代码,因此在一般情况下(属性中存储的对象不是描述符),它需要增加非常少的开销。
- 由于函数是描述符,我们必须小心不要为所有对象遵循描述符协议。否则,所有模块级别的函数将突然绑定到模块实例,就像它们是对模块对象的方法调用一样。模块句柄将作为“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