本小节主要梳理类的继承和多态,继承包含三种形式:单继承、多层继承、多重继承。
环境说明:Python 3.6、windows11 64位
基础语法如下,class B(A)
表示的含义就是B 继承A ,A 是B 的父类。
class A():
name='Xindata'
class B(A):
pass
子类继承父类之后,父类的所有属性和方法,子类都可以直接调用。
class A():
name='Xindata'
@classmethod
def func1(cls):
print('My name is %s.'%cls.name)
class B(A):
pass
# 子类调用父类属性和方法
print(B.name)
B.func1()
# 结果为:
# Xindata
# My name is Xindata.
子类继承父类之后,父类的所有实例属性和方法,子类实例化之后都可以调用。但是子类直接调用父类实例属性和方法则会报错,其实这点和父类本身调用父类实例属性和方法类似,实例的属性和方法不属于类本身,要实例化之后才能进行调用。
如果相关的基础不够扎实可以看看上一篇《类和实例》2.2.2 实例化调用部分内容。
class A():
name='Xindata'
@classmethod
def func1(cls):
print('My name is %s.'%cls.name)
def func2(self):
print('I\'m instance.')
class B(A):
pass
# 子类调用父类属性和方法
print(B.name) # 结果为:Xindata
B.func1() # 结果为:My name is Xindata.
# B.func2()
# 子类实例化后调用父类和父类的实例的属性和方法
b = B()
print(b.name) # 结果为:Xindata
b.func1() # 结果为:My name is Xindata.
b.func2() # 结果为:I'm instance.
继承带__init__()
函数的父类,为了获得父类的初始化属性和方法,可以通过调用父类的初始函数实现,如下代码第8行,直接调用类A
的初始函数,这样子对类B
进行实例化的时候,就可以调用父类的初始化属性和方法,同时也可以在子类的初始函数新增属性。
class A():
def __init__(self,name):
self.name = name
def func1(self):
print('My name is %s.'%self.name)
class B(A):
def __init__(self,name):
A.__init__(self,name) # 调用类A 的初始函数
self.eye = 'black'
b=B('Xindata')
b.func1() # 结果为:My name is Xindata.
调用父类的初始化属性和方法,除了通过调用父类的初始化函数,还可以通过super()
函数实现。使用super()
函数时注意不需要给参数self
传递值。
super() 函数是用于调用父类(超类)的一个方法。主要是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承(后两小节介绍),会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。
class A():
def __init__(self,name):
self.name = name
def func1(self):
print('My name is %s.'%self.name)
class B(A):
def __init__(self,name):
super().__init__(name) # 注意,不需要参数self
self.eye = 'black'
b=B('Xindata')
b.func1() # 结果为:My name is Xindata.
接下来看看一些从属关系,借助函数isinstance()
和issubclass()
辅助理解。
isinstance()
判断参数1的实例是否是参数2的类的实例。语法:isinstance([实例名], [类名])
issubclass()
判断参数1的类是不是参数2的类的子类。语法:issubclass([类名1], [类名2])
子类的实例属于父类,父类的实例不属于子类。
class A():
name='Xindata'
@classmethod
def func1(cls):
print('My name is %s.'%cls.name)
def func2(self):
print('I\'m instance.')
class B(A):
pass
a = A()
b = B()
# 实例和类
print(isinstance(a,A)) # True,父类实例属于父类
print(isinstance(a,B)) # False,父类实例不属于子类
print(isinstance(b,B)) # True,子类实例属于类子
print(isinstance(b,A)) # True,子类实例属于父类
# 子类和父类,如果是实例会报错
print(issubclass(B,A)) # True,B是A的子类
print(issubclass(A,B)) # False
# print(issubclass(a,B)) # 报错
# print(issubclass(b,A)) # 报错
多层继承,或者叫多级继承,就是父类继承祖父类,子类继承父类,孙类继承子类,一级一级传参。最后的一个类继承了前面所有类的属性和方法。
class A():
name='Xindata'
@classmethod
def func1(cls):
print('My name is %s.'%cls.name)
def func2(self):
print('I\'m instance.')
class B(A):
@classmethod
def func3(cls):
print('class B')
class C(B):
@classmethod
def func4(cls):
print('class C')
print(C.name) # 结果为:Xindata
C.func1() # 结果为:My name is Xindata.
C.func3() # 结果为:class B
C.func4() # 结果为:class C
多层继承遵循就近原则,如果父类和祖父类都有func1()
方法,则子类从父类获取相关的属性和方法;如果父类没有func1()
方法,再到祖父类中获取,如果祖父类也没有相关方法则报错。如果是子类也含有func1()
方法,则直接取子类的。
注意:如果父类和祖父类同时都有某个属性或方法,祖父类的相关属性方法会被父类覆盖,此时如果某属性在父类没有,但在祖父类中有,子类也不能调用该属性,具体看看下例的name
属性,该属性仅在祖父类A
存在,但是在父类B
所在的方法被重写,在调用类C
时,可以看做A.func1()
不存在过,所以C.name
不会到A
的func1()
方法中寻找属性name
,而是返回错误:ttributeError: type object ‘C’ has no attribute ‘name’。
class A():
@classmethod
def func1(cls):
cls.name = 'class A.func1.name'
cls.hair = 'class A.func1.hair'
print('class A.func1')
@classmethod
def func2(cls):
print('class A.func2')
class B(A):
@classmethod
def func1(cls): # 重写父类A 的func1()函数,新增功能
cls.skill_1 = 'class B.func1.skill_1'
cls.hair = 'class B.func1.hair'
print('class B.func1')
class C(B):
@classmethod
def func3(cls):
print('class C.func3')
C.func1() # 结果为:class B.func1
C.func2() # 结果为:class A.func2
C.func3() # 结果为:class C.func3
print(C.hair) # 结果为:class B.func1.hair
print(C.skill_1) # 结果为:class B.func1.skill_1
# print(C.name) # 报错
类的多重继承,即同时继承多个类。基本语法:
class A():
pass
class B():
pass
class C(B,A):
pass
多重继承也有一个“就近原则”,就是靠近左侧的类优先调用。在调用子类的某个属性或方法的时候,如果子类本身没有,则从继承的父类中,依次从左到右找寻相关的属性和方法。
当继承的类中有相同的属性或方法的时候,多重继承和多层继承一样有一个先后调用的顺序,只调用了排在靠左侧的类的相关属性和方法,忽略靠右侧的类的相关属性和方法。如下代码,父类B
和父类A
都有func1()
方法,由于类B
在左侧,所以子类C
优先继承父类B
的方法func1
,而忽略类A
的方法func1
。而类属性name
在类A
的方法func1
中,所以类C
没有继承到类A
的属性name
,当执行C.name
时报错:AttributeError: type object ‘C’ has no attribute ‘name’。
class A():
@classmethod
def func1(cls):
cls.name = 'class A.func1.name'
cls.hair = 'class A.func1.hair'
print('class A.func1')
@classmethod
def func2(cls):
print('class A.func2')
class B():
@classmethod
def func1(cls):
cls.skill_1 = 'class B.func1.skill_1'
cls.hair = 'class B.func1.hair'
print('class B.func1')
class C(B,A):
@classmethod
def func3(cls):
print('class C.func3')
C.func1() # 结果为:class B.func1
C.func2() # 结果为:class A.func2
C.func3() # 结果为:class C.func3
print(C.hair) # 结果为:class B.func1.hair
print(C.skill_1) # 结果为:class B.func1.skill_1
print(C.name) # 报错
多态,我的理解就是在不同的类中使用同样的方法名称,实现某一类功能,调用相关方法时,解释器把调用分派给正确的方法。
不管对象属于哪个类,也不管声明的具体接口是什么,只要对象实现了相应的方法,函数就可以在对象上执行操作。
具体看以下代码,对Animal
、Cat
、Dog
三个类分别实例化然后分别调用各个类的run()
方法,虽然名称相同,但是解释器可以把相关的调用给正确的方法,打印出正确的值。
class Animal():
def run(self):
print('Anaimal running')
class Cat(Animal):
def run(self):
print('Cat running')
class Dog(Animal):
def run(self):
print('Dog running')
A = Animal()
C = Cat()
D = Dog()
for animal in [A,C,D]:
animal.run()
# 结果为:
# Anaimal running
# Cat running
# Dog running
本节主要的知识点框架如下:
<下节预告:模块和包>
- End -