PythonI/O进阶学习笔记_3.2面向对象编程_python的继承(多继承/super/MRO/抽象基类/mixin模式)
前言:
本篇相关内容分为3篇多态、继承、封装,这篇为第二篇 继承。
Content:
- 继承
1. 什么是继承,继承的作用和常用状态?
2. python的多继承、instance和type?
3. python中的super函数
4. python的MRO查找机制来对应多继承和super
5. python的抽象基类
6. django等大大框架和python源码中最常用的Mixin模式多继承实例
(下篇
- 封装
1.数据封装和私有属性
2. 类变量和实例变量(对象变量)
3. 类属性和实例属性得查找顺序(MRO)
4. 静态方法 类方法和对象方法使用以及参数
5. python的接口和自省机制
6. 上下文管理器
)
一 继承
1.什么是继承?继承有哪些作用?常用?
- 继承的概念 :在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
//INPUT:
class Animals(): def say(self): print("say something") def eat(self): print("eat something") class Duck(Animals): def say(self): print("gaga") class Dog(Animals): def say(self): print("wangwang") class Miao(Animals): def say(self): print("miaomiao") for i in [Duck(),Dog(),Miao()]: i.eat()
//OUTPUY:
eat something
eat something
eat something
- 继承的作用:可以解决代码重用的问题。
- 比较常用的应该就是在python相关web框架里这种比较比较复杂的框架,和许多第三方库的类。天知道这些大牛的框架里面默默有效率的帮我们做了多少事- -。天知道我们直接用他们的方法有多方便。
2.python多继承需要注意哪些?用instance和type来判断python类继承的关系。
a.多继承需要注意的地方:
- 类继承的顺序和方法在多继承类中查找的顺序
--.子类继承父类时,在子类进行属性调用的顺序为:先查找自己的属性字典,若自己的属性字典中无该属性,则会依次按照继承父类的顺序来依次查找父类的属性字典;
--.子类继承父类,当父类和子类均有相同的属性时,子类并不会影响父类的属性。
总结起来就是:按继承的顺序来依次查询属性,一旦查到则停止;子类和父类的属性相互独立,互不影响;子类可以调用父类的属性,反之不行;
b.用instance和type来判断两个类的关系
instance和type都是用来判断参数1是否是参数2类型,区别在于是否会考虑继承关系。
#Python3.x 实例: class A: def add(self, x): y = x+1 print(y) class B(A): def add(self, x): super().add(x) b = B() b.add(2) # 3
#Python2.x 实例: #!/usr/bin/python # -*- coding: UTF-8 -*- class A(object): # Python2.x 记得继承 object def add(self, x): y = x+1 print(y) class B(A): def add(self, x): super(B, self).add(x) b = B() b.add(2) # 3
C3线性是用于获取多重继承下继承顺序的一种算法。通常,被称为方法解析顺序,即MRO(method resolution order)。
算法的名字“C3”并不是缩写,而是指该算法的三大重要属性:
- 前趋图。作为有向无环图,找不到任何的循环,通常用前趋图来理解程序的依赖关系。
- 保持局部的优先次序。
- 单调性。
C3是1996年首次被提出。在python2.3及后续版本中,C3被选定为默认的解析算法。
一个类的C3线性表,是由两部分进行merge操作得到的,第一部分是是它所有父类的C3线性表(parents' linearizations),第二部分是它所有父类组成的列表(parents list)。后者其实是局部的优先级列表。
在C3被应用之前,广度优先和深度优先是被应用于解析顺序的。
C3算法计算方法:有点类似拓扑排序(有向图) 想深刻了解 C3 还是自己查吧...下面有段简单的多继承代码和其对应的拓扑排序的抽象图 (所用代码实例和图片均来应用自别处,文章末尾有链接)
class D(object): pass class E(object): pass class F(object): pass class C(D, F): pass class B(E, D): pass class A(B, C): pass if __name__ == '__main__': print A.__mro__
得到的输出结果:
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <type 'object'>)
-
下面就是抽象出来的图
我们就用拓扑排序来分析,但是这里会碰到同时存在好几个点都是入度为0 (说人话,就是没有被别人指向的点),这时按照树的排序来,即从左到右,从根到叶,这里 A 就是根。
所以具体情况就是:我们先找到了点 A只有它没有被别人指向,输出A
;去掉A及其伸出的两条线,剩 B 和 C 点同时满足只指向别人,按照树的顺序从左到右,故先输出B
;去掉线剩 E 和 C ,输出E
;去线剩 C,输出C
;去线剩 D 和 F ,输出D
;去线只剩F ,输出F
;最后输出object
;得到的输出结果: A B E C D F object
对比系统打印出的结果,顺序是一致的。这样下次你就可以装逼地手动计算多继承时调用类的顺序了
以上结论来源链接:https://www.jianshu.com/p/6651ed35104c
class Bird: def __init__(self): self.hungry = True def eat(self): if self.hungry: print 'Ahahahah' else: print 'No thanks!' class SongBird(Bird): def __init__(self): self.sound = 'Squawk' def sing(self): print self.song() sb = SongBird() sb.sing() # 能正常输出 sb.eat() # 报错,因为 songgird 中没有 hungry 特性
这时候,如果需要继承父类构造函数里的属性,其实是可以有两种方法的。
第一种 - 调用未绑定的超类构造方法(多用于旧版 python 阵营)
class SongBird(Bird): def __init__(self): Bird.__init__(self) self.sound = 'Squawk' def sing(self): print self.song()
原理:在调用了一个实例的方法时,该方法的self参数会自动绑定到实例上(称为绑定方法);如果直接调用类的方法(比如Bird.__init__),那么就没有实例会被绑定,可以自由提供需要的self参数(未绑定方法)。
第二种 - 使用super函数(只在新式类中有用)
class SongBird(Bird): def __init__(self): super(SongBird,self).__init__() self.sound = 'Squawk' def sing(self): print self.song()
原理:它会查找所有的超类,以及超类的超类,直到找到所需的特性为止。
from abc import ABC, abstractmethod class Talker(ABC): @abstractmethod def talk(self): pass
结果:
##抽象基类不能被实例化
>>> Talker() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Talker with abstract methods talk
##没有重写方法的时候,继承抽象基类的类 本质也是抽象类
class Knigget(Talker): pass ##由于没有重写方法talk,因此这个类也是抽象的,不能实例化。如果你试图这样做,将出现类似于前面的错误消息。
###继承后重写了方法就没什么问题
class Knigget(Talker): def talk(self): print("Ni!")
##output:
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为Mixin。
为了更好地看出继承关系,我们把Runnable
和Flyable
改为RunnableMixin
和FlyableMixin
。类似的,你还可以定义出肉食动物CarnivorousMixin
和植食动物HerbivoresMixin
,让某个动物同时拥有好几个Mixin:
class Dog(Mammal, RunnableMixin, CarnivorousMixin):
pass
Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。
Python自带的很多库也使用了Mixin。举个例子,Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixin
和ThreadingMixin
提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixin):
pass
编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixin
:
class MyTCPServer(TCPServer, CoroutineMixin):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。