Python系列教程(四十二):类的继承

  一、类的继承  

1、什么是类的继承

    继承作为类的三大特性之一,其重要性不言而喻,在一些大型项目中,构造错综复杂的类关系的时候,用的非常广泛。继承可以最大程度上体现类与类之间的关系,利用这种关系,可以做到代码复用,简化代码的作用。

    而怎么去认识继承这个特性呢,其实也非常好理解,它跟我们人类社会中的继承特性非常类似,比如人类的继承,可以继承父母的一部分特征,但是也可以有自己的特征,体现在python中类的继承,就是子类继承父类的属性和方法,而子类也可以拥有属于自己的属性和方法。

    所以,类的继承指的是:属性和方法的继承。所以,如果当某个类是从其他类继承而来的话,那么这个类它天生就拥有父类的属性和方法。

    继承的类叫做父类,也称为基类、超类,而被继承的叫做子类,也称为派生类。

2、继承的定义

    格式如下:

class 子类名(基类1,[基类2]):
    pass

    格式说明:

    ① 一个子类可以继承多个基类。

    ② python3中,如果没有显式指定继承的基类,则object是所有类的基类

    ③ python2中,如果没有显式指定继承的基类,则需要指定object为基类。

3、关于继承的特殊属性和方法

    __base__:查看类的基类

    __bases__:查看基类的元组

    __mro__:查看方法的查找顺序,也就是所有父类按照继承顺序组成的元组

    mro():方法同上

    __subclasses__():查看类的子类列表

class A:
    def foo(self):
        print('called A.foo()')

class B(A):
    pass

class C(A):
    def foo(self):
        print('called C.foo()')

class D(B, C): 
    pass

#查看类的基类    
>>> A.__base__    
object        
>>> B.__base__    
__main__.A    

#查看类的基类元组    
>>> B.__bases__    
(__main__.A,)    
>>> D.__bases__    
(__main__.B, __main__.C)

#查看类的子类列表
>>> A.__subclasses__()
[__main__.B, __main__.C]

#查看方法的查找顺序
>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)

4、继承中的访问控制和查找顺序

class A:
    def __init__(self, age, name):
        self.age = age
        self.__name = name
    
    def call(self):
        print("call.....")
        
    def __print(self):
        print("print......")
        
class B(A):
    pass
    
>>> b = B(28,'jimmy')    

#访问公有成员:正常
>>> b.age    
28
>>> b.call()    
call.....

#访问私有成员:报错
>>> b.__name    
AttributeError: 'B' object has no attribute '__name'
>>> b.__print()
AttributeError: 'B' object has no attribute '__print'

#查看实例和父类的所有秘密:__dict__
>>> b.__dict__
{'age': 28, '_A__name': 'jimmy'}
>>> A.__dict__
mappingproxy({'__module__': '__main__',
              '__init__': <function __main__.A.__init__(self, age, name)>,
              'call': <function __main__.A.call(self)>,
              '_A__print': <function __main__.A.__print(self)>,
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              '__doc__': None})

#用黑科技访问私有成员
>>> b._A__name
'jimmy'
>>> b._A__print()
print......

    示例说明:

    ① 继承时,公有成员(属性和方法),子类和实例都可以直接访问。

    ② 继承时,私有成员(属性和方法),子类和实例都不可以直接访问。

    ③ 类的继承不是把父类的方法和属性在子类中又重新复制一次,而是当实例调用的时候,如果子类没有,会根据查找顺序,去父类找到这个方法或者属性。

    ④ 属性的查找顺序:实例.__dict__ > 子类.__dict__ > 父类.__dict__

    ⑤ 关于实例或者类的所有成员的秘密,其实都隐藏在特殊属性__dict__这个字典当中,从这个字典当中,我们可以看到关于它的所有成员,包括私有成员,本质就是前面讲到的,重命名了而已,所以我们可以利用这个特点访问私有属性,但是不建议这么做。

5、子类方法的重写和覆盖

    子类除了从父类继承而来的属性和方法外,还有一部分属于自己特有的属性,这些特有的属性就需要在子类中重写或者覆盖。

    覆盖:指的是在子类重新定义已经在父类中定义过的方法。

    重写:指的是在子类新定义未在父类中定义过的方法。

class A:
    def __init__(self, age, name):
        self.age = age
        self.__name = name
    
    def call(self):
        print("call.....")
        
    def __print(self):
        print("print......")
        
class B(A):
    #覆盖父类的方法
    def call(self):
        print("B_call.......")
    
    #重写父类没有的方法
    def show(self):
        print("show..........")
        
>>> b = B(28,'jimmy')        

#调用的是子类的方法        
>>> b.call()        
B_call.......        
>>> b.show()        
show..........

#子类的字典中,已经有call方法,所以不会再去调用父类的。
B.__dict__
mappingproxy({'__module__': '__main__',
              'call': <function __main__.B.call(self)>,
              'show': <function __main__.B.show(self)>,
              '__doc__': None})

    示例说明:

    关于子类方法的重写或者覆盖,本质上还是属性的查找顺序,当子类没有对应的方法时,会去父类找,而当重写或者覆盖后,子类已经有对应的方法了,就会从子类调用,而且在子类找到之后,就不会再去父类找了,这就是覆盖的原理。

6、初始化方法的继承

class A:
    def __init__(self, age):
        self.age = age
        
class B(A):
    def __init__(self,name):
        self.name = name
        
    def call(self):
        print(self.name)
        print(self.age)
        
>>> b = B("jimmy")        
>>> b.call()        
AttributeError: 'B' object has no attribute 'age'

    示例问题点说明:

    从上面的例子我们可以看到,子类继承父类的时候,覆盖了初始化方法,这样会导致一个什么样的问题呢,从上面的说明中,我们知道,一旦子类覆盖了父类的方法,那么父类的初始化方法将不再被调用,所以在父类初始化方法中定义的属性,子类就无法拥有,所以上面的示例中,子类打印父类的属性就会报错。

class A:
    def __init__(self, age):
        self.age = age
        
class B(A):
    def __init__(self,name,age):
        super().__init__(age)
        self.name = name
        
    def call(self):
        print(self.name)
        print(self.age)

b = B("jimmy",28)        
b.call()        
jimmy
28

    初始化方法的继承:

    ① 如果子类没有覆盖父类的初始化方法,那么可以忽略。

    ② 如果子类覆盖了父类的初始化方法,那么子类的初始化方法要显式的调用父类的初始化方法:super().__init__(),这么做的目的就是,让子类可以继承父类在初始化方法中定义的属性。

Jimmy's Blog ,版权所有丨如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权,转载请注明转自:https://www.xjimmy.com/python-42-inheritance.html

Leave a Comment