面向对象之继承与C3算法
面向对象之继承与C3算法
一 继承介绍
1 什么是继承
继承是一种新建类的方式,新建的类称之为子类,被继承的类称之为父类、基类、超类。
python支持多继承。
#####——————————#####
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承
pass
class SubClass2(ParentClass1,ParentClass2): #多继承
pass
# 通过类的内置属性__ bases__可以查看继承类的所有父类。
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
2 为何要继承
由于子类会遗传父类的属性,所以继承是用来解决类与类之间代码冗余问题的方案。
3 如何实现继承
#####——————————##### 继承案例,解决了部分代码冗余问题
class OldboyPeople:
school = "oldboy"
class Student(OldboyPeople):
def __init__(self, name, age, gender, stud_id, course):
self.name = name
self.age = age
self.gender = gender
self.stu_id = stud_id
self.course = course
def choose(self):
print('%s 正在选课' % self.name)
class Teacher(OldboyPeople):
def __init__(self, name, age, gender, salary, level):
self.name = name
self.age = age
self.gender = gender
self.salary = salary
self.level = level
def score(self, stu, num):
stu.num = num
stu1 = Student("艾利克斯", 73, 'male', 1001, "python开发")
tea1 = Teacher("矮根", 88, 'male', 2000, 10)
print(stu1.school) # oldboy
二 派生与方法重用
实现继承,就是在子类派生的新方法中重用父类的功能。
1 方式一(非继承方法) :
指名道姓地调用某一个类的函数。
特点:不依赖于继承关系。需要什么功能直接调用即可。
#####——————————##### 非继承方法,可以重用父类的功能
class OldboyPeople:
school = 'oldboy'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def f1(self):
print('111111')
class Student:
def __init__(self, name, age, gender, stu_id, course):
OldboyPeople.__init__(self, name, age, gender)
#
self.stu_id = stu_id
self.course = course
def course(self):
print('%s 正在选课' % self.name)
def f1(self):
OldboyPeople.f1(self)
print('222222')
class Teacher:
def __init__(self, name, age, gender, salary, level):
OldboyPeople.__init__(self, name, age, gender)
self.salary = salary
self.level = level
def score(self, stu, num):
stu.num = num
stu1 = Student('艾利克斯', 77, 'male', 1001, 'Python开发')
tea1 = Teacher('矮根', 88, 'male', 2000, 10)
stu1.f1() # 111111
# 222222
2 方式二:调用super(自己的类名, self)
调用super(自己的类名, self) 会返回一个特殊的对象,super(自己的类名, self).属性,会参照属性查找发起的那个类的mro列表去它的父类中查找属性。
特点:严格依赖于继承关系。
#####——————————##### super(自己的类名, self)方法的使用
class OldboyPeople:
school = 'oldboy'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def f1(self):
print('1111')
class Student(OldboyPeople):
def __init__(self, name, age, gender, stu_id, course):
super(Student, self).__init__(name, age, gender)
self.stu_id = stu_id
self.course = course
def choose(self):
print('%s 正在选课' % self.name)
def f1(self):
print('2222')
stu1 = Student("艾利克斯", 73, 'male', 1001, "python开发") # {'name': '艾利克斯', 'age': 73, 'gender': 'male', 'stu_id': 1001, 'course': 'python开发'}
print(stu1.__dict__)
#####——————————##### 严格依赖继承关系,以发起者为基准,去父类中依次往上寻找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A, B):
pass
c = C()
print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
c.test() # from B
#####——————————##### 严格依赖继承关系,以发起者为基准,去父类中依次往上寻找,不会在自身属性中查找!class A: def test(self): print('A---->test') super().aaa()class B: def test(self): print('B---->test') def aaa(self): print('B---->aaa')class C(A, B): def aaa(self): print('C---->aaa')c = C()print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]c.test()"""A---->testB---->aaa"""
三 属性查找
有了继承关系,对象在查找属性时,先从对象自己的__ dict__中查找,如果没有则去子类中找,然后再去父类中找。
1 父类的方法会被子类的同名方法覆盖掉
#####——————————##### 属性查找时,先在自身对象、类属性中查找,没有的话再去父类中查找,每一次的调用都是如此,包括在父类中有调用发生时,还是首先在自身对象、类属性中查找。如下示例:class Foo: def f2(self): print("Foo.f2") def f1(self): print('Foo.f1') self.f2() # obj.f2()class Bar(Foo): def f2(self): print("Bar.f2")obj1 = Bar()obj1.f1()"""Foo.f1Bar.f2"""
2 父类方法不被子类覆盖的解决方法
父类如果不想让子类覆盖自己的方法,可以在方法名前面加前缀"__ "双下划线。
#####——————————##### 加上前缀"__"之后,就会用到类中属性的特性,对外隐藏,对内可调用(语法检测阶段确定此关系) 。class Foo: def __f2(self): print("Foo.f2") def f1(self): print('Foo.f1') self.__f2() # obj.f2()class Bar(Foo): def __f2(self): print("Bar.f2")obj1 = Bar()obj1.f1()"""Foo.f1Foo.f2"""
四 继承的实现原理
1 补充:关于新式类与经典类
#### 1.新式类:(python3中全是新式类) 但凡是继承了object类的子类,以及该子类的继承类都称之为新式类。#### 2.经典类:(python2中才有) 没有继承object类的子类,以及该子类的继承类都称之为经典类。#### 3.如何实现兼容 在python3中是默认加上经典类的,所以,在代码中额外加上object这个父类即可:
#####——————————##### 加上object类之后,代码无论是在python的哪个版本,均可运行。class Foo(object): passprint(Foo.__bases__) # (<class 'object'>,)
2 菱形问题
大多数面向对象语言都不支持多继承,而在python中,一个子类是可以同时继承多个父类的,这可以容一个子类可以同时重用多个父类的功能,但也会带来菱形问题。即,一个子类继承的多个分支最终汇聚到一个非object类上。那么就带来了一个问题:如何继承,这需要了解继承的原理。
#####——————————##### A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。class A(object): def test(self): print('from A')class B(A): def test(self): print('from B')class C(A): def test(self): print('from C')class D(B,C): passobj = D()obj.test() # 结果为:from B
3 继承原理(属性查找规则)
python是如何实现继承的呢?对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO) 列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下:
D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
# 查找规则: 1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去。 2.由类发起的属性查找,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
4 深度优先与广度优先
4.1 非菱形继承下属性的查找顺序
经典类与新式类,属性的查找顺序相同。# 以下述代码为例:

#####——————————##### 非菱形继承下属性的查找顺序class E: def test(self): print('from E')class F: def test(self): print('from F')class B(E): def test(self): print('from B')class C(F): def test(self): print('from C')class D: def test(self): print('from D')class A(B, C, D): # def test(self): # print('from A') passprint(A.mro())'''[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]'''obj = A()obj.test() # 结果为:from B# 可依次注释上述类中的方法test来进行验证
4.2 菱形继承下属性的查找顺序
如果继承结构为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先与广度优先。
4.2.1 经典类:深度优先
每一条分支都进行到底。

#####——————————##### 菱形继承————经典类class G: # 在python2中,未继承object的类及其子类,都是经典类 def test(self): print('from G')class E(G): def test(self): print('from E')class F(G): def test(self): print('from F')class B(E): def test(self): print('from B')class C(F): def test(self): print('from C')class D(G): def test(self): print('from D')class A(B,C,D): # def test(self): # print('from A') passobj = A()obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试
4.2.2 新式类:广度优先
每一条分支,都不会进行到底,最后一条分支进行完之后,再查找最底层的那一层级。

#####——————————##### 菱形继承————新式类class G(object): def test(self): print('from G')class E(G): def test(self): print('from E')class F(G): def test(self): print('from F')class B(E): def test(self): print('from B')class C(F): def test(self): print('from C')class D(G): def test(self): print('from D')class A(B,C,D): # def test(self): # print('from A') passobj = A()obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object# 可依次注释上述类中的方法test来进行验证
五 python Mixins 机制
1 什么是Mixins机制
是一种命名规范,用来标识某些单一功能的类。
2 为什么要有Mixins机制
由于在python中一个子类可以继承多个父类,所以它有可能会导致菱形问题,使用Mixins机制可以避免该问题。
3 如何使用Mixins机制
简单说Mixins机制指的是子类混合(mixin) 不同类的功能,这些类采用统一的命名规范(例如Mixin后缀) ,以此标识这些类只是用来混合功能,并不是用来标识子类的丛书”is-a“关系,所以Mixins机制本质仍是多继承,但同样遵守”is-a“关系。
#####——————————##### 使用Mixins机制,标识一个交通工具类中不通用的飞行功能。class Vehicle: # 交通工具 passclass FlyableMixin: def fly(self): ''' 飞行功能相应的代码 ''' print("I am flying")class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机 passclass Helicopter(FlyableMixin, Vehicle): # 直升飞机 passclass Car(Vehicle): # 汽车 pass# ps: 采用某种规范(如命名规范) 来解决具体的问题是python惯用的套路
4 使用Mixin类实现多重继承的注意事项
(1) 首先,它必须表示某一种功能,而不是某个物品,python对于mixin类的命名方式一般以Mixin,able,ible为后缀;(2) 其次,它必须责任单一,如果有多个功能,那就写多个mixin类,一个类可以继承多个Mixin类,为了保证遵循继承的”is-a“原则,只能继承一个标识其归属含义的父类;(3) 再次,它不依赖于子类的实现;(4) 最后,子类即便是没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。
5 使用Mixin类的代价
# 定义的Mixin类越多,代码的可读性会越差。class Displayer: def display(self, message): print(message)class LoggerMixin: def log(self, message, filename='logfile.txt'): with open(filename, 'a') as fh: fh.write(message) def display(self, message): super().display(message) # super的用法请参考下一小节 self.log(message)class MySubClass(LoggerMixin, Displayer): def log(self, message): super().log(message, filename='subclasslog.txt') obj = MySubClass()obj.display("This string will be shown and logged in subclasslog.txt")# 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性#[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]# 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码# 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt"# 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt
六 C3算法
python3中的MRO实现基于C3算法,是广度优先算法。
1 为什么采用C3算法
C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
# 本地优先级: 指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。# 单调性: 如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
2 C3算法
判断mro要先确定一个线性序列,然后查找路径由由序列中类的顺序决定。所以C3算法就是生成一个线性序列。
# 如果继承至一个基类:class B(A)# 这时B的mro序列为[B,A]
# 如果继承至多个基类class B(A1,A2,A3 ...)
# 这时B的mro序列mro(B) = [B] + merge(mro(A1), mro(A2), mro(A3) ..., [A1,A2,A3])
merge操作就是C3算法的核心。
遍历执行merge操作的序列,如果一个序列的第一个元素,在其他序列中也是第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。 merge操作后的序列,继续执行merge操作,直到merge操作的序列为空。 如果merge操作的序列无法为空,则说明不合法。
# 例子:class A(O):passclass B(O):passclass C(O):passclass E(A,B):passclass F(B,C):passclass G(E,F):pass
# A、B、C都继承至一个基类,所以mro序列依次为[A,O]、[B,O]、[C,O]mro(E) = [E] + merge(mro(A), mro(B), [A,B]) = [E] + merge([A,O], [B,O], [A,B])
# 执行merge操作的序列为[A,O]、[B,O]、[A,B]# A是序列[A,O]中的第一个元素,在序列[B,O]中不出现,在序列[A,B]中也是第一个元素,所以从执行merge操作的序列([A,O]、[B,O]、[A,B])中删除A,合并到当前mro,[E]中。mro(E) = [E,A] + merge([O], [B,O], [B])
# 再执行merge操作,O是序列[O]中的第一个元素,但O在序列[B,O]中出现并且不是其中第一个元素。继续查看[B,O]的第一个元素B,B满足条件,所以从执行merge操作的序列中删除B,合并到[E, A]中。mro(E) = [E,A,B] + merge([O], [O]) = [E,A,B,O]
# 同理mro(F) = [F] + merge(mro(B), mro(C), [B,C]) = [F] + merge([B,O], [C,O], [B,C]) = [F,B] + merge([O], [C,O], [C]) = [F,B,C] + merge([O], [O]) = [F,B,C,O]mro(G) = [G] + merge(mro[E], mro[F], [E,F]) = [G] + merge([E,A,B,O], [F,B,C,O], [E,F]) = [G,E] + merge([A,B,O], [F,B,C,O], [F]) = [G,E,A] + merge([B,O], [F,B,C,O], [F]) = [G,E,A,F] + merge([B,O], [B,C,O]) = [G,E,A,F,B] + merge([O], [C,O]) = [G,E,A,F,B,C] + merge([O], [O]) = [G,E,A,F,B,C,O]
# 自己实现了一个mro算法:from exceptions import Exceptiondef c3_lineration(kls): if len(kls.__bases__) == 1: return [kls, kls.__base__] else: l = [c3_lineration(base) for base in kls.__bases__] l.append([base for base in kls.__bases__]) return [kls] + merge(l)def merge(args): if args: for mro_list in args: for class_type in mro_list: for comp_list in args: if class_type in comp_list[1:]: break else: next_merge_list = [] for arg in args: if class_type in arg: arg.remove(class_type) if arg: next_merge_list.append(arg) else: next_merge_list.append(arg) return [class_type] + merge(next_merge_list) else: raise Exception else: return []class A(object):passclass B(object):passclass C(object):passclass E(A,B):passclass F(B,C):passclass G(E,F):passprint c3_lineration(G)

浙公网安备 33010602011771号