- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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 术语表
- 作者简介
- 关于封面
9.9 覆盖类属性
Python 有个很独特的特性:类属性可用于为实例属性提供默认值。Vector2d 中有个 typecode 类属性,__bytes__ 方法两次用到了它,而且都故意使用 self.typecode 读取它的值。因为 Vector2d 实例本身没有 typecode 属性,所以 self.typecode 默认获取的是 Vector2d.typecode 类属性的值。
但是,如果为不存在的实例属性赋值,会新建实例属性。假如我们为 typecode 实例属性赋值,那么同名类属性不受影响。然而,自此之后,实例读取的 self.typecode 是实例属性 typecode,也就是把同名类属性遮盖了。借助这一特性,可以为各个实例的 typecode 属性定制不同的值。
Vector2d.typecode 属性的默认值是 'd',即转换成字节序列时使用 8 字节双精度浮点数表示向量的各个分量。如果在转换之前把 Vector2d 实例的 typecode 属性设为 'f',那么使用 4 字节单精度浮点数表示各个分量,如示例 9-13 所示。
我们在讨论如何添加自定义的实例属性,因此示例 9-13 使用的是示例 9-9 中不带 __slots__ 属性的 Vector2d 类。
示例 9-13 设定从类中继承的 typecode 属性,自定义一个实例属性
>>> from vector2d_v3 import Vector2d >>> v1 = Vector2d(1.1, 2.2) >>> dumpd = bytes(v1) >>> dumpd b'd\x9a\x99\x99\x99\x99\x99\xf1?\x9a\x99\x99\x99\x99\x99\x01@' >>> len(dumpd) # ➊ 17 >>> v1.typecode = 'f' # ➋ >>> dumpf = bytes(v1) >>> dumpf b'f\xcd\xcc\x8c?\xcd\xcc\x0c@' >>> len(dumpf) # ➌ 9 >>> Vector2d.typecode # ➍ 'd'
❶ 默认的字节序列长度为 17 个字节。
❷ 把 v1 实例的 typecode 属性设为 'f'。
❸ 现在得到的字节序列是 9 个字节长。
❹ Vector2d.typecode 属性的值不变,只有 v1 实例的 typecode 属性使用 'f'。
现在你应该知道为什么要在得到的字节序列前面加上 typecode 的值了:为了支持不同的格式。
如果想修改类属性的值,必须直接在类上修改,不能通过实例修改。如果想修改所有实例(没有 typecode 实例变量)的 typecode 属性的默认值,可以这么做:
>>> Vector2d.typecode = 'f'
然而,有种修改方法更符合 Python 风格,而且效果持久,也更有针对性。类属性是公开的,因此会被子类继承,于是经常会创建一个子类,只用于定制类的数据属性。Django 基于类的视图就大量使用了这个技术。具体做法如示例 9-14 所示。
示例 9-14 ShortVector2d 是 Vector2d 的子类,只用于覆盖 typecode 的默认值
>>> from vector2d_v3 import Vector2d >>> class ShortVector2d(Vector2d): # ➊ ... typecode = 'f' ... >>> sv = ShortVector2d(1/11, 1/27) # ➋ >>> sv ShortVector2d(0.09090909090909091, 0.037037037037037035) # ➌ >>> len(bytes(sv)) # ➍ 9
❶ 把 ShortVector2d 定义为 Vector2d 的子类,只用于覆盖 typecode 类属性。
❷ 为了演示,创建一个 ShortVector2d 实例,即 sv。
❸ 查看 sv 的 repr 表示形式。
❹ 确认得到的字节序列长度为 9 字节,而不是之前的 17 字节。
这也说明了我在 Vecto2d.__repr__ 方法中为什么没有硬编码 class_name 的值,而是使用 type(self).__name__ 获取,如下所示:
# 在Vector2d类中定义 def __repr__(self): class_name = type(self).__name__ return '{}({!r}, {!r})'.format(class_name, *self)
如果硬编码 class_name 的值,那么 Vector2d 的子类(如 ShortVector2d)要覆盖 __repr__ 方法,只是为了修改 class_name 的值。从实例的类型中读取类名,__repr__ 方法就可以放心继承。
至此,我们通过一个简单的类说明了如何利用数据模型处理 Python 的其他功能:提供不同的对象表示形式、实现自定义的格式代码、公开只读属性,以及通过 hash() 函数支持集合和映射。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论