PythonI/O进阶学习笔记_3.2面向对象编程_python的继承(多继承/super/MRO/抽象基类/mixin模式)

前言:

本篇相关内容分为3篇多态、继承、封装,这篇为第二篇 继承。

本篇内容围绕 python基础教程这段:
在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。使用对象而非全局变量和函数的原因有多个,下面列出了使用对象的最重要的好处。
 多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够正常运行。
 封装:对外部隐藏有关对象工作原理的细节。
 继承:可基于通用类创建出专用类。
内容较多,这篇为中篇。

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类型,区别在于是否会考虑继承关系。

isinstance:
 
type:
type(b)是:
由上可得,type用于判断对象,而isinstance判断是否在继承链里面。
 
3.python的super函数
a.super函数干嘛的?为什么要用super?
实际上是用来调用自己的父类的。
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。
 
b.super的用法
在python2中,super(B,self).__init__()
python3中 super().__init__()
#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

 

4.super和多继承时查找父类的顺序 MRO C3算法
a.MRO C3算法

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

b.用__init__和超类的super().__init__看子类和父类的构造函数关系。
现在有一个继承关系,SonBird继承Bird。但是子类和父类都有自己的构造函数。
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()

原理:它会查找所有的超类,以及超类的超类,直到找到所需的特性为止。

 
5.python的抽象基类(abc)
a.什么是抽象基类? 抽象基类的特性?
  抽象基类的作用类似于JAVA中的接口。在接口中定义各种方法,然后继承接口的各种类进行具体方法的实现。抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
抽象基类的特性:
- 无法用来实例化。
 
 
b.为什么要有抽象基类?直接用鸭子类型不就好了?
第一个需求:在某些情况下,我们需要判定某个对象的类型。
第二个需求:我们需要强制某个子类必须实现某些方法
 
c.抽象基类的应用
抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。
下面是一个简单的示例:使用@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。
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:
>>> k = Knigget()
>>> isinstance(k, Talker)
True 1
>>> k.talk()
Ni!

 

6.混入模式:多继承应用实例(Mixin 模式)
首先,Mixin模式在python应用中十分常见,包括openstack、django等,有大量的Mixin模式应用。想要进阶,就需要理解为什么大部分框架都会用这种模式。
详细的谷歌大神关于动态语言的设计模式可以看:http://norvig.com/design-patterns/ppframe.htm 或者《松本行弘的程序世界》里有介绍ruby的混入模式和python一样。
 
从某种程度上来说,继承强调 I am,Mixin 强调 I can。
 

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。

为了更好地看出继承关系,我们把RunnableFlyable改为RunnableMixinFlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:

class Dog(Mammal, RunnableMixin, CarnivorousMixin):
    pass

Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

Python自带的很多库也使用了Mixin。举个例子,Python自带了TCPServerUDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixinThreadingMixin提供。通过组合,我们就可以创造出合适的服务来。

比如,编写一个多进程模式的TCP服务,定义如下:

class MyTCPServer(TCPServer, ForkingMixin):
    pass

编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer, ThreadingMixin):
    pass

如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixin

class MyTCPServer(TCPServer, CoroutineMixin):
    pass

这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

 

posted @ 2019-08-28 18:01  besttr1225  阅读(643)  评论(0编辑  收藏  举报