python基础25——继承&属性查找&多继承的菱形问题&Mixins机制

继承

 

1、什么是继承

I:继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性
II:需要注意的是:python支持多继承
在Python中,新建的类可以继承一个或多个父类

class Parent1(object):
x = 1111


class Parent2(object):
pass


class Sub1(Parent1): # 单继承
pass


class Sub2(Parent1, Parent2): # 多继承
pass


print(Sub1.__bases__)
print(Sub2.__bases__)

print(Sub1.x)



ps1: 在python2中有经典类与新式类之分

新式类:继承了object类的子类,以及该子类的子类子子类。。。
经典:没有继承object类的子类,以及该子类的子类子子类。。。

ps2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类
print(Parent1.__bases__)
print(Parent2.__bases__)


III:python的多继承
优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
缺点
1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系
2、代码可读性会变差
3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,
如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins




2、为何要用继承:用来解决类与类之间代码冗余问题





3、如何实现继承

示范1:类与类之间存在冗余问题

class Student:
school = 'OLDBOY'

def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex

def choose_course(self):
print('学生%s 正在选课' % self.name)


class Teacher:
school = 'OLDBOY'

def __init__(self, name, age, sex, salary, level):
self.name = name
self.age = age
self.sex = sex
self.salary = salary
self.level = level

def score(self):
print('老师 %s 正在给学生打分' % self.name)



示范2:基于继承解决类与类之间的冗余问题

class OldboyPeople:
school = 'OLDBOY'

def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex


class Student(OldboyPeople):
def choose_course(self):
print('学生%s 正在选课' % self.name)


stu_obj = Student('lili', 18, 'female')
print(stu_obj.__dict__)
print(stu_obj.school)
stu_obj.choose_course()


class Teacher(OldboyPeople):
# 老师的空对象,'egon',18,'male',3000,10
def __init__(self, name, age, sex, salary, level):
# 指名道姓地跟父类OldboyPeople去要__init__
OldboyPeople.__init__(self, name, age, sex)
self.salary = salary
self.level = level

def score(self):
print('老师 %s 正在给学生打分' % self.name)


tea_obj = Teacher('egon', 18, 'male', 3000, 10)
print(tea_obj.__dict__)
print(tea_obj.school)

tea_obj.score()









属性的查找

# 单继承背景下的属性查找
示范一:
class Foo:
def f1(self):
print('Foo.f1')

def f2(self):
print('Foo.f2')
self.f1() # obj.f1()


class Bar(Foo):
def f1(self):
print('Bar.f1')


obj = Bar()
obj.f2()


# Foo.f2
# Foo.f1



示范二:
class Foo:
def f1(self):
print('Foo.f1')

def f2(self):
print('Foo.f2')
Foo.f1(self) # 调用当前类中的f1


class Bar(Foo):
def f1(self):
print('Bar.f1')


obj = Bar()
obj.f2()


# Foo.f2
# Foo.f1



示范三:

class Foo:
def __f1(self): # _Foo__f1
print('Foo.f1')

def f2(self):
print('Foo.f2')
self.__f1() # self._Foo__f1,# 调用当前类中的f1


class Bar(Foo):
def __f1(self): # _Bar__f1
print('Bar.f1')


obj = Bar()
obj.f2()

# Foo.f2
# Foo.f1







多继承带来的菱形问题

一:菱形问题介绍与MRO

class A(object):
# def test(self):
# print('from A')
pass


class B(A):
def test(self):
print('from B')

pass


class C(A):
# def test(self):
# print('from C')
pass


class D(C, B):
# def test(self):
# print('from D')
pass


print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表

obj = D()
obj.test()

print(D.test)

print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
c = C()
c.test()


# 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro






二:如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:
都是一个分支一个分支地找下去,然后最后找object

class E:
# def test(self):
# print('from E')
pass


class F:
def test(self):
print('from F')


class B(E):
# def test(self):
# print('from B')
pass


class C(F):
# def test(self):
# print('from C')
pass


class D:
def test(self):
print('from D')


class A(B, C, D):
# def test(self):
# print('from A')
pass


# 新式类
print(A.mro()) # A->B->E->C->F->D->object

obj = A()
obj.test() # 结果为:from F






三:如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
新式类:广度优先,会在检索最后一条分支的时候检索大脑袋



class G:                 # 在python2中,未继承object的类及其子类,都是经典类
# def test(self):
# print('from G')
pass


class E(G):
# def test(self):
# print('from E')
pass


class F(G):
def test(self):
print('from F')


class B(E):
# def test(self):
# print('from B')
pass


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')
pass


# 新式类
print(A.mro()) # A->B->E->C->F->D->G->object

# 经典类:A->B->E->G->C->F->D
obj = A()
obj.test() #



总结:

多继承到底要不用???
要用,但是规避几点问题
1、继承结构尽量不要过于复杂
2、推荐使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系







Mixins机制


一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?


​ 答案是有,我们还是拿交通工具来举例子:


​ 民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的

class Vehicle:  # 交通工具
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(Vehicle):  # 民航飞机
    pass


class Helicopter(Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
    pass

但是如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则(如果以后飞行工具越来越多,那会重复代码将会越来越多)。

​ 怎么办???为了尽可能地重用代码,那就只好在定义出一个飞行器的类,然后让民航飞机和直升飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。这个难题该怎么解决?

​ 不同的语言给出了不同的方法,让我们先来了解Java的处理方法。Java提供了接口interface功能,来实现多重继承:

// 抽象基类:交通工具类
public abstract class Vehicle {
}

// 接口:飞行器
public interface Flyable {
    public void fly();
}

// 类:实现了飞行器接口的类,在该类中实现具体的fly方法,这样下面民航飞机与直升飞机在实现fly时直接重用即可
public class FlyableImpl implements Flyable {
    public void fly() {
        System.out.println("I am flying");
    }
}



// 民航飞机,继承自交通工具类,并实现了飞行器接口
public class CivilAircraft extends Vehicle implements Flyable {
    private Flyable flyable;

    public CivilAircraft() {
        flyable = new FlyableImpl();
    }

    public void fly() {
        flyable.fly();
    }
}

// 直升飞机,继承自交通工具类,并实现了飞行器接口
public class Helicopter extends Vehicle implements Flyable {
    private Flyable flyable;

    public Helicopter() {
        flyable = new FlyableImpl();
    }

    public void fly() {
        flyable.fly();
    }
}

// 汽车,继承自交通工具类,
public class Car extends Vehicle {
}

现在我们的飞机同时具有了交通工具及飞行器两种属性,而且我们不需要重写飞行器中的飞行方法,同时我们没有破坏单一继承的原则。飞机就是一种交通工具,可飞行的能力是飞机的属性,通过继承接口来获取。

回到主题,Python语言可没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下

class Vehicle:  # 交通工具
    pass


class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass


class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路

 


可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。


使用Mixin类实现多重继承要非常小心


  • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
  • 然后,它不依赖于子类的实现
  • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

​ Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向,如下


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


ps:课外了解小知识
Java只允许接口的多重继承。接口本质上是抽象基类,具有所有抽象方法,没有数据成员。
java一样python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,继承的子类必须实现抽象基类规定的方法,这样便可保证始终只有一个特定方法或属性的实现,并且不会产生歧义,因而也可以起到避免菱形问题的作用

java的interfacehttps://www.cnblogs.com/linhaifeng/articles/7340153.html#_label6
python的抽象基类https://www.cnblogs.com/linhaifeng/articles/7340153.html#_label7

 

 
posted @ 2020-04-09 18:02  凌醉枫  阅读(311)  评论(0编辑  收藏  举报