第12章:继承的优缺点
12.1,子类化内置类型很麻烦
在Python2.2之前,内置类型(如list或dict)不能子类化。在Python2.2.之后,内置类型可以被子类化了,但是有个重要的注意事项:内置类型(使用C语言编写)不会调用类覆盖的特殊方法。
In [194]: class DoppelDict(dict): ...: def __setitem__(self, key, value): ...: super().__setitem__(key, [value] * 2) ...: In [195]: dd = DoppelDict(one=1) In [196]: dd Out[196]: {'one': 1} In [197]: dd['two'] = 2 In [198]: dd Out[198]: {'one': 1, 'two': [2, 2]} In [199]: dd.update(three=3) In [200]: dd Out[200]: {'one': 1, 'two': [2, 2], 'three': 3} In [201]:
通过书中的代码,可以看出定义的__setitem__只有在赋值的时候产生的效果,另外的初始化,升级字典都没有使用修改的__setitem__方法,用了超类的原始方法。
在这个糟糕的局面中__missing__方法却能按预期方式工作,不过这只是特例。
内置方法的类如果调用了其他类的方法,如果被覆盖,也不会被调用。
In [202]: class AnswerDict(dict): ...: def __getitem__(self, key): ...: return 42 ...: In [203]: ad = AnswerDict(a='foo') In [204]: ad['a'] Out[204]: 42 In [205]: d = {} In [206]: d.update(ad) In [207]: d['a'] Out[207]: 'foo' In [208]: d Out[208]: {'a': 'foo'} In [209]:
这个应该更加直观,因为你只在自己的类里面定义了__getitem__,所有当另外对象调用你(这里是升级),并不会使用你修改的方法,还是使用父类list的方法。
直接子类化内置类型(如dict、str、list),因为内置类型的方法通常会忽略用户覆盖的方法。不要子类化内置类型,
应该子类化这三个模块
In [211]: collections.UserDict
Out[211]: collections.UserDict
In [212]: collections.UserList
Out[212]: collections.UserList
In [213]: collections.UserString
Out[213]: collections.UserString
In [219]: import collections ...: ...: class DoppelDict2(collections.UserDict): ...: ...: def __setitem__(self, key, value): ...: return super(DoppelDict2, self).__setitem__(key, [value] *2) ...: ...: def __getitem__(self, item): ...: ...: return self.data[item] * 2 ...: In [220]: In [220]: dd = DoppelDict2(one=1) In [221]: dd Out[221]: {'one': [1, 1]} In [222]: dd['one'] Out[222]: [1, 1, 1, 1] In [223]: dd.get('one') Out[223]: [1, 1, 1, 1] In [224]: dd.update(three=3) In [225]: dd Out[225]: {'one': [1, 1], 'three': [3, 3]} In [226]:
前面说过,字典的数据在self.data里面,我自己添加的__getitem__属性。
In [226]: class AnswerDict2(UserDict): ...: def __getitem__(self, key): ...: return 42 ...: ...: In [227]: ad = AnswerDict2(a='foo') In [228]: ad['a'] Out[228]: 42 In [229]: ad Out[229]: {'a': 'foo'} In [230]: d = dict() In [231]: d.update(ad) In [232]: d Out[232]: {'a': 42} In [233]: d['a'] Out[233]: 42
通过这个可以发现,其他对象在调用update的时候,会调用ad的__getitem__方法,由于继承的是UserDict,所有用了修改后的方法。
12.2多重继承和方法解析顺序。
class A(object): def ping(self): print('ping', self) class B(A): def pong(self): print('pong', self) class C(A): def pong(self): print('PONG', self) class D(B, C): def ping(self): super(D, self).ping() print('post-ping', self) def pingpong(self): self.ping() super().ping() self.pong() super().pong() C.pong(self)
In [254]: d = D() In [255]: d.pong() pong <__main__.D object at 0x10e0a4cd0> In [256]: C.pong(d) PONG <__main__.D object at 0x10e0a4cd0> In [257]: D.__mro__ Out[257]: (__main__.D, __main__.B, __main__.C, __main__.A, object) In [258]:
上面执行了两种方式,一种是用实例执行父类的pong,按照__mro__的顺序执行。
还有一种是直接通过类属性执行,显示执行,这个有点强奸的感觉。
一般委托父类执行,用super()比较好。
In [259]: d.ping() ping <__main__.D object at 0x10e0a4cd0> post-ping <__main__.D object at 0x10e0a4cd0>
第一行输出,是根据__mro__一层层寻找,直接找到A的类里面的这个属性执行了。
...: def pingpong(self): ...: self.ping() ...: super().ping() ...: self.pong() ...: super().pong() ...: C.pong(self)
结果
d.pingpong() ping <__main__.D object at 0x10e0a4cd0> post-ping <__main__.D object at 0x10e0a4cd0> ping <__main__.D object at 0x10e0a4cd0> pong <__main__.D object at 0x10e0a4cd0> pong <__main__.D object at 0x10e0a4cd0> PONG <__main__.D object at 0x10e0a4cd0>
最后一个方法的执行。
首相执行self.ping(),自己有这个属性,执行self.ping()后输出
两条执行
第一条ping <__main__.D object at 0x10e0a4cd0>,是执行了父类的父类A的ping方法,
第二条post-ping <__main__.D object at 0x10e0a4cd0>,是自己方法里面执行。
接着super.ping(),就是执行了父类的父类A的ping方法
self.pong与super().pong执行的结果一样,因为self自身没有pong 的属性,需要通过__mro__的顺序找找父类里面这个属性。
super().pong就是通过__mro__去查找父类属性。
最后一个是通过显式的方式运行父类属性。
后面按照书中的代码,演示一些几个类的__mro__
In [269]: bool.__mro__ Out[269]: (bool, int, object) In [270]: import numbers In [271]: numbers.Integral.__mro__ Out[271]: (numbers.Integral, numbers.Rational, numbers.Real, numbers.Complex, numbers.Number, object) In [272]: import io In [273]: io.BytesIO.__mro__ Out[273]: (_io.BytesIO, _io._BufferedIOBase, _io._IOBase, object) In [274]: io.TextIOWrapper.__mro__ Out[274]: (_io.TextIOWrapper, _io._TextIOBase, _io._IOBase, object) In [275]:
书中后面介绍了Tkinter与Django视图中类的继承使用,我的能力有限,两个模块基本没啥实操过,所以书中的很多讲述没办法理解。
只能回头再看。