描述器学习指南
从面向过程到面向对象
最后更新于
从面向过程到面向对象
最后更新于
Python 语言当前使用的解释器主要是 CPython,也就是说,基于面向过程的 C 语言构建了面向对象的 Python,那么其中的基础原理有哪些值得我们学习呢?
描述器
我们来看看官方文档是怎么形容描述器的吧。
学习描述器能更深地理解 Python 工作的原理并更加体会到其设计的优雅性。 描述器是一个强大而通用的协议。 描述器是特征属性、方法静态方法、类方法和
super()
背后的实现机制。 描述器在 Python 内部被广泛使用,实现了自 2.2 版本中(最早由 PEP252 提出)引入的新式类。 描述器简化了底层的 C 代码并为 Python 的日常程序提供了一组灵活的新工具。
事实上描述器这个主题并不冷门,网上已有了大量的讨论。但是前几天在翻译官方文档间隙查资料的时候,发现已有的博文和参考资料大都比较零散,故在此作以简单整合。
本文的编排顺序是作者精心调整过的,建议您按照标题顺序依次阅读。
作者秉持 KISS 主义,故而本文不对已有的博文做重复陈述,只给出对应链接。
Keep it Simple, Stupid!
首先我们当然要 浏览 一下官方文档了,作者已经贡献了简中翻译。
首先,面向对象与面向过程的区别之一就是cls.attribute
形式的调用,这被称为属性调用,这里的属性是指广义上的,包括基础属性和方法。
那么我们就先来了解一下 Python 中的属性调用的顺序:
python 高级编程——描述符 Descriptor 详解(上篇)——python 对象的属性访问优先级与属性的控制与访问) python 高级编程——描述符 Descriptor 详解(中篇)——python 对象的属性访问优先级与属性的控制与访问)
依照 MRO 顺序的类的类属性中的 数据描述器属性
实例对象的属性object.__dict__
依照 MRO 顺序的类的类属性中的 非数据描述器属性
依照 MRO 顺序的类的类属性中的 普通(非描述器)属性cls.__dict__
依照元类的 MRO 顺序的类的类属性中的 元类数据描述器属性
依照 MRO 顺序的类的类属性中的 数据描述器属性
依照 MRO 顺序的类的类属性中的 普通(非描述器)属性cls.__dict__
依照 MRO 顺序的类的类属性中的 非数据描述器属性
依照元类的 MRO 顺序的类的类属性中的 元类非数据描述器属性
Python 中 类的本质是元类创建的对象,所以相当于是外面套了一层不包含普通属性(亦即非描述器属性)的对象属性访问顺序。
现在我们可以去看一下描述器的核心内容了。
python 高级编程——描述符 Descriptor 详解(下篇)——python 描述符三剑客详解
定义这些方法中的任何一个的对象被视为描述器,在被作为属性时覆盖其默认行为。
仅当一个包含这些方法的类(称为 描述器类)的实例出现于一个 所有者类 中的时候才会起作用(该描述器必须在所有者类或其某个上级类的字典中)。
如果一个对象定义了 __set__()
或 __delete__()
,则它会被视为数据描述器。
仅定义了 __get__()
的描述器称为非数据描述器(它们通常被用于方法,但也可以有其他用途)。
数据和非数据描述器的不同之处在于,如何计算实例字典中条目的替代值。 如果实例的字典具有与数据描述器同名的条目,则数据描述器优先。如果实例的字典具有与非数据描述器同名的条目,则该字典条目优先。
为了使数据描述器成为只读的,应该同时定义 __get__()
和 __set__()
,并在 __set__()
中引发 AttributeError
。用引发异常的占位符定义 __set__()
方法使其成为数据描述器。
如果想要彻底地理解描述器,那么我们就不得不了解一下描述器的具体代码实现。 但是直接上源码未必太过硬核,以流程图的形式去解析可能是最好的方式,下面这篇文章正是如此。
学习了这么多原理,最后自然要来个有实际意义例子来练练手了。看看怎样利用描述器来实现装饰器 property
吧。
最后我们就可以再回头看看官方文档了,相信这次你可以轻松理解其中内容。