目录
前言
描述符是对多个属性运用相同存取逻辑的一种方式。
描述符是实现了特定协议的类,这个协议包括__get__、__set__和__delete__方法。property类实现了完整的描述符协议。描述符是python的独有特征,不仅在应用层使用,在语言的基础设施中也有用到。
一、描述符示例:验证属性
LineItem类第三版:一个简单的描述符
实现了__get__、__set__、__delete__方法的类是描述符。描述符的用法是,创建一个实例,作为另一个类的类属性。下面例子中,我们将定义一个Quantity描述符类,然后创建两个Quantity实例作为LineItem类的类属性,分别用于管理weight实例属性和price实例属性:

以下是本节常用术语定义:
- 描述符类,实现描述符协议的类。比如Quantity。
- 托管类,把描述符类的实例声明为类属性的类,比如LineItem类。
- 描述符实例,描述符类的各个实例,声明为托管类的类属性。
- 托管实例,托管类的实例,比如LineItem类的实例。
- 储存属性,托管实例中存储自身托管属性的属性,比如LineItem实例的weight和price两个实例属性都是储存属性。
- 托管属性,托管类中由描述符实例处理的公开属性,值存储在储存属性中。
下面是Quantity描述符类和新版LineItem类,用到两个Quantity实例:


- 描述符基于协议实现,无需继承某抽象基类。
- Quantity实例中有个storage_name属性,这是托管实例中的储存属性的名称。
- 尝试为托管属性赋值时,会调用__set__方法。这里self是描述符实例(即LineItem.weight或LineItem.price),instance是托管实例(LineItem实例),value是要设定的值。
- 这里必须直接处理托管实例的__dict__属性,如果使用内置的setattr函数,会再次触发__set__方法,导致无限递归。
- 第一个描述符实例绑定给weight属性。
- 第二个描述符实例绑定给price属性。
该例中各个托管属性的名称和储存属性一样,而且读值方法不需要特殊的逻辑,所以Quantity类不需要定义__get__方法。
LineItem类第四版:自动获取储存属性的名称

如上图左边所示,我们在托管类的定义体中实例化描述符时要输入两次属性的名称,如果能像上图右边那样,自动获取储存属性的名称,即只输入一次,可以让代码更简便。
为了避免在描述符声明语句中重复输入属性名,我们将为每个Quantity实例的storage_name属性生成一个独一无二的字符串。



- __counter是Quantity类的类属性,统计Quantity实例的数量。
- cls是Quantity类的引用。
- 每个描述符实例的storage_name属性都是独一无二的,因为其值由描述符类的名称和__counter类属性的当前值构成,中间以#号隔开,如_Quantity#0。
- 递增__counter类属性的值。
- 需要实现__get__方法,因为托管属性的名称与storage_name不同。
- 使用内置的getattr函数从instance中获取储存属性的值。
- 使用内置的setattr函数把值存储在instance中,此时储存属性的名称是生成的,即Quantity中的self.storage_name属性,与托管属性的名称weight或price不同了,即我们在托管实例中打了猴子补丁,动态生成了新的属性作为储存属性。
- 现在不用把托管属性的名称传给Quanti

本文深入探讨Python的描述符机制,展示了如何使用描述符进行属性验证和管理。通过LineItem类的多个版本,阐述了描述符的工作原理,包括覆盖型和非覆盖型描述符的区别。此外,还讨论了方法作为描述符的角色,以及描述符在类和实例属性之间的交互。文章提供了关于描述符使用的一些建议,强调了正确实现描述符以提高代码效率和数据安全性的重要性。
最低0.47元/天 解锁文章
1364

被折叠的 条评论
为什么被折叠?



