类和对象 (part 2)
本节主要介绍 类和对象的构造函数、重写、钻石继承、Mixin及案例源码剖析(原视频P62-63)\
构造函数
之前我们在函数章节里说,函数是可以通过参数来进行个性化定制的。类在实例化的时候其实也是支持个性化定制对象的。
定义类的时候定义一个构造函数,构造函数:__init__()
。
我们只需要在类中定义 __init__()
方法,那么就可以在实例化对象的同时实现个性化的定制。
实例:
class C:
def __init__(self,x,y): # 这里写上构造函数,构造函数 __init__ 第一个参数是 self(必须)。之后两个参数是实例化对象的两个参数 x,y
self.x = x # 注意这里 等号左边的 self.x 是绑定到实例化对象里面的x属性,等号右边的x是传进来的 x 参数
self.y = y
def add(self):
return self.x + self.y
def mul(self):
return self.x * self.y
c1 = C(2,3) # 即我们在实例化的时候,就顺带着把对象的两个变量给安排进去了
print(c1.add())
print(c1.mul())
c1.__dict__ #我们这里内省一下,可以看到传入的参数都成了c1的属性
5
6
{'x': 2, 'y': 3}
# 传入 不同参数 测试一下:
c2 = C(4,5)
print(c2.add())
print(c2.mul())
c2.__dict__
9
20
{'x': 4, 'y': 5}
重写 与 钻石继承
重写
如果我们对于父类的某个属性和方法不满意的话,完全可以写一个同名的属性或方法对其进行覆盖。这种行为我们就称之为子类对父类的重写。
实例:
# 我们重写一个类D(继承自上面的类C),但不满足其只有两个参数,我们要多写一个参数
class D(C):
def __init__(self,x,y,z):
C.__init__(self,x,y) # 这一句等于:
# self.x=x
# self.y=y
self.z=z
def add(self):
return C.add(self)+self.z
def mul(self):
return C.mul(self)*self.z
d1 = D(2,3,4)
print(d1.add())
print(d1.mul())
d1.__dict__
9
24
{'x': 2, 'y': 3, 'z': 4}
这种直接通过类名访问类里边的方法的做法,称之为调用未绑定的父类方法。 这种方法比较直接但没错,有时候可能会造成钻石继承的问题!
钻石继承
实例:
class A:
def __init__(self):
print('A.__init__ 调用一次')
# we define other 2 classes, B1 and B2, and they all inherit from A
class B1(A):
def __init__(self):
A.__init__(self)
print('B1.__init__ 调用一次')
class B2(A):
def __init__(self):
A.__init__(self)
print('B2.__init__ 调用一次')
# we define class C, which inherit from B1 and B2 at the same time
class C(B1,B2):
def __init__(self):
B1.__init__(self) # 我么这里调用一下类C父类的 __init__
B2.__init__(self)
print('C.__init__ 调用一次')
# we see what happened
c1=C()
A.__init__ 调用一次
B1.__init__ 调用一次
A.__init__ 调用一次
B2.__init__ 调用一次
C.__init__ 调用一次
上面的示例就是一个钻石继承发生的例子,我们可以发现当我们实例化 c1=C()
的时候,A.__init__
被重复调用了两次。
究其原因:类 C 是同时继承自 类B1 和 类 B2 的。类B1 和 类 B2 又是继承自 类A 的。所以当我们 类C 去调用 类B1 和 类 B2 的构造函数的时候,类A的构造函数也会被跟着调用两次(B1一次,B2一次)。
与之对应,可以调用 super()
函数 来解决钻石继承的问题。
super()
函数
super()
函数 能够在父类中搜索指定的方法,并自动绑定好 self
参数。
我们修改一下上面 类B1,类B2,类C的代码:
class B1(A):
def __init__(self):
super().__init__() # 这里我们用 super(),不再用未绑定的父类方法。
# 因为我们有写清继承关系 B1是继承自A,super()自动会去搜寻父类。
# 同时 self 也不用显示的传入了, super() 会解决
print('B1.__init__ 调用一次')
class B2(A):
def __init__(self):
super().__init__()
print('B2.__init__ 调用一次')
class C(B1,B2):
def __init__(self):
super().__init__() # 这里不用去写 B1 B2 了,它自己会去遍历
print('C.__init__ 调用一次')
# we see what happened
c1=C()
A.__init__ 调用一次
B2.__init__ 调用一次
B1.__init__ 调用一次
C.__init__ 调用一次
此时结果就不会重复的调用了,所以我们使用 super()
函数去查找父类的方法,他就会自动按照 MRO 顺序去搜索父类的相关方法,并且自动避免重复调用的问题。\
MRO
顺序
MRO (method resolution order) 顺序(方法解析顺序): 在我们之前将多继承的时候说过,如果出现同名的属性或方法,Python 会有一个明确的查找覆盖的顺序,这个顺序就是 MRO 顺序。
如果我们要查找一个类的 MRO 顺序,有两种方法:
法1:
mro()
方法
C.mro()
[__main__.C, __main__.B1, __main__.B2, __main__.A, object]
其中 object
是所有类的基类,就算我们不写,也会被隐式的继承。
B1.mro()
[__main__.B1, __main__.A, object]
__mro__
属性
B1.__mro__
(__main__.B1, __main__.A, object)
Mixin 案例源码剖析
Mixin 这个概念就是 mix-in,是一种设计模式(设计模式就是利用编程语言已有的特性,针对面向对象开发过程中反复出现的问题,而设计出来的解决方案)。
Mixin 示例:
原来代码:
class Animal: # 我们先定义了一个 Animal 类
def __init__(self,name,age): # Animal 类 有构造函数,构造函数需要传入 name 和 age
self.name = name
self.age = age
def say(self):
print(f'my name is {self.name}, now I am {self.age} years old')
class Pig(Animal): # 定义 Pig 类 继承 Animal 类
def special(self):
print('I am doing xxx')
p1 = Pig('A',5)
p1.say()
p1.special()
my name is A, now I am 5 years old
I am doing xxx
我们现在想加一个功能,让 p1 有飞的功能。其实可以写一个 飞 的功能,让 Pig 去继承他,继承一个类就可以使用类里面的方法和属性。
class Flymixin: # mixin 不是必要的,这里写上只是加以区分
def fly(self):
print('I am flying')
# 使用多继承,插入到 Pig 类里
class Pig(Flymixin, Animal):
def special(self):
print('I am doing xxx')
p1 = Pig('A',5)
p1.say()
p1.special()
p1.fly()
my name is A, now I am 5 years old
I am doing xxx
I am flying
这其实就是游戏外挂的思路。下面我们有一个更复杂的例子,加深我们理解这个技巧:
class Displayer:
def display(self, message):
print(message)
class LoggerMixin:
def log(self, message, filename='logfile.txt'):
with open(filename,'a') as f:
f.write(message)
def display(self, message):
super().display(message)
self.log(message)
class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message,filename='subclasslog.txt')
subclass = MySubClass()
subclass.display('this is a test')
this is a test
运行结果除了打印了 :this is a test,还生成了一个 ‘subclasslog.txt’ 的文件里面内容:
this is a test
详解:
subclass = MySubClass()
subclass.display('this is a test')
首先,类 MySubClass
实例化一个对象 subclass
。调用 display()
方法,参数 'this is a test'
。
那么调用的 display()
方法 是谁的?
首先不可能是自己 MySubClass
的,因为里面没有这个方法。于是我们要从父类里面查找,这里父类有两个一个是 LoggerMixin
(一般我们看到起名以Mixin结尾的就是Mixin 设计模式,是为了添加某个功能后期插上去的(想象成游戏外挂));另一个父类是 Displayer
。
根据先左后右的原则, Python会先去类 LoggerMixin
里查找display()
方法,然后找到:
class LoggerMixin:
......
def display(self, message):
super().display(message)
self.log(message)
这里面的 display()
方法带 message
参数,就是说这里会把 'this is a test'
传递。这里方法实现的第一句,super().display(message)
,super()
就会去父类里面查找对应的方法。但是其实这里 类LoggerMixin 没有写父类,所以默认应该是 Object。
但是 Object 作为一个总的基类,是不会有 display()
方法的。这里是因为,super()
函数是严重依赖 MRO 顺序的,我们可以看一下 MySubClass
的 MRO 顺序:
MySubClass.mro()
[__main__.MySubClass, __main__.LoggerMixin, __main__.Displayer, object]
所以我们在类 LoggerMixin 里调用 super()
函数,它会先去类 Displayer 里查找,而不是 object。所以:
class LoggerMixin:
......
def display(self, message):
super().display(message)
self.log(message)
第一句 super().display(message)
就是去调用类 Displayer 里面的display()
方法。
第二句 self.log(message)
,这里面 log()
方法是谁的?这里取决于 self
。这里 self
就是 subclass
这个对象本身(绑定)。subclass
是 类MySubClass 的实例化对象。且 类MySubClass 里面刚好有 log()
方法:
class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message,filename='subclasslog.txt')
这里面又有 super()
函数,那么按照 MRO 原则去 LoggerMixin 里找:
class LoggerMixin:
def log(self, message, filename='logfile.txt'):
with open(filename,'a') as f:
f.write(message)
这里 log()
方法就是创建一个文件,并追加保存 message 信息。
附言:
题目:Self-study Python Fish-C Note-19 P62-P63
本文为自学B站上鱼C的python课程随手做的笔记。一些概念和例子我个人为更好的理解做了些查询和补充
因本人水平有限,如有任何问题,欢迎大家批评指正!
原视频链接:https://www.bilibili.com/video/BV1c4411e77t?p=8