流畅的python笔记(十二)继承的优缺点

Python多重继承与混入实践:避免菱形难题与代码复用

目录

一、子类化内置类型很麻烦

二、多重继承和方法解析顺序

三、多重继承的真实应用

四、处理多重继承

五、Django通用视图中的混入


一、子类化内置类型很麻烦

python2.2之前,内置类型不能子类化。python2.2以后内置类型可以子类化了,但是内置类型的方法不会调用子类覆盖的方法,比如下边例子,用继承的内置类型的方法无法调用覆盖的内置类型的方法。

  1. DoppelDict继承内置的dict类型,它会把存入的值重复一下,即[1, 2]变成[1, 2, 1, 2],并且具体的实现委托给超类的__setitem__方法。
  2. 这里one的值没有重复,DoppelDict的__init__方法是继承自dict的,显然__init__忽略了我们覆盖的__setitem__方法。
  3. [ ] 运算符调用了我们覆盖的__setitem__方法,成功地让two对应了两个重复的值[2, 2]。
  4. three的值没有重复,即继承自dict的update方法也不使用我们覆盖的__setitem__方法。

内置类型的这种行为违背了面向对象编程的一个基本原则:始终应该从实例所属的类开始搜索方法。

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

  1. 不管传入什么键,__getitem__的返回值都是42。
  2. ad是AnswerDict对象(继承自dict),这里传入a='foo',但是由于AnswerDict的__getitem__返回值永远是42,因此a的值是42,符合预期。
  3. 如2所说。
  4. d是一个dict对象,用ad中的值来更新d,用的是d中的方法update
  5. a的值变成了‘foo’,说明dict对象d的方法update忽略了AnswerDict.__getitem__方法。

以上两个例子均说明,基本类型的方法无法调用基本类型的子类所覆盖的方法

        综上,不要直接子类化内置类型,因为内置类型的方法通常会忽略用户覆盖的方法,用户自定义的类应该继承collections模块中的类,如UserDict、UserList、UserString,这些类做了特殊设计,易于扩展。把上边例子中继承自dict改成继承自UserDict则可以按预期使用。

二、多重继承和方法解析顺序

菱形继承问题:由不相关的祖先类实现同名方法而引起的命名冲突。

class A:
    def ping(self):
        print('ping:', self)

class B(A):
    def pong(self):
        print('pong:', self)

class C(A):
    def pong(self):
        print('PONG: ', self)

class D(B, C):

    def ping(self):
        super().ping()
        print('post-ping:', self)

    def pingpong(self):
        self.ping()
        super().ping()
        self.pong()
        super.pong()
        C.pong(self)

在D实例上调用pong方法有两种方式:

  1. 直接调用d.pong
  2. 直接用超类名调用实例方法pong,比如C.pong(d)或者D.pong(d),因为pong在类C和类D中都是实例方法而不是类方法,因此是不能直接用类名调用的,必须显示传入对应的实例d。

第二种方法直接用父类的名称来调用就不用说了,肯定能准确调用。第一种方法中python能区分d.pong()调用的是哪个方法,是因为python会按照方法解析顺序(Method Resoluton Order, MRO)来遍历继承图。类都有一个名为__mro__的类属性,它的值是要给元组,按照方法解析顺序列出各个超类,从当前类一直列到object类。方法解析顺序会考虑继承列表中的超类顺序,比如D(B, C)和D(C, B)中方法解析顺序C和B就是反过来的,下面是类D的方法解析顺序。

如果想把方法调用委托给超类,推荐方式是使用内置的super()函数,super()函数也会按照方法解析顺序来找到具体调用的方法,使用super()方法是最安全的。

三、多重继承的真实应用

略。

四、处理多重继承

  1. 把接口继承和实现继承区分开,继承接口,是为了实现“是什么”的关系,即实现某个功能,继承实现,则是为了代码重用。
  2. 使用抽象基类显示表示接口。
  3. 通过混入重用代码。如果一个类的作用是为多个不相关的子类提供方法实现,从而实现重用,但不体现“是什么”关系(即不是为了提供接口),应该把那个类明确定义为混入类,混入类只是为了打包方法,便于重用,绝对不能实例化,且具体类不能只继承混入类,混入类应该提供某方面的特定行为,只实现少量关系非常紧密的方法。
  4. 在名称中明确指明混入。即在类名中加入Mixin后缀。
  5. 抽象基类可以作为混入,反之不行。因为抽象基类可以实现具体方法(虽然抽象基类中的具体方法只能与抽象基类及其超类中的方法协作),因此可以作为混入使用。抽象基类可以作为其他类的唯一基类,而混入不能作为唯一的超类,除非继承另一个更具体的混入,但一般不这么做。
  6. 不要子类化多个具体类。即在超类列表中最多有一个超类是具体类,也可以没有。
  7. 为用户提供聚合类。如果抽象基类或混入的组合对客户代码非常有用,那就提供一个类,使用易于理解的方式将它们结合起来,这种类叫聚合类。
  8. 优先使用对象组合,而不是类继承。

五、Django通用视图中的混入

略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值