类的继承

基本概念

在面向对象中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码,多复用,子类可以定义自己的属性和方法。

继承

class Cat(Animal)这种形式就是从父类继承,继承可以让子类从父类获取特征(属性和方法)

父类

Animal就是Cat的父类,也称为基类、超类

子类

Cat就是Animal的子类,也称为派生类

定义格式

  1. class 子类名(基类1[,基类2,...]):
  2. 语句块

如果类定义时,没有基类列表,等同于继承自object,在Python3中,object类是所有对象的根基类。

查看继承的特殊属性和方法

特殊属性和方法 含义 示例
base 查看类的基类
bases 查看类的基类元组
mro 显示方法查找顺序,基类的元组
mro()方法 同上 int.mro()
subclasses() 查看类的子类列表 int.subclasses()
  1. class Animal:
  2. def __init__(self, name):
  3. self._name = name
  4. @property
  5. def name(self):
  6. return self._name
  7. class Cat(Animal):
  8. pass
  9. tom = Cat('tom') # 实例化
  10. print(tom.name) # 通过子类访问父类的属性
  11. # tom
  12. print(Cat.__base__) # 查看子类的父类
  13. # <class '__main__.Animal'>
  14. print(Cat.__bases__) # 查看子类的父类元组
  15. # (<class '__main__.Animal'>,)
  16. print(Cat.__mro__) # 查看类的查找方式,返回元组
  17. # (<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>)
  18. print(Animal.__subclasses__()) # 查看子类的列表
  19. # [<class '__main__.Cat'>]
  • 从父类继承,子类没有的,从父类中找。
  • 私有的都是不可以访问的,但本质上依然是改了个名称放在这个属性所有类的dict_
  • 属性查找顺序:实例的__dict__ >> 类的__dict__ >> 父类的__dict__,如果没都没找到,就会抛出异常。

方法的重写、覆盖

class Animal:
    def shout(self):
        print('Animal Shout!')

class Cat(Animal):
    # 覆盖父类方法
    def shout(self):
        print(super())
        super().shout()  # 用来调用父类的一个方法
        print('Cat shout')


c = Cat()
c.shout()
# <super: <class 'Cat'>, <Cat object>>
# Animal Shout!
# Cat shout
  • 在单继承时,super().__shout__()``与Animal.__shout__()是一样的,super()避免了显示调用
  • 在多继承中,super()获取的是继承顺序中的下一个类

多继承

概念

一个类继承自多个类就是多继承,将具有多个类的特征

OCP原则:多继承,少修改

继承的用途:增强基类,实现多态

多态:在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同的表现,就是多态。

多继承的弊端

多继承引入了复杂性,可能带来二义性,例如,猫和狗都继承自动物类,现在有一个类多继承了猫和狗类,猫和狗都有shout方法,子类究竟应该继承谁的shout呢。

实现多继承的语言,要解决二义性,一般通过查找父类的顺序,深度优先或者广度优先。

21-Python面向对象-类的继承 - 图1

多继承带来路径选择问题,Python使用MRO(method resolution order)解决基类搜索问题。

MRO搜索算法

  • Python2.2之前,按照定义从左至右,深度优先策略,左图中的MRO是MyClass,D,B,A,C,A
  • Python2.2,新式类算法,重复的只保留最后一个,左图中的MRO是MyClass,D,B,C,A,Object
  • Python2.3之后,C3算法,在类被创建出来的时候,就计算出一个MRO有序列表,为Python3唯一支持的算法,左图中的MRO是MyClass,D,B,C,A,Object,C3算法旨在解决多继承的二义性。

多继承的进化

需求:

类有下面的继承关系,Document类是其他文档类的抽象基类,word、pdf类是Document的子类,需求是为Document提供打印功能。

21-Python面向对象-类的继承 - 图2

思路:

  • 在Document中提供print方法

    • 基类的方法不应该具体实现,因为它未必适合子类的打印,子类需要覆盖重写
  • 需要在打印的子类上增加

    • 如果在子类上直接增加,违反了OCP原则,所以应该继承后增加。因此有了下图

21-Python面向对象-类的继承 - 图3

方案一:使用类的继承

class Document:
    def __init__(self, content):
        self.content = content

    def print(self):
        print(self.content)

class Word(Document):  # 继承
    pass

class PrintableWord(Word): # 继承后增加功能
    def print(self):
        print('Word print {}'.format(self.content))

class Pdf(Document):
    def print(self):
        pass

print(PrintableWord.mro())
word = PrintableWord('test abc')
word.print()
# [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
# Word print test abc
  • 使用上述方法会发现,如果需要的功能很多,如序列化、网络传输,序列化又分为pickle、json等,使用上述方式实现,需要的类太多了,继承的方式就很繁琐了。

方案二:使用装饰器

用装饰器增强一个类,把功能给附加上去,哪个类需要,就装饰哪个类

def printable(cls):
    def _print(self):
        print(self.content, '装饰器')
    cls.echo = _print
    return cls

class Document: # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content

class Word(Document): pass
class Pdf(Document): pass

@printable # 先继承,再装饰
class PrintableWord(Word): pass

print(PrintableWord.mro())
# [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
print(PrintableWord.__dict__)
# {'__module__': '__main__', '__doc__': None, 'echo': <function printable.<locals>._print at 0x000002C67B6AB510>}
pw = PrintableWord('test word content')
pw.echo()
# test word content 装饰器
  • 此方式的优点是在需要的地方动态增加,直接使用装饰器。

方案三:使用Mixin混合类

class PrintableMixin:
    def print(self):
        print(self.content, 'Mixin')

class SuperPrintableMixin(PrintableMixin): # 使用混合类对PrintableMixin重写
    def print(self):
        print('~'*20)
        super().print()
        print('~'*20)

class Document:
    def __init__(self, content):
        self.content = content

    def print(self):
        print(self.content, 'Document')  # 根据mro顺序,下面的调用就不会再查找Document中的print方法

class Word(Document): pass
class Pdf(Document): pass

class PrintableWord(SuperPrintableMixin, Word): pass # 此处Mixin类PrintableMixin需放置在左边

print(PrintableWord.mro())
pw = PrintableWord('test word')
pw.print()
  • Mixin本质上就是多继承实现的,体现的是一种组合的设计模式
  • 在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能又来自不同的类提供,这就需要很多类组合在一起。实现多组合,少继承
  • Mixin类的使用原则

    • Mixin类中不应该显式出现__init__初始化方法
    • Mixin类通常不能独立工作,因为它是准备混入别的类的部分功能实现
    • Mixin类的祖先类也是Mixin类
  • 使用时,Mixin类通常在继承列表的第一个位置,例如class PrintableWord(SuperPrintableMixin, Word)