Python面向对象特性 - 继承

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python面向对象特性 - 继承相关的知识,希望对你有一定的参考价值。

面向对象有3大特性:继承、多态、封装,本章介绍 Python中的 继承 特性~
?

什么是继承

继承是创建类的一种方式,在 Python中支持多继承,即在创建类的时候可以继承一个或者多个父类。
在继承关系中,被继承的类通常称为父类(或超类,基类),新建的类则称为子类(或派生类)。
?
继承的优势在于可以有效地重用代码,提高代码的可读性~
?
继承示例:

class Fu_1:    # 父类
    pass

class Fu_2:    # 父类
    pass

class Zi_1(Fu_1):   # 单继承
    pass

class Zi_2(Fu_1, Fu_2):    # 多继承
    pass

?
上述示例中,Fu_1 和 Fu_2 没有继承任何类,在 Python3 中,这样就会默认继承object类,而在 Python2 中,默认不会继承 object类,注意区分 ~
?
可通过 类的内置属性 __bases__ 查看这个类 继承的所有父类

print(Zi_1.__bases__)
print(Zi_2.__bases__)

# 输出结果:
(<class ‘__main__.Fu_1‘>,)
(<class ‘__main__.Fu_1‘>, <class ‘__main__.Fu_2‘>)

重用与派生

重用

在开发过程中,若新建的一个类和已创建的另一个类 属性及方法大致相同,则可以让新建的类(子类)继承已创建的类(父类),这样子类会继承父类的所有属性,包括数据属性和函数属性,实现了代码重用,代码变得简洁,可以有效缩短开发周期~

class Father:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print(‘Hello !‘)

class Son(Father):
    pass

p = Son(‘baby‘, 19)
p.say()

# 输出结果:
Hello !

?

派生

在继承过程中,子类也可以添加或者重新定义这些属性,当父类和子类中有同名的属性时(包括数据属性和函数属性),会先调用子类中的属性(操作的是子类的实例化对象)

class Father:
    city = ‘NB‘
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print(‘Hello !   ‘ + self.city)

class Son(Father):
    city = ‘HZ‘
    def say(self):
        print(‘你好 ~   ‘ + self.city)

    def eat(self):
        pass

p = Son(‘baby‘, 19)
p.say()

# 输出结果:
你好 ~   HZ

super

在子类的函数中,若是要重用父类中某个函数的功能,可以直接通过 super 来调用父类中的函数,当然也可以通过 类名.func() 来调用,只不过这样与调用普通函数无异。这个经常使用在需要对父类的同名方法进行扩展的场景~

class Father:
    def say(self):
        print(‘Hello !‘)

    def introduce(self):
        print(‘Father‘)

class Son(Father):
    def say(self):
        super().say()
        # Father.say(self)   # 通过 类名.func(),输出结果一致
        print(‘你好 ~‘)

p = Son()
p.say()

# 输出结果
Hello !
你好 ~

上述示例中,使用 super的时候省略了2个参数:Son(当前类名称,注意不是父类),self(当前对象)

super().say()
# 等同于
super(Son, self).say()

由于 super 方法中已经默认传递了self参数,所以后面的函数不需要再次传递self~
?
注意:super关键字只在新式类中有,Python3中所有的类都是新式类...
?
在子类的函数中使用super方法,不一定仅调用同名的父类函数,也可以调用其他的父类函数~

def say(self):
        super().introduce()
        print(‘你好 ~‘)

?
如下示例中子类对父类的 init方法 进行了扩展,这是一种较为常用的使用方式~

class Father:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def say(self):
        print(‘Hello !‘)

class Son(Father):
    def __init__(self, name, age, hobby):
        super().__init__(name, age)
        self.hobby = hobby

    def say(self):
        super().say()
        print(‘你好 ~‘)

?
super方法不光可以在类的内部使用,也可以在类的外部的使用,在类的外部使用时,super方法不可以省略参数

# 外部使用super
p = Son()
super(Son, p).say()

# 输出结果:
Hello !

Python中多继承的继承顺序

上面已经说过,Python3 中所有的类都是新式类,新建的类没有继承任何类的时候,会默认继承 object 类。
在 Python2中,经典类和新式类并存,新建的类若是没有继承任何类,则这个类为经典类,只有显示地继承了 object 类或其子类,这个类才是新式类~

# Python2中
class C1:        # 经典类
    pass

class C2(C1):   # 经典类
    pass

class C3(object):   # 新式类
    pass

# Python3中
class N1:         # 新式类
    pass

class N2(N1):   # 新式类
    pass

class N3(object):   # 新式类
    pass

?
在Python中支持多继承,新式类和经典类的继承顺序有所差异,以下以钻石继承为例给出示例:

class A(object):
    def fun(self):
        print(‘from A‘)

class B(A):
    def fun(self):
        print(‘from B‘)

class C(A):
    def fun(self):
        print(‘from C‘)

class D(B):
    def fun(self):
        print(‘from D‘)

class E(C):
    def fun(self):
        print(‘from E‘)

class F(D, E):
    # def fun(self):
    #     print(‘from F‘)
    pass

上述多个类的继承关系如下图所示:
技术分享图片

当前环境为Python3,即新式类,可通过内置的__mro__方法查看继承顺序:

f1 = F()
# f1.fun()
print(F.__mro__)

# 输出结果:
(<class ‘__main__.F‘>, <class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.E‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>)

即新式类的继承顺序:F->D->B->E->C->A,广度优先。
那什么是继承顺序呢?就是寻找父类的顺序,例如这里调用 f1对象(F类的实例化对象)的 fun方法,若F类中没有这个方法,就去D类中寻找,若D类中也没有,就去B类中寻找,然后是E类,C类,A类,即按照 MRO列表上 从左到右查找基类~
?
若这里的类是经典类,则继承顺序为:F->D->B->A->E->C ,深度优先~
广度优先 与 深度优先 的区别在于,广度优先算法在查找基类的时候,若之后能找到的,则之后再进行查找,若之后找不到的,现在就去查找。例如,通过E,C也能再次找到A,则先不找A,但是通过E,C不能再次找到B,所以查找D之后就找B,查找B之后不会去查找A,而是在C之后再去查找A~
?
再看如下示例,由于B和C只能通过一条途径找到,所以新式类和经典类的继承顺序一致:
技术分享图片
新式类继承顺序:F->D->B->E->C
经典类继承顺序:F->D->B->E->C

结合super的多继承顺序

若是在钻石继承中用到了 super 关键字,super 会去调用父类的对应方法,但是 super 的本质并不是直接找父类,而是根据调用者的节点位置的广度优先顺序来查找的。即 按照广度优先的继承顺序找到上一个类~
Tip:super 关键字只有在新式类中有,所以肯定是按照广度优先的继承顺序来进行查找的~
?

class A:
    def func(self):
        print(‘A‘)

class B(A):
    def func(self):
        super().func()
        print(‘B‘)

class C(A):
    def func(self):
        super().func()
        print(‘C‘)

class D(B, C):
    def func(self):
        super().func()
        print(‘D‘)

d = D()
d.func()

# 输出结果:
A
C
B
D

上述多个类的继承关系如下图所示:
技术分享图片
当前使用的是新式类,继承顺序为:D->B->C->A(经典类的继承顺序为:F->B->A->C)
?
其中super的调用过程如下:
D类的 func() 方法中的 super().func() 会调用 B类的 func() 方法,B类的 func() 方法中的super().func() 会调用 C类的 func() 方法,C类的 func() 方法中的 super().func() 会调用 A类的 func() 方法~
?
当然在单继承中不会有这样的问题~
?
.................^_^

以上是关于Python面向对象特性 - 继承的主要内容,如果未能解决你的问题,请参考以下文章

19.Python面向对象之:三大特性:继承,封装,多态。

继承 多态 封装 Python面向对象的三大特性

python面向对象三大特性之继承多态

Python之面向对象继承详解以及面向对象三大特性

Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)

Python 面向对象的三大特性:封装,继承,多态