- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
钻石继承变动
简而言之,对经典类而言,继承搜索程序是绝对深度优先,然后才是由左至右。Python一路往上搜索,深入树的左侧,返回后,才开始找右侧。在新式类中,在这类情况下,搜索相对来说是宽度优先的。Python先寻找第一个搜索的右侧的所有超类,然后才一路往上搜索至顶端共同的超类。换句话说,搜索过程先水平进行,然后向上移动。搜索算法也比这里介绍的更复杂一些,但是,大多数程序员了解这些就够了。
因为有这样的变动,较低超类可以重载较高超类的属性,无论它们混入的是哪种多重继承树。此外,当从多个子类访问超类的时候,新式搜索规则避免重复访问同一超类。
钻石继承例子
为了说明起见,举一个经典类构成的简单钻石继承模式的例子。这里,D是B和C的超类,B和C都导向相同的祖先A:
此处是在超类A中内找到属性的。因为对经典类来说,继承搜索是先往上搜索到最高,然后返回再往右搜索:Python会先搜索D、B、A,然后才是C(但是,当attr在A找到时,B之上的就会停止)。
这里,对于派生自object这样的内置类的新式类,以及Python 3.0中的所有类,搜索顺序是不同的:Python会先搜索C(B的右侧),然后才是A(B之上):也就是先搜索D、B、C,然后才是A(在这个例子中,则会停在C处)。
这种继承搜索流程的变化是基于这样的假设:如果在树中较低处混入C,和A相比,可能会比较想获取C的属性。此外,这也是假设C总是要覆盖A的属性:当C独立使用时,可能是真的,但是当C混入经典类钻石模式时,可能就不是这样了。当编写C时,可能根本不知道C会以这样的方式混入。
在这个例子中,很可能程序员认为C应该覆盖A,尽管如此,新式类先访问C。否则,C将会在钻石环境中基本无意义:它不会定制A,并且只对同名的C使用。
明确解决冲突
当然,假设的问题就是这是假设的。如果难以记住这种搜索顺序的偏好,或者如果你想对搜索流程有更多的控制,都可以在树中任何地方强迫属性的选择:通过赋值或者在类混合处指出你想要的变量名。
在这里,经典类树模拟了新式类的搜索顺序:在D中为属性赋值,使其挑选C中的版本,因而改变了正常的继承搜索路径(D.attr位于树中最低的位置)。新式类也能选择类混合处以上的属性来模拟经典类。
如果愿意以这样的方式解决这种冲突,大致上就能忽略搜索顺序的差异,而不依赖假设来决定所编写的类的意义。
自然,以这种方式挑选的属性也可以是方法函数(方法是正常可赋值的对象)。
在这里,我们明确在树中较低处赋值变量名以选取方法。我们也可以明确调用所需要的类。在实际应用中,这种模式可能更为常用,尤其是构造函数。
这类在混合点进行赋值运算或调用而做的选择,可以有效地把代码从类的差异性中隔离出。通过这种方式明确地解决冲突,可以确保你的代码不会因以后更新的Python版本而有所变化(除了在Python 2.6中,新式类需要从object或内置类型派生类以使用新式工具之外)。
注意:即使没有经典/新式类的差异,这种技术在一般多重继承场合中也很方便。如果你想要左侧超类的一部分以及右侧超类的一部分,可能就需要在子类中明确使用赋值语句,告诉Python要选择哪个同名属性。我们会在本章结尾的陷阱中再介绍这个概念。
此外,钻石继承模式在有些情况下的问题,比此处所提到的还要多(例如,如果B和C都有所需的构造函数会调用A中的构造器,那该怎么办呢?),由于这样的语境在Python中很罕见,已不是本书范围之内(请参见super定制函数以获得提示——除了提供对单继承树中的超类的通用性访问,super还支持一种协作模式,以解决多继承树中的一些冲突)。
搜索顺序变化的范围
总而言之,默认情况下,钻石模式对于经典类和新式类进行不同的搜索,并且这是一个非向后兼容的变化。此外要记住,这种变化主要影响到多继承的钻石模式情况。新式类继承对于大多数其他的继承树结构都是不变的工作。此外,整个问题不可能在理论上比实践中更重要,因为,新式类搜索直到Python 2.2才足够显著地解决,并且在Python 3.0中才成为标准,它不可能影响到太多的Python代码。
正如已经提到的,我还应该注意到,即便你没有在自己编写的类中用到钻石模式,由于隐式的object超类在Python 3.0中的每个类之上,所以如今多继承的每个例子都展示了钻石模式。也就是说,在新式类中,object自动扮演了我们前面所讨论的实例中类A的角色。因此,新的类搜索规则不仅修改了逻辑语义,而且通过避免多次访问相同的类而优化了性能。
同样重要的是,新模式中的隐式object超类为各种内置操作提供了默认方法,包括__str__和__repr__显示格式化方法。运行一个dir(object)来看看提供了哪些方法。没有一个新式的搜索顺序,在多继承情况中,object中的默认方法将总是覆盖用户编写的类中的重新定义,除非这些重定义总是放在最左边的超类之中。换句话会说,新类模式自身使得使用新搜索顺序更关键!
要了解Python 3.0中隐式object超类的一个更清楚示例,以及该对象所创建的钻石模式的其他示例,参见上一章lister.py示例中的ListTree类的输出,以及第28章中的classtree.py示例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论