第12章–继承的优缺点
本章探讨继承和子类化,重点是说明对 Python 而言尤为重要的两个细节:
• 子类化内置类型的缺点–例如写一个继承list的列表类
• 多重继承和方法解析顺序
子类化内置类型
结论:不要子类化内置类型,而是选择子类化collections模块对应的类比如UserDict UserList UserStr
原因:内置类型比如list、dict使用c语言编写的,内置类型不会调用用户定义的类覆盖的特殊方法
内置类型的子类覆盖的方法会不会隐式调用,CPython 没有制定官方规则。基本上,内置类型的方法不会调用子类覆盖的方法。例如,dict 的子类覆盖的
getitem() 方法不会被内置类型的 get() 方法调用。
具体代码对比
from collections import UserDict
class ADict(dict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
class BDict(UserDict):
def __setitem__(self, key, value):
super().__setitem__(key,[value]*2)
ad=ADict(name='jason')
print(ad) #{'name': 'jason'}
ad['age']=64
print(ad) # {'name': 'jason', 'age': [64, 64]}
ad.update(job='student')
print(ad) # {'name': 'jason', 'age': [64, 64], 'job': 'student'}
bd=BDict(name='jason')
print(bd) # {'name': ['jason', 'jason']}
bd['age']=64
print(bd) # {'name': ['jason', 'jason'], 'age': [64, 64]}
bd.update(job='student')
print(bd) # {'name': ['jason', 'jason'], 'age': [64, 64], 'job': ['student', 'student']}
ADict和BDict的setitem方法是一样的,把传进来的value设置成列表形式,ADict继承内置类型dict,BDict继承自UserDict,
ADict类型的init和update函数不会发现用户定义的setitem函数,而是继续用dict的setitem函数,导致了value的值的类型不一致,有的是非列表有的是列表,这不是用户愿意看到的
BDict类型的init和update函数都用到了用户定义的setitem函数,整个类实例的行为看上去都是一致的
多重继承和方法解析顺序
python支持多重继承
super()调用父类的方法会根据方法解析顺序(MRO)的顺序来调用方法
每个类都有一个内置属性__mro__,是一个元祖,会按照MRO列出各个父类,调用方式是CLassName.__mro__,而不是类的实例
如果想调用特定的父类的方法,可以通过ParentClass.fun(child_instance),把子类的实例传给父类的方法
class A():
def fun(self):
print("A fun of ",self)
class B(A):
def fun(self):
print("B fun of ",self)
class C(A):
def fun(self):
print("C fun of ",self)
class D(B,C):
def fun(self):
super().fun()
d=D()
d.fun() # B fun of <__main__.D object at 0x00000286500AD438>
print("MRO of Class D ",D.__mro__)
# MRO of Class D (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
C.fun(d) # C fun of <__main__.D object at 0x0000020B7CA3D470>
A.fun(d) # A fun of <__main__.D object at 0x000001FE08FED470>