python学习笔记DAY17(面向对象编程-继承)

这是我个人的学习笔记,都赖于(egon老师)的分享,以下是老师博客的原地址:
https://www.cnblogs.com/xiaoyuanqujing/articles/11640888.html

面向对象编程

一、继承的概念

继承是一种新建类的方式,在python中,新建的类可以继承一个或者多个父类。新建的类可以称为子类或者派生类,父类可以称为基类或者超类。

class Animal:  # 动物类
    pass

class Plant:   # 植物类
    pass

class People(Animal):   # 人类 单继承
    pass

class yizu(Animal,Plant):  # 苅族 多继承
    pass

# 当前类. __bases__   展示所有父类
print(Yizu.__bases__)  # (<class '__main__.Animal'>, <class '__main__.Plant'>)

ps:

在python2中,分经典类和新式类。

新式类:继承了object类的子类,以及该子类的子子类。

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

在python3中,即使没有明显表示继承object,也会默认继承该类:

class Animal:  # 动物类
    pass
# 默认继承object类
print(Animal.__bases__)  # (<class 'object'>,)

所以,在python中所有类都是新式类,都是object的子类

二、继承的作用

用来解决代码冗余的问题。

之前的类的学习,我们知道类是用来解决对象与对象之间代码冗余的问题。而继承则是为了解决类与类之间的代码冗余问题

  • python继承的特点—多继承

    • 优点:子类可以同时遗传多个父类的属性,最大限度的重复利用代码
    • 缺点:1.违背了人的思维习惯(继承是表达一种“是”的关系,可多继承表示:既是这个,又是那个)2.代码可读性变差 3.扩展性变差。

    综上所述:不建议使用多继承,如果特使情况无法避免,也应该使用Mixins(规范的使用多继承)

  • 继承实例

class Monkey():   # 猴类
    family = "earth"
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def eat(self):
        print(f"{self.name}正在吃香蕉")

class People():   # 人类
    family = "earth"
    def __init__(self,name,age,country,job,salary):
        self.name = name
        self.age = age
        self.country = country
        self.job = job
        self.salary = salary

    def eat(self):
        print(f"{self.name}正在吃午餐")       

以下创建了一个人类,一个猴类。现在提取出两个类中共同属性,创建一个父类(动物类):

class Animal:  # 动物类
    family = "earth"
    def __init__(self,name, age):
        self.name = name
        self.age = age

class Monkey(Animal):   # 猴类
    def eat(self):
        print(f"{self.name}正在吃香蕉")

    def tell_info(self):
        print(self.name,self.age)

class People(Animal):   # 人类
    family = "earth"
    def __init__(self,name, age,country,job,salary):
        self.country = country
        self.job = job
        self.salary = salary
        Animal.__init__(self,name,age)   # 用到父类的属性的话,直接调用即可

    def eat(self):
        print(f"{self.name}正在吃午餐")

    def tell_info(self):
        print(self.name,self.age,self.country,self.job,self.salary)

monkey_obj1 = Monkey("圆圆",3)  
people_obj1 = People("nida",20,"中国","打字员",20000)

monkey_obj1.tell_info()
people_obj1.tell_info()

三、属性查找

  • 单继承继承的关系,对象在查找属性时,先从对象自己的_dict__中找,如果没有则去子类中找,然后再去父类中找。
class Foo:
    def f1(self):
        print("正在打印Foo.f1")
    def f2(self):
        print("正在打印Foo.f1")
        self.f1()

class Bar(Foo):
    def f1(self):
        print("正在打印Bar.f1")

b = Bar()
b.f2()

# 结果
正在打印Foo.f1
正在打印Bar.f1

备注:以上代码,按照我们惯性思维b.f2()被调用,打印之后,看到self.f1()会自然想到应该打印"正在打印Foo.f1",但是要记住查找的规律,当看到self.f1()时思考,这时的self就是传过来的对象,要先从对象本身查找,发现没有,就回去对象的类中找,就发现找到了。所以打印的是Bar.f1。

  • 父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的而方式,将其设为私有的。
class Foo:
    def __f1(self):              # 隐藏属性变形为:_Foo__f1
        print("正在打印Foo.f1")
    def f2(self):              
        print("正在打印Foo.f1")
        self.__f1()     # 此时调用 __f1()已经被变形过了,就是_Foo__f1

class Bar(Foo):
    def __f1(self):              # 隐藏属性变形为:_Bar__f1
        print("正在打印Bar.f1")

b = Bar()
b.f2()

# 结果
正在打印Foo.f1
正在打印Foo.f1

备注:所以,如果对象的类和父类有一样的属性,而对象此时就想访问父类中的属性,就可以把它设为隐藏属性

四、继承原理的实现

  • 对于你定义的每一个类,python都会计算出一个方法解析顺序的(MRO)列表,该列表就是一个简单的所有基类的线型顺序列表:
print(Bar.mro()) # 新式类有内置方法mro可以查看线性列表内容,经典类没有该方法
# [<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>]

备注:MRO列表的构造是通过一个C3线性化的算法来实现的,我们无需深究,它其实就是合并所有父类的MRO列表,且在查找属性时,python会基于MRO列表从左到右的顺序依次查找基类,直到找到第一个匹配这个属性的类为止。

  • 在python中子类可以同时继承多个父类,在子类继承多个父类时,经典类与新式类会有不同的MRO,分别对应属性的两种查找方式:深度优先和广度优先
class G():
    pass

class F(G):
    pass

class E(G):
    pass

class D(G):
    pass

class C(F):
    pass

class B(E):
    pass

class A(B,C,D):
    pass

print(A.mro())

# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, 
# <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]

经典类(没有默认继承object的类),多继承情况下,在要查找的属性不存在时,会按照深度优先的方式查找下去(也是按分支从左到右的查看,但是最后一个顶级父类会在所有类查找完之后,再查找)

新式类(默认继承了object的类),多继承情况下,在要查找的属性不存在时,会按照广度度优先的方式查找下去(也是按分支从左到右的查看,但是最后一个顶级父类会在第一个分支就被查找)

五、派生与方法重用

子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找。

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(f"{self.name}正在授课")

obj = Teacher("yoayao","female",18,"高级讲师")
print(obj.name,obj.sex,obj.age,obj.title)

# yoayao female 18 高级讲师

如上代码:很明显Teacher中的_init__中的前三行代码都是与父类重复的,那如果想省去写重复的,直接用父类的,有两种实现方式

方法一:“指名道姓”的调用某一个类的函数

class Teacher(People):
    def __init__(self,name,sex,age,title):
        People.__init__(self,name,sex,age)    # 此时调用的是函数,所以要手动传入self
        self.title = title
    def teach(self):
        print(f"{self.name}正在授课")

方法二:super()

调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找

class Teacher(People):
    def __init__(self,name,sex,age,title):
        super().__init__(name,sex,age)       # 此时调用的是绑定方法,自动传入self
        self.title = title
    def teach(self):
        print(f"{self.name}正在授课")

备注:在python2中super的使用需要完整的写成super(自己的类名,self),而python中可以简写为super()

两种方法的区别:方式一是跟继承没有关系的,方式二的super()是依赖于继承的。而且没有继承关系,super()依然会按照MRO继续往后查找:

class A:
    def f1(self):
        print("form A")
        super().f2()
        print("在f1中找到f2")

class B:
    def f2(self):
        print("form B")

class C(A,B):
    print("from c")

print(C.mro())  # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
c = C()
c.f1()
# 结果
# from c   先去自己类里边找,没有
# form A   就去第一个父类 A 里找,从A中看到super,按照mro顺序,找到f2
# form B   但是AB 并没有继承关系
# 在f1中找到f2
posted @ 2021-06-01 10:25  nida  阅读(111)  评论(0)    收藏  举报