面向对象编程之继承和派生

本文详细介绍了Python中的继承机制,包括单继承和多继承,以及如何处理菱形问题。讲解了抽象概念在类定义中的应用,属性查找顺序,Python的Mixin机制,方法重用(super()函数)和类的组合。
摘要由CSDN通过智能技术生成

目录

一、继承介绍

二、继承与抽象

三、属性查找

四、继承实现原理

4.1菱形问题

4.2继承原理

4.3深度优先和广度优先

 4.4Python Mixin机制

五、派生与方法重用

六、组合

七、总结


一、继承介绍

继承是一种创建新类的方式,子类(派生类)可以继承一个或多个父类(基类、超类)。继承了object的类称为新式类,没有继承object的类称为经典类。Python3定义的类默认继承object类。

class ParentClass1: #定义父类
    pass
 
class ParentClass2: #定义父类
    pass
 
class SubClass1(ParentClass1): #单继承
    pass
 
class SubClass2(ParentClass1,ParentClass2): #多继承
    pass

说明:通过“类名.__bases__”可以查看子类继承的所有父类

二、继承与抽象

所谓抽象就是总结事物之间的共性即相似之处。通过总结各对象之间的相似之处我们可以定义出类,通过总结类之间的相似之处我们可以定义出父类。

根据上图抽象的结果我们可以找到类之间的继承关系:

继承的本质: 是将各子类中相同的数据变量和函数都整合到父类中;然后各子类在以继承父类的形式来获取父类中所有的数据,从而实现减少各子类间代码重复问题(继承:什么是什么的问题如:黄种人、黑种人、白种人都是人类)。

 例如:学生和老师都属于相同的学校、都有名字、性别、年龄存在相同的数据属性,所以可以定义一个父类来保存相同的数据属性;但老师负责教书、学生负责学习,因此可以分别为学生和老师定义一个子类里面只保存各自的功能函数,相同的数据属性可以通过继承父类来实现访问。

class People:
    school='清华大学'
 
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age
 
class Student(People):
    def learning(self):
        print('%s is learning a course' %self.name)
 
class Teacher(People):
    def teach(self):
        print('%s is teaching' %self.name)

三、属性查找

在继承关系下,对象查找数据变量和函数属性的顺序:  对象本身内存【对象.__dict__列表】——对象所属类.mro( )。

 >>> class Foo:
...     def f1(self):
...         print('Foo.f1')
...     def f2(self):
...         print('Foo.f2')
...         self.f1()
... 
>>> class Bar(Foo):
...     def f1(self):
...         print('Foo.f1')
... 
>>> b=Bar()
>>> b.f2()
Foo.f2
Foo.f1

b.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f10,即b.f1(), 仍会按照:对象本身- >类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Foo.f1。

如果不想让子类覆盖父类中的方法可以将父类的方法设置为隐藏的私有属性(本质:__类名__属性名):

>>> class Foo:
...     def __f1(self): # 变形为_Foo__fa
...         print('Foo.f1') 
...     def f2(self):
...         print('Foo.f2')
...         self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
... 
>>> class Bar(Foo):
...     def __f1(self): # 变形为_Bar__f1
...         print('Foo.f1')
... 
>>> 
>>> b=Bar()
>>> b.f2() #在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
Foo.f2
Foo.f1

四、继承实现原理

4.1菱形问题

如果A类中有一个方法,B和C类都重写了父类A的方法,而继承了B和C类的D类没有重写父类方法,那么D类的实例对象在调用此方法时,会使用那个类中的方法?这就是菱形问题。如:

4.2继承原理

在python中你所定义的每一个类都会为其类计算出一个“类名/对象名.mro”列表,里面记录着python在父类与子类之间查找使用属性的顺序。查找mro列表顺序是:从左到右,一旦找到则执行此类下的属性。

>>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

 说明:

1.由对象发起的属性查找(即"."前面所指),会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去。

4.3深度优先和广度优先

在经典类多继承的情况下,如果在当前类没有找到要查找的属性,则会根据mro列表、采取深度优先的方式(最先查找顶部父类)进行属性查找。

 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')
    pass
 
obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试

 在新式类多继承的情况下,如果在当前类中没有找到属性则会在“类名.mro()”列表中、广度优先(最后查找最顶部父类)的原则去查找属性。

 4.4Python Mixin机制

Mixin类: 多继承场景下父类命名规范,一般以Mixin、able、ible为后缀。用来告诉读者这个类只是用来给子类添加一个功能函数的、而不是为了作为子类的父类。

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惯用的套路

Mixin实现多重继承注意点

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

五、派生与方法重用

子类可以定义自己的新属性,在熟属性查找时优先查找子类中的属性。
方法一:直接在子类中调用父类中的某个函数

>>> 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()函数来执行父类中的某个函数

>>> 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)
... 

这两种方式的区别是:方式一是跟继承没有关系的, 方式二的super0是依赖于继承的,即使类没有
直接继承关系,super()仍然会按照MRO继续往后查找。

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

#说明:obj.test()首先找到A下的test()方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。

六、组合

类的组合:   在一个类中,将另一个类的对象赋值给当然类的某个属性变量,如:  self.birth=类名(参数,…,…)。其本质为一个类对象中保存着另一个类对象的地址,实现通过本对象可以访问到另一个对象所能访问到的所有属性方法。

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()

七、总结

1、类名.__bases__: 查看当前类继承的所有父类
2、继承了object的类称为新式类,没有继承object的类称为经典类。Python3定义的类默认继承object类。
3、继承的本质: 是将各子类中相同的数据变量和函数都整合到父类中;然后各子类在以继承父类的形式,来获取父类中所有的数据,从而实现减少各子类间代码重复问题。
4、Mixin类: 多继承场景下父类命名规范,一般以Mixin、able、ible为后缀。用来告诉读者这个类只是用来给子类添加一个功能函数的、而不是为了作为子类的父类。
5、在继承关系下,对象查找数据变量和函数属性的顺序:  对象本身内存【对象.__dict__列表】——对象所属类.mro( )。
6、在子类方法内调用父类功能函数方式:
“父类.函数( )” ——需要手动传入所有参数
“super().函数( )”——函数self参数会以对象地址自动传入;super( )函数的返回值是"低层类名.mro"下一个要查找的类地址。
7、如果不想让子类覆盖父类中的变量和函数,可以其改为私有属性(本质: __类名__属性名)
8、类的组合:   在一个类中,将另一个类的对象赋值给当然类的某个属性变量,如:  self.birth=类名(参数,…,…)。其本质为一个类对象中保存着另一个类对象的地址,实现通过本对象可以访问到另一个对象所能访问到的所有属性方法。

@声明:

1、以上文章的部分代码和插图来源于林海峰老师的资料。

2、“山月润无声”博主专业知识水平有限,以上文章如有不妥之处希望广大IT爱好者指正,小弟定当虚心受教!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Orion Guan's 山月润无声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值