面向对象之继承与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)
posted @ 2020-03-24 22:50  越关山  阅读(143)  评论(0)    收藏  举报