《Fluent Python》CH.12_面向对象_继承的优缺点 (菱形继承问题、不靠谱的内置类型的继承、不要使用多重继承)

2021-03-01 01:28

阅读:427

标签:ons   绕过   spl   lan   实例化   pos   int   多重   org   

CH.12_面向对象_继承的优缺点

本章探讨继承和子类化,重点是说明对 Python 而言尤为重要的两个细节:

  • 子类化内置类型的缺点
  • 多重继承和方法解析顺序

12.1 子类化内置类型很麻烦

在 Python 2.2 之前,内置类型(如 list 或 dict)不能子类化。在 Python 2.2 之后,内置类型可以子类化了,但是有个重要的注意事项: 内置类型(使用 C 语言编写)不会调用用户定义的类覆盖的特殊方法。

基本上,内置类型的方法不会调用子类覆盖的方法。

原生类型的这种行为违背了面向对象编程的一个基本原则:始终应该从 实例(self)所属的类开始搜索方法,即使在超类实现的类中调用也是 如此。在这种糟糕的局面中,__missing__ 方法(参见 3.4.2 节)却能 按预期方式工作,不过这只是特例。

不只实例内部的调用有这个问题(self.get() 不调用 self.getitem()),内置类型的方法调用的其他类的方法,如果 被覆盖了,也不会被调用。

class DoppelDict(dict):
    def __setitem__(self, key, value):
        super().__setitem__(key, [value] * 2)

dd = DoppelDict(one=1)
dd
{‘one‘: 1}

上面的init方法并没有使用我们的setitem()方法

dd[‘two‘] = 2
dd
{‘one‘: 1, ‘two‘: [2, 2]}
dd.update(three=3)
dd
{‘one‘: 1, ‘two‘: [2, 2], ‘three‘: 3}

继承自 dict 的 update 方法也不使用我们覆盖的 setitem 方 法:‘three‘ 的值没有重复。

内置类型的方法调用的其他类的方法,如果 被覆盖了,也不会被调用

class AnswerDict(dict):
    def __getitem__(self, item):
        return 42
ad = AnswerDict(a=‘foo‘)
ad[‘a‘] # 这里调用的子类方法
42
ac = {‘b‘:‘233‘}
ac.update(ad)
ac
{‘b‘: ‘233‘, ‘a‘: ‘foo‘}

d 是 dict 的实例,使用 ad 中的值更新 d。

dict.update 方法忽略了 AnswerDict.__getitem__ 方法。

如需继承内置类型,进行子类化UserDict、UserList 和 UserString

直接子类化内置类型(如 dict、list 或 str)容易出错, 因为内置类型的方法通常会忽略用户覆盖的方法。不要子类化内置 类型,用户自己定义的类应该继承 collections 模块 (http://docs.python.org/3/library/collections.html)中的类,例如 UserDict、UserList 和 UserString,这些类做了特殊设计,因 此易于扩展。

12.2 多重继承和方法解析顺序

这种冲突称为“菱形问题”:python的方法是逐层向上、从左到右依次查找方法来进行解析。

在 C++ 中,程序员必须使用类名限定方法调用来避免这种歧义。
如下:

void Derived::display(){
    BaseA::show();  //调用BaseA类的show()函数
    BaseB::show();  //调用BaseB类的show()函数
    cout

在 Python 3 中,这种方式变得更容易了,如示例 12-4 中 D 类的 pingpong 方法所示。4 然而,有时可能需要绕过方法解析顺序,直接 调用某个超类的方法——这样做有时更方便。例如,D.ping 方法可以 这样写:

def ping(self):
    A.ping(self) # 而不是super().ping()
    print(‘post-ping:‘, self)

12.3 多重继承的真实应用

《设计模式:可复用面向对象软件的基础》 一书中的适配器模式用的就是多重继承,因此使用多重继承肯定没有错 (那本书中的其他 22 个设计模式都使用单继承,因此多重继承显然不 是灵丹妙药)。

在 Python 标准库中,最常使用多重继承的是 collections.abc 包。

使用__mro__ 获得python的父类信息

def print_mro(x):
    return x.__mro__
import tkinter
print_mro(tkinter.Toplevel)


(tkinter.Toplevel, tkinter.BaseWidget, tkinter.Misc, tkinter.Wm, object)

12.4 处理多重继承

01. 把接口继承和实现继承区分开

使用多重继承时,一定要明确一开始为什么创建子类。主要原因可 能有:

  • 继承接口,创建子类型,实现“是什么”关系
  • 继承实现,通过重用避免代码重复 其实这两条经常同时出现,不过只要可能,一定要明确意图。

通过 继承重用代码是实现细节,通常可以换用组合和委托模式。而接口继承则是框架的支柱。

02. 使用抽象基类显式表示接口

现代的 Python 中,如果类的作用是定义接口,应该明确把它定义为 抽象基类。Python 3.4 及以上的版本中,我们要创建 abc.ABC 或其 他抽象基类的子类(如果想支持较旧的 Python 版本,参见 11.7.1 节)。

03. 通过混入重用代码

如果一个类的作用是为多个不相关的子类提供方法实现,从而实现 重用,但不体现“是什么”关系,应该把那个类明确地定义为混入类 (mixin class)。从概念上讲,混入不定义新类型,只是打包方 法,便于重用。混入类绝对不能实例化,而且具体类不能只继承混 入类。混入类应该提供某方面的特定行为,只实现少量关系非常紧 密的方法。

04. 在名称中明确指明混入

06. 不要子类化多个具体类

具体类可以没有,或最多只有一个具体超类。

07. 为用户提供聚合类

如果抽象基类或混入的组合对客户代码非常有用,那就提供一个 类,使用易于理解的方式把它们结合起来。Grady Booch 把这种类 称为聚合类(aggregate class)。

08. “优先使用对象组合,而不是类继承”

有道理

《Fluent Python》CH.12_面向对象_继承的优缺点 (菱形继承问题、不靠谱的内置类型的继承、不要使用多重继承)

标签:ons   绕过   spl   lan   实例化   pos   int   多重   org   

原文地址:https://www.cnblogs.com/zhazhaacmer/p/14454290.html

上一篇:java标识符和数据类型

下一篇:树状数组


评论


亲,登录后才可以留言!