Python2.2版本后,內(nèi)置類型可以被子類化,但是需要注意的是,內(nèi)置類型不會調(diào)用用戶定義的類覆蓋的特殊方法。
例如,內(nèi)置類型 dict
的 __init__
和 __update__
方法會忽略自定義類覆蓋的 __setitem__
方法。
>>> class DoppelDict(dict):
... def __setitem__(self, key, value):
... super().__setitem__(key, [value] * 2)
...
>>> dd = DoppelDict(one=1)
>>> dd
{'one': 1} # 繼承自 dict 的 __init__ 方法忽略 __setitem__ 方法
>>> dd['two'] = 2
>>> dd
{'one': 1, 'two': [2, 2]} # [] 運(yùn)算符會調(diào)用__setitem__ 方法
>>> dd.update(three=3)
>>> dd
{'three': 3, 'one': 1, 'two': [2, 2]} #繼承自 dict 的 update 方法也不使用 __setitem__ 方法
但是有一個(gè)特例,__missing__
方法能按預(yù)期方式工作,并不會被忽略。
另外,不只實(shí)例內(nèi)部的調(diào)用會有這個(gè)問題(self.get()
不調(diào)用 self.__getitem__()
),內(nèi)置類型的方法調(diào)用的其他類的方法,如果被覆蓋了,也不會被調(diào)用,例子如下。
>>> class AnswerDict(dict):
... def __getitem__(self, key): # 不管傳入什么鍵,始終返回42
... return 42
...
>>> ad = AnswerDict(a='foo') # 以 ('a', 'foo') 鍵值對初始化
>>> ad['a'] #
42 # ad['a'] 返回 42,這與預(yù)期相符
>>> d = {}
>>> d.update(ad) # 使用 ad 中的值更新 d
>>> d['a'] # dict.update 方法忽略了 AnswerDict.__getitem__ 方法
'foo'
>>> d
{'a': 'foo'}
直接子類化內(nèi)置類型(如 dict
、list
或 str
)容易出錯(cuò),因?yàn)閮?nèi)置類型的方法通常會忽略用戶覆蓋的方法。不要子類化內(nèi)置類型,用戶自己定義的類應(yīng)該繼承 collections模塊中的類,例如 UserDict
、UserList
和 UserString
,這些類做了特殊設(shè)計(jì),因此易于擴(kuò)展,例子如下。
>>> import collections
>>> class DoppelDict2(collections.UserDict):
... def __setitem__(self, key, value):
... super().__setitem__(key, [value] * 2)
...
>>> dd = DoppelDict2(one=1)
>>> dd
{'one': [1, 1]} # __init__ 正常工作,調(diào)用了__setitem__方法
>>> dd['two'] = 2
>>> dd
{'two': [2, 2], 'one': [1, 1]}
>>> dd.update(three=3)
>>> dd
{'two': [2, 2], 'three': [3, 3], 'one': [1, 1]} # update 正常工作,調(diào)用了__setitem__方法
>>>
>>> class AnswerDict2(collections.UserDict):
... def __getitem__(self, key):
... return 42
...
>>> ad = AnswerDict2(a='foo')
>>> ad['a']
42
>>> d = {}
>>> d.update(ad)
>>> d['a']
42 # 如預(yù)期,正常工作
>>> d
{'a': 42}
綜上,上述問題只發(fā)生在 C 語言實(shí)現(xiàn)的內(nèi)置類型內(nèi)部的方法委托上,而且只影響直接繼承內(nèi)置類型的用戶自定義類。如果子類化使用 Python 編寫的類,如 UserDict 或 MutableMapping,就不會受此影響。