面向对象之继承

class继承

一:继承介绍

什么是继承?

继承就是创建了一个新的类,在Python中,新创建的类可以继承一个或者多个父类(现实社会有点难)新建的类就是子类,围着派生类,父类称之为基类或者超类(累)

为什么要有继承?

继承可以减少代码冗余

如何使用继承?

class P1:
	pass

class P2:
    pass

class sub1(P1): # 继承了P1类
    pass

class sub2(P1,P2): # 继承了P1,P2类
    pass

通过类内的__bases__可以查看类继承的所有的父类

print(sub1.__bases__) # (<class '__main__.P1'>,)
print(sub2.__bases__) # (<class '__main__.P1'>, <class '__main__.P2'>)

示列一(继承)

class School:
    scholl = '东京校区'
    
class Student(School):
    
    def __init__(self,name,age,gender,suid,course):
        self.name = name
        self.age = age
        self.gender = gender
        self.suid = suid
        self.course = course
    
    def choose(self):
        print(f"{self.name}正在选课")
    
     
class Teacher(School):
    
    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,student,num):
        student.num = num
        
s1 = Student('alen',66,'male',10001,'python开发')
t1 = Teacher('eg',18,'male',2000,10)

print(t1.scholl)    # 东京校区
print(s1.__dict__)  # {'name': 'alen', 'age': 66, 'gender': 'male', 'suid': 10001, 'course': 'python开发'}
print(t1.__dict__)  # {'name': 'eg', 'age': 18, 'gender': 'male', 'salary': 2000, 'level': 10}

二:如何在子类中派生新方法重用父类的功能

方式一:指名道姓地调用某一个类的函数

特点:可以不以依赖继承关系

示列

class School:
    scholl = '东京校区'
    
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    
    
    def t1(self):
        print("这是一个测试继承的函数")
    
class Student(School):
    
    def __init__(self,name,age,gender,suid,course):
        School.__init__(self,name,age,gender)
        self.suid = suid
        self.course = course
    
    def choose(self):
        print(f"{self.name}正在选课")
    
     
class Teacher(School):
    
    def __init__(self,name,age,gender,salary,level):
     	School.__init__(self,name,age,gender)
        self.salary = salary
        self.level = level
        
    def score(self,student,num):
        School.t1(self)
        student.num = num
        
s1 = Student('alen',66,'male',10001,'python开发')
t1 = Teacher('eg',18,'male',2000,10)
print(s1.__dict__)   # {'name': 'alen', 'age': 66, 'gender': 'male', 'suid': 10001, 'course': 'python开发'}
print(t1.__dict__)  # {'name': 'eg', 'age': 18, 'gender': 'male', 'salary': 2000, 'level': 10}

三:属性查找

在python中如果对象内部没有属性,就去类里去找,如果类里没有就去父类找(如果没有有继承,就找不到报错)

在python3中会去object里去找,(如果没有默认继承,默认就是继承object类),在python2中,存在两种类(新式类和经典类),两种类的查找循序有点区别

示列

class  Foo:
    
    def f2(self):
        print("from foo.f2")
    
    def f1(self):
        print("from foo.f1")
        self.f2()
        
class Bar(Foo):
    def f2(self):
        print("from Bar.f2")

t = Bar()
t.f1() 
"""
from foo.f1
from Bar.f2
"""

父类如果不想让子类覆盖自己的方法,可以在方法名前加__,让其成为私有属性

class  Foo:
    
    def __f2(self):
        print("from foo.f2")
    
    def f1(self):
        print("from foo.f1")
        self.__f2()
        
class Bar(Foo):
    def __f2(self):
        print("from Bar.f2")

obj = Bar()
obj.f1()
​```
from foo.f1
from foo.f2
​```

四:继承的原理

基础知识

新式类:凡是继承了object的子类,以该子类子子孙孙类都称之为新式类

经典类:没有继承object的子类,以该子类子子孙孙类都称之为经典类

在python3中全都是新式类,python2中有经典类

在python3中没有继承任何类,默认基础obj类

在py3中
class Foo:
	pass

print(Foo.__bases__) # (<class 'object'>,)
在py2中
class Foo:
	pass
print(Foo.__bases__) # 为空

继承的实现原理

1:菱形问题

大多数面向对象语言都不支持多继承,在python中,一个子类是可以同时继承多个父类的,这个可以让一个子类,可以对多个不同父类加以重用的好处,但是也有可能引发著名的菱形问题(或称死亡钻石问题)

如图

![菱形继承](D:\Program Files (x86)\markdown\python3.8\菱形继承.png)

A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。

这个图展示的就是菱形继承

如果A中有一个方法,B或者C都重写了该方法,而D没有重写,那么D继承的是哪个版本的方法:是B或者是C?看下面

在py3中
class A:
    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):
    pass
                                    
obj = D
obj.test()  # 结果为from B

要想知道obj.test()是如何找到方法test的,需要了解Python的继承实现原理--

2继承原理

MRO

Python是如何实现继承的呢?

对于你定义的每一个类,python都会计算出方法解析顺(mro)列表,该mro列表就是一个简单的所有基类的线性顺序列表,如下

print(D.mro())
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python会在mro列表上从左到右开始查找基类,直到找到一个第一匹配这个属性的类为止

这个mro列表的构造是通过一个C3线性算法来实现的。

这个算法实际上就是合并所有父类的mro列表并遵循如下原则

1:子类会先于父类查找
2:多个父类会根据它们所在列表中的顺序被检查
3:如果对下一个类存在两个合法的选择,选择第一个父类

属性查找

1:由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类的mro列表规定的顺序依次找下去
2:由类发起的属性查找,会按照当前类.mro规定的顺序依次找下去

示列

#【test1】

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(C,B):
    def test(self):
        print('from D')

print(D.mro())  # 类D以及类D的对象访问属性都是参照该类的mro列表
# 输出结果:
# [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj1 = D()
obj1.test()   # 输出结果:from D
print(D.test)  # 输出结果:<function D.test at 0x0000012EA27FA4C0>


print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
# 输出结果:[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
obj2 = C()
obj2.test()  # 输出结果:from C
print(C.test) # 输出结果: <function C.test at 0x0000022FA7D0A3A0>

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

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

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列表
# 输出结果:
# [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj1 = D()
obj1.test()   # 输出结果:from B
print(D.test)  # 输出结果:<function B.test at 0x00000246D6F3A310>

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

3:深度优先和广度优先

1:非菱形继承

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

示列如下

![image-20200807201837419](D:\Program Files (x86)\markdown\python3.8\image-20200807201837419.png)

代码演示

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 
# [<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 F

2:菱形继承

如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样

经典类:深度优先,会在在检索第一个分支的时候就直接一天道走到黑,即会检索(共同的父类)

新式类:广度优先,会在检索最后一条分支的时候检索共同的父类

![image-20200807202406380](D:\Program Files (x86)\markdown\python3.8\image-20200807202406380.png)

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


obj = D()
obj.test() # 结果为:from B
# 如果B没有去找C,c没有然后找A
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
print(d.mro) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
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
# # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>,
# # <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
obj = A()
obj.test()  # 输出结果:from C


# 经典类:A->B->E->G->C->F->D
print(A.mro())
obj = A()
obj.test()  # 输出结果:from C

3:总结

多继承到底要不要用?
python既然提供了,那么就要用,但是要规避问题
1:继承结构尽量不要过于复杂
2:推荐使用mixins机制:在多继承的背景下满足继承是什么关系

四:python Mixins机制

用来解决多继承所带来的问题的,将原本的混乱的继承清晰的变成’is-a‘的关系

# 民航飞机、直升飞机、汽车都是交通工具,但是飞机会飞,汽车不会,
# 这个时候我们将飞这个功能放到交通工具中就显得不合适
class Vehicle:  # 交通工具
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


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


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


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

# 针对上面的问题,就引入Mixin机制
# Mixin机制指的就是子类混合不同类的功能,然后将其用统一的命名规范来标识该类只是用来混合功能的
# 从而来遵守多继承下的“is-a”关系,Mixin机制本质仍是多继承
class Vehicle:  # 交通工具
	pass

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


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


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


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

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

五:派生与方法重用

  • 子类中衍生出的新东西

    1. 子类独有,父类没有
    2. 子类有,父类也有,子类是完全覆盖父类
    3. 子类有,父类也有,子类在父类的基础上进行扩展
    
    

class People:
... school='清华大学'
...
... def init(self,name,sex,age):
... self.name=name
... self.sex=sex
... self.age=age
...
class Teacher(People):
... def init(self,name,sex,age,title): # 派生
... self.name=name
... self.sex=sex
... self.age=age
... self.title=title
... def teach(self):
... print('%s is teaching' %self.name)

```
  • 想在子类派生出的方法内重用父类的功能,有两种实现方式

    # 方法一:“指名道姓”地调用某一个类的函数=》不依赖于继承
    >>> class Teacher(People):
    ...     def __init__(self,name,sex,age,title):
    ...         People.__init__(self,name,age,sex) #调用的是函数,因而需要传入self
    ...         self.title=title
    ...     def teach(self):
    ...         print('%s is teaching' %self.name)
    
    # 方法二:super()调用父类提供给自己的方法=》严格依赖继承关系
    # 调用super()会得到一个特殊的对象,该对象会按照发起属性查找的类的mro去当前类的父类中找属性
    >>> class Teacher(People):
    ...     def __init__(self,name,sex,age,title):
    ...         super().__init__(name,age,sex) #调用的是绑定方法,自动传入self
    ...         self.title=title
    ...     def teach(self):
    ...         print('%s is teaching' %self.name)
    
    
    >>> #A没有继承B
    ... class A:
    ...     def test(self):
    ...         super().test()
    ... 
    >>> class B:
    ...     def test(self):
    ...         print('from B')
    ... 
    >>> class C(A,B):
    ...     pass
    ... 
    >>> C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
    [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>]
    >>> obj=C()
    >>> obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro()
    from B
    

    六:组合

    什么是组合?

    • 一个类中以另外一个类的对象作为数据属性,作为类的组合
    # 组合与继承都是用来解决代码的重用性问题。
    # 二者的区别在于
    '''
    继承:强调‘是’的关系,例如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承
    组合:强调‘有’的关系,例如老师有多门课程,老师有给学生打分,当类之间有显著不同,并且较小的类是较大的类所需
    要的组件时,应该使用组合
    '''
    class Course:
        def __init__(self,name,period,price):
            self.name=name
            self.period=period
            self.price=price
        def tell_info(self):
            print('<%s %s %s>' %(self.name,self.period,self.price))
    
    class Date:
        def __init__(self,year,mon,day):
            self.year=year
            self.mon=mon
            self.day=day
        def tell_birth(self):
           print('<%s-%s-%s>' %(self.year,self.mon,self.day))
    
    class People:
        school='清华大学'
        def __init__(self,name,sex,age):
            self.name=name
            self.sex=sex
            self.age=age
    
    #Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
    class Teacher(People): #老师是人
        def __init__(self,name,sex,age,title,year,mon,day):
            super().__init__(name,age,sex)
            self.birth=Date(year,mon,day) #老师有生日
            self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
        def teach(self):
            print('%s is teaching' %self.name)
    
    
    python=Course('python','3mons',3000.0)
    linux=Course('linux','5mons',5000.0)
    teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)
    
    # teacher1有两门课程
    teacher1.courses.append(python)
    teacher1.courses.append(linux)
    
    # 重用Date类的功能
    teacher1.birth.tell_birth()
    
    # 重用Course类的功能
    for obj in teacher1.courses: 
        obj.tell_info()
    

参考

https://zhuanlan.zhihu.com/p/109331525

https://www.cnblogs.com/linhaifeng/articles/7340153.html

posted @ 2020-08-08 21:23  为了等  阅读(108)  评论(0编辑  收藏  举报