Python编程:继承的优缺点

1.子类化内置类型很麻烦

        内置类型可以子类化,但是有个重要的注意事项:内置类型不会调用用户定义的类覆盖的特殊方法。下面用例子进行简要介绍。

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

d = DoppelDict(one=1)
d['two'] = 2
d.update(three=3)
print(d)

        DoppelDict._setitem_方法会重复存入的值。它把职责委托给超类。通过运行代码可以发现,继承自dict的_init_方法显然忽略了我们覆盖的_setitem_方法,‘one’值并没有重复。但当我们使用[]号赋值时,会按我们预期的那样重复存入的值。同时,也会发现继承自dict的update方法也不会使用我们覆盖的_setitem_方法。那么,为什么会出现这种现象?

        是因为原生对象的这种行为违背了面向对象编程的一个基本原则:始终应该从实例(self)所属的类开始搜索方法,即便是在超类实现的类中调用也是如此。不只实例内部的调用有这个问题,内置类型的方法调用的其他类的方法,如果被覆盖了,也不会被调用。

        综上,本节所述的问题,只发生在C语言实现的内置类型内部的方法委托上,而且只影响直接继承内置类型的用户自定义类。如果子类化使用Python编写的类,如UserDict或MutableMapping,就不会受此影响。

2.多重继承和方法解析顺序

        任何实现多重继承语言都要处理潜在的命名冲突,这种冲突是由不相关的祖先类实现同名方法引起的。这种冲突称为“菱形问题”。下面通过一个简单的实例进行简要的介绍。

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 = D()
print(d.pong())
print(C.pong(d))

        上段代码中,B和C都实现了pong方法,二者之间的唯一区别是,C.pong方法实现的是大写的PONG。运行上段代码会发现,d.pong调用的是B类中的版本。此外,超类中方法都可以直接调用,不过此时要把实例作为显式参数传入。

        Python为什么会运行B类中的版本呢?这是因为Python会按特定的顺序遍历继承图。这个顺序叫方法解析顺序。类都有一个名为_mro_的属性,它的值是一个元组,按照方法解析顺序列出每个超类,从当前类一直向上,直到object类。运行D._mro_会得到下面的结果。

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

        上面的结果,显示了Python遍历D类的顺序,若想把方法调用委托给超类,推荐的方式是使用内置的super()函数。

3.处理多重继承

        继承有多种用途,而多重继承增加了可选方案和复杂度。使用多重继承容易得出令人费解和错误的设计。下面是给出的一些建议。

(1)把接口继承和实现继承区分开。使用多重继承时,一定要明确一开始为什么创建子类。主要的原因可能有:继承接口,创建子类型,实现“是什么”关系;继承实现,通过重用避免代码重复。一般情况下,二者可能重复出现,不过只要可能,一定要明确意图。通过继承重用代码,是实现细节,通常可以换用组合和委托模式。而接口继承则是框架的支柱。

(2)使用抽象基类显式表示接口。如果类的作用是定义接口,应该明确把它定义为抽象基类。

(3)通过混入重用代码。如果一个类的作用是为多个不相关的子类提供实现方法,从而实现重用,但不体现“是什么”关系,应该把这个类明确定义为混入类。从概念上讲,混入类不定义新类型,只是打包方法,便于重用。此外,混入类绝对不能实例化,而且具体类不能只继承混入类。

(4)在名称中明确指明混入。在Python中,推荐使用...Mixin后缀把类声明为混入。

(5)抽象基类可以作为混入,但是反过来不成立。抽象基类可以实现具体的方法,因此,可以作为混入来使用。不过,抽象基类会定义类型,而混入做不到。

(6)不要子类化多个具体类。具体类可以没有,或最多只有一个具体超类。也就是说,具体类的超类除了这一个具体超类之外,其余的都是抽象基类或混入。

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

(8)优先使用对象组合,而不是类继承。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值