- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
20.2 覆盖型与非覆盖型描述符对比
如前所述,Python 存取属性的方式特别不对等。通过实例读取属性时,通常返回的是实例中定义的属性;但是,如果实例中没有指定的属性,那么会获取类属性。而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类。
这种不对等的处理方式对描述符也有影响。其实,根据是否定义 __set__ 方法,描述符可分为两大类。若想观察这两类描述符的行为差异,则需要使用几个类。我们将使用示例 20-8 中的代码作为接下来几节的试验台。
在示例 20-8 中,每个 __get__ 和 __set__ 方法都调用了 print_args 函数,使调用方式易于阅读。没必要深入理解 print_args 函数及辅助函数 cls_name 和 display,因此不要花心思研究它们。
示例 20-8 descriptorkinds.py:几个简单的类,用于研究描述符的覆盖行为
### 辅助函数,仅用于显示 ### def cls_name(obj_or_cls): cls = type(obj_or_cls) if cls is type: cls = obj_or_cls return cls.__name__.split('.')[-1] def display(obj): cls = type(obj) if cls is type: return '<class {}>'.format(obj.__name__) elif cls in [type(None), int]: return repr(obj) else: return '<{} object>'.format(cls_name(obj)) def print_args(name, *args): pseudo_args = ', '.join(display(x) for x in args) print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args)) ### 对这个示例重要的类 ### class Overriding: ➊ """也称数据描述符或强制描述符""" def __get__(self, instance, owner): print_args('get', self, instance, owner) ➋ def __set__(self, instance, value): print_args('set', self, instance, value) class OverridingNoGet: ➌ """没有``__get__``方法的覆盖型描述符""" def __set__(self, instance, value): print_args('set', self, instance, value) class NonOverriding: ➍ """也称非数据描述符或遮盖型描述符""" def __get__(self, instance, owner): print_args('get', self, instance, owner) class Managed: ➎ over = Overriding() over_no_get = OverridingNoGet() non_over = NonOverriding() def spam(self): ➏ print('-> Managed.spam({})'.format(display(self)))
❶ 有 __get__ 和 __set__ 方法的典型覆盖型描述符。
❷ 在这个示例中,各个描述符的每个方法都调用了 print_args 函数。
❸ 没有 __get__ 方法的覆盖型描述符。
❹ 没有 __set__ 方法,所以这是非覆盖型描述符。
❺ 托管类,使用各个描述符类的一个实例。
❻ spam 方法放在这里是为了对比,因为方法也是描述符。
在接下来的几节中,我们要分析对 Managed 类及其实例做属性读写时的行为,还会讨论所定义的各个描述符。
20.2.1 覆盖型描述符
实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。示例 20-2 就是这样实现的。特性也是覆盖型描述符:如果没提供设值函数,property 类中的 __set__ 方法会抛出 AttributeError 异常,指明那个属性是只读的。我们可以使用示例 20-8 中的代码测试覆盖型描述符的行为,如示例 20-9 所示。
示例 20-9 覆盖型描述符的行为,其中 obj.over 是 Overriding 类(见示例 20-8)的实例
>>> obj = Managed() ➊ >>> obj.over ➋ -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) >>> Managed.over ➌ -> Overriding.__get__(<Overriding object>, None, <class Managed>) >>> obj.over = 7 ➍ -> Overriding.__set__(<Overriding object>, <Managed object>, 7) >>> obj.over ➎ -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) >>> obj.__dict__['over'] = 8 ➏ >>> vars(obj) ➐ {'over': 8} >>> obj.over ➑ -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)
❶ 创建供测试使用的 Managed 对象。
❷ obj.over 触发描述符的 __get__ 方法,第二个参数的值是托管实例 obj。
❸ Managed.over 触发描述符的 __get__ 方法,第二个参数(instance)的值是 None。
❹ 为 obj.over 赋值,触发描述符的 __set__ 方法,最后一个参数的值是 7。
❺ 读取 obj.over,仍会触发描述符的 __get__ 方法。
❻ 跳过描述符,直接通过 obj.__dict__ 属性设值。
❼ 确认值在 obj.__dict__ 属性中,在 over 键名下。
❽ 然而,即使是名为 over 的实例属性,Managed.over 描述符仍会覆盖读取 obj.over 这个操作。
20.2.2 没有 __get__ 方法的覆盖型描述符
通常,覆盖型描述符既会实现 __set__ 方法,也会实现 __get__ 方法,不过也可以只实现 __set__ 方法,如示例 20-1 所示。此时,只有写操作由描述符处理。通过实例读取描述符会返回描述符对象本身,因为没有处理读操作的 __get__ 方法。如果直接通过实例的 __dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法插手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符对象。也就是说,实例属性会遮盖描述符,不过只有读操作是如此。参见示例 20-10。
示例 20-10 没有 __get__ 方法的覆盖型描述符,其中obj.over_no_get 是 OverridingNoGet 类(见示例 20-8)的实例
>>> obj.over_no_get ➊ <__main__.OverridingNoGet object at 0x665bcc> >>> Managed.over_no_get ➋ <__main__.OverridingNoGet object at 0x665bcc> >>> obj.over_no_get = 7 ➌ -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7) >>> obj.over_no_get ➍ <__main__.OverridingNoGet object at 0x665bcc> >>> obj.__dict__['over_no_get'] = 9 ➎ >>> obj.over_no_get ➏ 9 >>> obj.over_no_get = 7 ➐ -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7) >>> obj.over_no_get ➑ 9
❶ 这个覆盖型描述符没有 __get__ 方法,因此,obj.over_no_get 从类中获取描述符实例。
❷ 直接从托管类中读取描述符实例也是如此。
❸ 为 obj.over_no_get 赋值会触发描述符的 __set__ 方法。
❹ 因为 __set__ 方法没有修改属性,所以在此读取 obj.over_no_get 获取的仍是托管类中的描述符实例。
❺ 通过实例的 __dict__ 属性设置名为 over_no_get 的实例属性。
❻ 现在,over_no_get 实例属性会遮盖描述符,但是只有读操作是如此。
❼ 为 obj.over_no_get 赋值,仍然经过描述符的 __set__ 方法处理。
❽ 但是读取时,只要有同名的实例属性,描述符就会被遮盖。
20.2.3 非覆盖型描述符
没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。方法是以非覆盖型描述符实现的。示例 20-11 展示了对一个非覆盖型描述符的操作。
示例 20-11 非覆盖型描述符的行为,其中 obj.non_over 是 NonOverriding 类(见示例 20-8)的实例
>>> obj = Managed() >>> obj.non_over ➊ -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>) >>> obj.non_over = 7 ➋ >>> obj.non_over ➌ 7 >>> Managed.non_over ➍ -> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>) >>> del obj.non_over ➎ >>> obj.non_over ➏ -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)
❶ obj.non_over 触发描述符的 __get__ 方法,第二个参数的值是 obj。
❷ Managed.non_over 是非覆盖型描述符,因此没有干涉赋值操作的 __set__ 方法。
❸ 现在,obj 有个名为 non_over 的实例属性,把 Managed 类的同名描述符属性遮盖掉。
❹ Managed.non_over 描述符依然存在,会通过类截获这次访问。
❺ 如果把 non_over 实例属性删除了……
❻ 那么,读取 obj.non_over 时,会触发类中描述符的 __get__ 方法;但要注意,第二个参数的值是托管实例。
Python 贡献者和作者讨论这些概念时会使用不同的术语。覆盖型描述符也叫数据描述符或强制描述符。非覆盖型描述符也叫非数据描述符或遮盖型描述符。
在上述几个示例中,我们为几个与描述符同名的实例属性赋了值,结果依描述符中是否有 __set__ 方法而有所不同。
依附在类上的描述符无法控制为类属性赋值的操作。其实,这意味着为类属性赋值能覆盖描述符属性,正如下一节所述的。
20.2.4 在类中覆盖描述符
不管描述符是不是覆盖型,为类属性赋值都能覆盖描述符。这是一种猴子补丁技术,不过在示例 20-12 中,我们把描述符替换成了整数,这其实会导致依赖描述符的类不能正确地执行操作。
示例 20-12 通过类可以覆盖任何描述符
>>> obj = Managed() ➊ >>> Managed.over = 1 ➋ >>> Managed.over_no_get = 2 >>> Managed.non_over = 3 >>> obj.over, obj.over_no_get, obj.non_over ➌ (1, 2, 3)
❶ 为后面的测试新建一个实例。
❷ 覆盖类中的描述符属性。
❸ 描述符真的不见了。
示例 20-12 揭示了读写属性的另一种不对等:读类属性的操作可以由依附在托管类上定义有 __get__ 方法的描述符处理,但是写类属性的操作不会由依附在托管类上定义有 __set__ 方法的描述符处理。
若想控制设置类属性的操作,要把描述符依附在类的类上,即依附在元类上。默认情况下,对用户定义的类来说,其元类是 type,而我们不能为 type 添加属性。不过在第 21 章,我们会自己创建元类。
下面我们调转话题,分析 Python 是如何使用描述符实现方法的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论