8.18 利用Mixins擴(kuò)展類功能

2018-02-24 15:26 更新

問題

你有很多有用的方法,想使用它們來擴(kuò)展其他類的功能。但是這些類并沒有任何繼承的關(guān)系。因此你不能簡單的將這些方法放入一個基類,然后被其他類繼承。

解決方案

通常當(dāng)你想自定義類的時候會碰上這些問題。可能是某個庫提供了一些基礎(chǔ)類,你可以利用它們來構(gòu)造你自己的類。

假設(shè)你想擴(kuò)展映射對象,給它們添加日志、唯一性設(shè)置、類型檢查等等功能。下面是一些混入類:

class LoggedMappingMixin:
    """
    Add logging to get/set/delete operations for debugging.
    """
    __slots__ = ()  # 混入類都沒有實(shí)例變量,因?yàn)橹苯訉?shí)例化混入類沒有任何意義

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return super().__setitem__(key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return super().__delitem__(key)

class SetOnceMappingMixin:
    '''
    Only allow a key to be set once.
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(str(key) + ' already set')
        return super().__setitem__(key, value)

class StringKeysMappingMixin:
    '''
    Restrict keys to strings only
    '''
    __slots__ = ()

    def __setitem__(self, key, value):
        if not isinstance(key, str):
            raise TypeError('keys must be strings')
        return super().__setitem__(key, value)

這些類單獨(dú)使用起來沒有任何意義,事實(shí)上如果你去實(shí)例化任何一個類,除了產(chǎn)生異常外沒任何作用。它們是用來通過多繼承來和其他映射對象混入使用的。例如:

class LoggedDict(LoggedMappingMixin, dict):
    pass

d = LoggedDict()
d['x'] = 23
print(d['x'])
del d['x']

from collections import defaultdict

class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
    pass

d = SetOnceDefaultDict(list)
d['x'].append(2)
d['x'].append(3)
# d['x'] = 23  # KeyError: 'x already set'

這個例子中,可以看到混入類跟其他已存在的類(比如dict、defaultdict和OrderedDict)結(jié)合起來使用,一個接一個。結(jié)合后就能發(fā)揮正常功效了。

討論

混入類在標(biāo)志庫中很多地方都出現(xiàn)過,通常都是用來像上面那樣擴(kuò)展某些類的功能。它們也是多繼承的一個主要用途。比如,當(dāng)你編寫網(wǎng)絡(luò)代碼時候,你會經(jīng)常使用 socketserver 模塊中的 ThreadingMixIn 來給其他網(wǎng)絡(luò)相關(guān)類增加多線程支持。例如,下面是一個多線程的XML-RPC服務(wù):

from xmlrpc.server import SimpleXMLRPCServer
from socketserver import ThreadingMixIn
class ThreadedXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
    pass

同時在一些大型庫和框架中也會發(fā)現(xiàn)混入類的使用,用途同樣是增強(qiáng)已存在的類的功能和一些可選特征。

對于混入類,有幾點(diǎn)需要記住。首先是,混入類不能直接被實(shí)例化使用。其次,混入類沒有自己的狀態(tài)信息,也就是說它們并沒有定義 __init__() 方法,并且沒有實(shí)例屬性。這也是為什么我們在上面明確定義了 __slots__ = ()

還有一種實(shí)現(xiàn)混入類的方式就是使用類裝飾器,如下所示:

def LoggedMapping(cls):
    """第二種方式:使用類裝飾器"""
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__
    return cls

@LoggedMapping
class LoggedDict(dict):
    pass

這個效果跟之前的是一樣的,而且不再需要使用多繼承了。參考9.12小節(jié)獲取更多類裝飾器的信息,參考8.13小節(jié)查看更多混入類和類裝飾器的例子。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號