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

浙公网安备 33010602011771号