前言:
本篇相关内容分为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:
classAnimals():defsay(self):print("say something")defeat(self):print("eat something")classDuck(Animals):defsay(self):print("gaga")classDog(Animals):defsay(self):print("wangwang")classMiao(Animals):defsay(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 实例:
classA:defadd(self, x):
y= x+1
print(y)classB(A):defadd(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
defadd(self, x):
y= x+1
print(y)classB(A):defadd(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 还是自己查吧...下面有段简单的多继承代码和其对应的拓扑排序的抽象图 (所用代码实例和图片均来应用自别处,文章末尾有链接)
classD(object):pass
classE(object):pass
classF(object):pass
classC(D, F):pass
classB(E, D):pass
classA(B, C):pass
if __name__ == '__main__':print A.__mro__
得到的输出结果:
(, , , , , , )
下面就是抽象出来的图
我们就用拓扑排序来分析,但是这里会碰到同时存在好几个点都是入度为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。但是子类和父类都有自己的构造函数。
classBird:def __init__(self):
self.hungry=Truedefeat(self):ifself.hungry:print 'Ahahahah'
else:print 'No thanks!'
classSongBird(Bird):def __init__(self):
self.sound= 'Squawk'
defsing(self):printself.song()
sb=SongBird()
sb.sing()#能正常输出
sb.eat() #报错,因为 songgird 中没有 hungry 特性
这时候,如果需要继承父类构造函数里的属性,其实是可以有两种方法的。
第一种 - 调用未绑定的超类构造方法(多用于旧版 python 阵营)
classSongBird(Bird):def __init__(self):
Bird.__init__(self)
self.sound= 'Squawk'
defsing(self):print self.song()
原理:在调用了一个实例的方法时,该方法的self参数会自动绑定到实例上(称为绑定方法);如果直接调用类的方法(比如Bird.__init__),那么就没有实例会被绑定,可以自由提供需要的self参数(未绑定方法)。
第二种 - 使用super函数(只在新式类中有用)
classSongBird(Bird):def __init__(self):
super(SongBird,self).__init__()
self.sound= 'Squawk'
defsing(self):print self.song()
原理:它会查找所有的超类,以及超类的超类,直到找到所需的特性为止。
5.python的抽象基类(abc)
a.什么是抽象基类? 抽象基类的特性?
抽象基类的作用类似于JAVA中的接口。在接口中定义各种方法,然后继承接口的各种类进行具体方法的实现。抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
抽象基类的特性:
- 无法用来实例化。
b.为什么要有抽象基类?直接用鸭子类型不就好了?
第一个需求:在某些情况下,我们需要判定某个对象的类型。
第二个需求:我们需要强制某个子类必须实现某些方法
c.抽象基类的应用
抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。
下面是一个简单的示例:使用@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。
from abc importABC, abstractmethodclassTalker(ABC):
@abstractmethoddeftalk(self):pass
结果:
##抽象基类不能被实例化
>>>Talker()
Traceback (most recent call last):
File"", line 1, in TypeError: Can't instantiate abstract class Talker with abstract methods talk
##没有重写方法的时候,继承抽象基类的类 本质也是抽象类
classKnigget(Talker):pass##由于没有重写方法talk,因此这个类也是抽象的,不能实例化。如果你试图这样做,将出现类似于前面的错误消息。
###继承后重写了方法就没什么问题
classKnigget(Talker):deftalk(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。
为了更好地看出继承关系,我们把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
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。