python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python面向对象继承

详析Python面向对象中的继承

作者:搬砖,赞路费

这篇文章主要详析Python面向对象中的继承,类继承作为python的三大特性之一,在我们学习python的时候是必不可少的。使用类继承,能够大大减少重复代码的编写,下文详细内容需要的小伙伴可以参考一下

一 单继承

类继承作为python的三大特性之一,在我们学习python的时候是必不可少的。使用类继承,能够大大减少重复代码的编写。现来记录下,python中关于类继承的一些知识点。
类的继承有单继承,多层继承以及多重继承,先来看看单继承。

1. 继承的基本语法格式如下

#类继承语法格式,B类继承A类
class A():
    类属性
    类方法
    ...
class B(A):
    类属性
    类方法
    ...

单继承的话一般类A是没有继承其他派生类的,只继承了基类。因为在python新式类中,一个类会默认去继承基类object的,基类object是顶级类。

2. 查看类继承情况

class Father():
    #这是父类
    name1 = 'father_name'
    age1 = 'father_age'
    def father_method(self):
        print('我是父亲')

class Son(Father):
    #这是子类
    name2 = 'son_name'
    age2 = 'son_age'
    def son_method(self):
        print('我是孩子')

if __name__ == '__main__':
    A = Father()
    B = Son()
    #单继承
    print(B.__class__.__mro__)
    #或者Son.mro()
    print(Son.mro())

如上:我们定义了一个父类Father,一个子类Son,并且子类Son继承父类Father,它们都有自己的属性和方法。我们可以通过打印B.__ class__.__mro __ 或者Son.mro()来查看Son类的继承情况,如下:

>>>
(<class '__main__.Son'>, <class '__main__.Father'>, <class 'object'>)
[<class '__main__.Son'>, <class '__main__.Father'>, <class 'object'>]

可以看到,Son类确实继承了Father类,并且继承基类object。

3. 继承中的属性和方法

如果一个类继承了另外一个类,那么这个类是可以调用其继承类的属性和方法的(子类可以调用父类的属性和方法),如下

class Father():
    #这是父类
    name1 = 'father_name'
    age1 = 'father_age'
    def father_method(self):
        print('我是父亲')

class Son(Father):
    #这是子类
    name2 = 'son_name'
    age2 = 'son_age'
    def son_method(self):
        print('我是孩子')

    def fun1(self):
        #调用父类属性和方法
        print(self.age1, self.name1)
        self.father_method()

if __name__ == '__main__':
    A = Father()
    B = Son()
    #单继承
    # print(B.__class__.__mro__)
    B.fun1()

结果如下:

>>>
father_age father_name
我是父亲

当子类中的属性名方法名和父类中的属性名方法名同名时,在该子类中会覆盖父类的属性名和方法名(重写)。

class Father():
    #这是父类
    name1 = 'father_name'
    age1 = 'father_age'
    def father_method(self):
        print('我是父亲')

class Son(Father):
    #这是子类
    name1 = 'son_name'
    age1 = 'son_age'
    def son_method(self):
        print('我是孩子')

    def father_method(self):
        #和父类方法同名,将以子类方法为准
        print("与父类方法同名,但是我是子类")

    def son_fun1(self):
        #调用父类属性
        print("子类属性和父类属性同名,以子类为准:", self.name1, self.age1)


if __name__ == '__main__':
    A = Father()
    B = Son()
    #单继承
    # print(B.__class__.__mro__)
    B.father_method()
    B.son_fun1()

输出如下:

>>>
与父类方法同名,但是我是子类
子类属性和父类属性同名,以子类为准: son_name son_age

4. 初始化函数__init__()和 super

上面写的子类和父类都是不需要传参数的,而当我们需要给类传参数时,往往都是要初始化的,下面来看看子类继承父类时,参数初始化的几种情况。

子类无新增参数:

class Father():
    #父类
    def __init__(self, name='张三', age=23):
        self.name = name
        self.age = age

class Son(Father):
    #子类
    def son_fun1(self):
        print(self.name, self.age)

if __name__ == '__main__':
    B = Son()
    B.son_fun1()

输出:

>>>
张三 23

如上,在子类无新增参数时,无需进行__init__ 初始化,直接调用父类的对象属性即可。因为子类Son是继承自父类Father的,所以在调用时会首先去调父类的__init__ 进行初始化

子类有新增参数:

当子类有新增参数时,该怎么初始化呢?先来看看这个对不对

class Father():
    #父类
    def __init__(self, name='张三', age=23):
        self.name = name
        self.age = age

class Son(Father):
    #子类
    def __init__(self, height):
        self.height = height

    def son_fun1(self):
        print(self.height)
        print(self.name, self.age)

if __name__ == '__main__':
    B = Son(170)
    B.son_fun1()

输出:

>>>
AttributeError: 'Son' object has no attribute 'name'
170

上面子类Son新增了一个height参数,然后用__init__ 进行初始化。但是从输出结果可以看出,height参数是正常打印的,打印name和age参数时就报错:子类Son没有属性’name’,因为这个时候就不会去调用父类的__init__ 进行初始化,而是直接调用子类中的__init__ 进行初始化,这时,子类初始化就会覆盖掉父类的__init__ 初始化,从而报错。

正确的初始化有两种方法,如下:

#方法1
def __init__(self, 父类参数1, 父类参数2, ..., 子类参数1, 子类参数2, ...)
    父类名.__init__(self, 父类参数1, 父类参数2, ...)
    self.子类属性 = 子类属性

class Father():
    #父类
    def __init__(self, name='张三', age=23):
        self.name = name
        self.age = age

class Son(Father):
    #子类
    def __init__(self, name, age, height):
        #方法1
        Father.__init__(self, name, age)
        self.height = height

    def son_fun1(self):
        print(self.height)
        print(self.name, self.age)

if __name__ == '__main__':
    B = Son('李四', 24, 170)
    B.son_fun1()

>>>
175
李四 24

从结果可以看出,调用父类初始化后,结果就正常输出。这是在子类__init__初始化的时候,调用了Father.__ init __(self, name, age)对父类对象属性进行了初始化。

现在来看看另外一个初始化方法super()初始化,不过使用super()的时候要注意python的版本,因为python2和python3的使用是不同的,如下

#方法2
def __init__(self, 父类参数1, 父类参数2, ..., 子类参数1, 子类参数2, ...): 
    #python2的super初始化
    super(子类名, self).__init__(父类类参数1, 父类参数2, ...)
    self.子类属性 = 子类属性
    #python3的super初始化
    super().__init__(父类类参数1, 父类参数2, ...)
    self.子类属性 = 子类属性


class Father():
    #父类
    def __init__(self, name='张三', age=23):
        self.name = name
        self.age = age

class Son(Father):
    #子类
    def __init__(self, name, age, height):
        #方法2
        #python2的super初始化
        super(Son, self).__init__(name, age)
        self.height = height
        #python3的super初始化
        # super().__init__(name, age)
        #或者 super(Son, self).__init__(name, age)
        
    def son_fun1(self):
        print(self.height)
        print(self.name, self.age)

if __name__ == '__main__':
    B = Son('李四', 24, 175)
    B.son_fun1()

结果:

>>>
175
李四 24

上面使用的是super(Son, self).__ init __ (name, age) 来对父类对象属性进行初始化。不过这是要区分python版本的,在python3中使用super(Son, self).__ init__(name, age)或者super().__ init__(name, age)都是可以的,但是在python2的版本中,要使用super(Son, self).__ init__(name, age)来进行初始化,而且父类必须继承object,否则就会报错。

二 多层继承

上面的记录的是单继承,现在来看看多层继承。多层继承:子类继承自多个父类,父类只继承自object顶级类。

class Father():
    def __init__(self):
        print("enter father")
        print("leave father")

    def fun(self):
        print("这是father")

class Mother():
    def __init__(self):
        print("enter mother")
        print("leave mother")

    def fun(self):
        print("这是mather")

class Son(Father, Mother):
    def __init__(self):
        print("enter son")
        super().__init__()
        print("leave son")

if __name__ == '__main__':
    B = Son()
    B.fun()
    print(Son.mro())

输出:

>>>
enter son
enter father
leave father
leave son
这是father

继承关系:

[<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class 'object'>]

这里有两个父类Father,Mother,一个子类Son,子类Son继承了类Father和Mother,继承顺序是Father在前,Mother在后。从上面输出结果来看,子类初始化过程是先进入子类Son的 __ init __方法,其次调用super(). __init __()进行父类对象属性初始化,最后初始化完成。
从结果可以知道上面super初始化调用的是父类Father的 __ init __方法。
。也可以看出,类Father和类Mother都有一个fun方法(同名),但在子类调用时调的是父类father的fun方法。这是为什么呢?

这里就涉及到super的继承机制,即super会根据MRO机制,从左到右依次调用父类的属性和方法, 当父类中有同属性名,同方法名时,以靠左的那个父类为准。

下面我们把Father和Mother位置换一下,如下:

class Father():
    def __init__(self):
        print("enter father")
        print("leave father")

    def fun(self):
        print("这是father")

class Mother():
    def __init__(self):
        print("enter mother")
        print("leave mother")

    def fun(self):
        print("这是mather")

class Son(Mother, Father):#这里变动,变换Father和Mother的位置
    def __init__(self):
        print("enter son")
        super().__init__()
        print("leave son")

if __name__ == '__main__':
    B = Son()
    B.fun()
    print(Son.mro())

结果:

>>>
enter son
enter mother
leave mother
leave son
这是mather
[<class '__main__.Son'>, <class '__main__.Mother'>, <class '__main__.Father'>, <class 'object'>]

继承关系:

>>>
[<class '__main__.Son'>, <class '__main__.Mother'>, <class '__main__.Father'>, <class 'object'>]

可以看出,当Mother在前时,调用的就是类Mather的初始化方法和fun方法。所以在多层继承时,一定要注意父类的位置顺序。

三 多重继承

其实super的产生就是用来解决多重继承问题的,什么是多重继承呢?即,子类继承多个父类,而父类又继承自其它相同的类,这种又被称为菱形继承或者砖石继承,

如下:

     A
   /   \
  /     \
 B       C
  \     /
   \   /
     D

先来看看如下的一个菱形继承:

class A():
    def __init__(self):
        print("enter A")
        print("leave A")

class B(A):
    def __init__(self):
        print("enter B")
        # super().__init__()
        A.__init__(self)
        print("leave B")

class C(A):
    def __init__(self):
        print("enter C")
        # super().__init__()
        A.__init__(self)
        print("leave C")

class D(B, C):
    def __init__(self):
        print("enter D")
        B.__init__(self)
        C.__init__(self)
        # super().__init__()
        print("leave D")

if __name__ == '__main__':
    d = D()

输出结果:

>>>
enter D
enter B
enter A
leave A
leave B
enter C
enter A
leave A
leave C
leave D
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

上面用的是父类名. __ init__ () 来进行子类初始化的,但是从结果来看,类A的初始化方法是被执行了两次的,一次是类B的调用,一次是类C的调用。也可以看出子类D的初始化 __ init __ 调用了B.__ init __() 和C. __init __() 。现在D类继承的父类只有B和C,但是当代码比较复杂,继承的类比较多时,就得的一个一个写,如果类B和类C也是继承自多个类,那它们的初始化方法也得重新写,这样就比较繁琐,也容易出错。

下面来使用super初始化父类看看:

class A():
    def __init__(self):
        print("enter A")
        print("leave A")

class B(A):
    def __init__(self):
        print("enter B")
        super().__init__()
        print("leave B")

class C(A):
    def __init__(self):
        print("enter C")
        super().__init__()
        print("leave C")

class D(B, C):
    def __init__(self):
        print("enter D")
        super().__init__()
        print("leave D")

if __name__ == '__main__':
    d = D()
    print(D.mro())

输出结果:

enter D
enter B
enter C
enter A
leave A
leave C
leave B
leave D
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

可以看到输出结果是不一样的,类A的初始化只执行了一次,而且当D类有新增父类时,初始化方法也不用动,从而避免重写初始化方法出错,代码也变得整洁。
这里还是和super的继承机制有关,上面说过当子类继承自多个父类时,super会根据MRO机制,从左到右依次调用父类的属性和方法,而且使用super初始化父类时,会一次性初始化所有的父类,所以上面的类A初始化方法不会被调用两次。

所以在类的多重继承中,一般建议使用super来初始化父类对象属性。

到此这篇关于详析Python面向对象中的继承的文章就介绍到这了,更多相关Python面向对象继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文