流畅的python,Fluent Python 第十二章笔记 (继承)

第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视图中类的继承使用,我的能力有限,两个模块基本没啥实操过,所以书中的很多讲述没办法理解。

只能回头再看。

 

 

 

上一篇:MacOS升级到10.15.5后没有自动迁移/etc/pam.d/sudo文件


下一篇:Akka Study - 白墨的博客