一、类的继承
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__(),这么做的目的就是,让子类可以继承父类在初始化方法中定义的属性。