Python面向对象继承

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Python面向对象继承

搬砖,赞路费   2022-05-23 我要评论

一 单继承

类继承作为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来初始化父类对象属性。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们