Inheritance: For Good or For Worse
1. Subclassing Built-In Types Is Tricky
Since Python 2.2, subclassing built-in types such as list or dict can be done but there is a major caveat: the code of the built-ins (written in C) does not call special methods overridden by user-defined classes.
A good short description of the problem is in the documentation for PyPy, shown as below:
Officially, CPython has no rule at all for when exactly overridden method of subclasses of built-in types get implicitly called or not.
As an approximation, these methods are never called by other built-in methods of the same object.
For example, an overridden __getitem__() in a subclass of dict will not be called by e.g. the built-in get() method.
自写示例:
# 字典的 [] 操作符取值会自动调用 __getitem__() 方法,但字典内置的 get() 方法并没有直接关联 __getitem__() 方法 In [53]: class DoppelDict(dict): ...: def __getitem__(self, key): ...: return key * 3 ...: In [54]: dd = DoppelDict(one=10) In [55]: dd['one'] Out[55]: 'oneoneone' In [56]: dd['two'] Out[56]: 'twotwotwo' In [57]: dd.get('one') Out[57]: 10 # 虽然 DoppelDict 继承了内置的 dict 类型,但其重写的 __getitem__() 方法并没有被它其它的内置方法(如: get() 方法)自动调用
Example 12-1. Our __setitem__ override is ignored by the __init__ and __update__ methods of the built-in dict.
>>> class DoppelDict(dict): ... def __setitem__(self, key, value): ... super().__setitem__(key, [value] * 2) ... >>> dd = DoppelDict(one=1) # The __init__ method inherited from dict clearly ignored that __setitem__ was overridden: the value of 'one' is not duplicated. >>> dd {'one': 1} >>> dd['two'] = 2 # The [] operator calls our __setitem__ and works as expected: 'two' maps to the duplicated value [2, 2] >>> dd {'one': 1, 'two': [2, 2]} >>> dd.update(three=3) # The update method from dict does not use our version of __setitem__ either: the value of 'three' was not duplicated. >>> dd {'one': 1, 'two': [2, 2], 'three': 3}
This built-in behavior is a violation of a basic rule of object-oriented programming: the search for methods should always start from the class of the target instance(self), even when the call happens inside a method implemented in a superclass. In this sad state of affairs, the __missing__ method works as documented only because it's handled as a special case.
This problem is not limited to calls within an instance, but also happens with overridden methods of other classes that should be called by the built-in methods.
Example 12-2. The __getitem__ of AnswerDict is bypassed by dict.update
>>> class AnswerDict(dict): ... def __getitem__(self, key): ... return 42 ... >>> ad = AnswerDict(a='foo') >>> ad {'a': 'foo'} >>> ad['a'] # ad['a'] returns 42, as expected. 42 >>> d = {} >>> d.update(ad) >>> d['a'] # The dict.update method ignored our AnswerDict.__getitem__ 'foo' >>> d {'a': 'foo'} """ Subclassing built-in types like dict or list or str directly is error-prone because the built-in methods mostly ignore user-defined override. Instead of subclassing the built-ins, derive your classes from the collections module using UserDict, UserList, and UserString, which are designed to be easily extended. """
在自己做测试时,出现了一些没有想到的结果。。。:
# iPython 的结果: In [60]: class AnswerDict(dict): ...: def __getitem__(self, key): ...: return 42 ...: In [61]: ad = AnswerDict(a='foo') In [62]: ad Out[62]: {'a': 42} # 终端直接调用python3 的结果: Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> >>> class AnswerDict(dict): ... def __getitem__(self, key): ... return 42 ... >>> ad = AnswerDict(a='foo') >>> ad {'a': 'foo'} # 同样都是直接输入 ad ,但得到的结果却不一样。。。
If you subclass collections.UserDict instead of dict, the issues exposed in Eamples 12-1 and 12-2 are both fixed. See Example 12-3.
Example 12-3. DoppelDict2 and AnswerDict2 work as expected because they extend UserDict and not dict.
>>> import collections >>> >>> class DoppelDict2(collections.UserDict): ... def __setitem__(self, key, value): ... super().__setitem__(key, [value] * 2) ... >>> dd = DoppelDict2(one=1) >>> dd {'one': [1, 1]} >>> dd['two'] = 2 >>> dd {'one': [1, 1], 'two': [2, 2]} >>> dd.update(three=3) >>> dd {'one': [1, 1], 'two': [2, 2], 'three': [3, 3]} >>> >>> >>> class AnswerDict2(collections.UserDict): ... def __getitem__(self, key): ... return 42 ... >>> ad = AnswerDict2(a='foo') >>> ad['a'] 42 >>> d = {} >>> d.update(ad) >>> d['a'] 42 >>> d {'a': 42} """ The problem described in this section applies only to method delegation within the C language implementation of the built-in types, and only affects user-defined classes derived directly from those types. If you subclass from a class coded in Python such as UserDict or MutableMapping, you will not be troubled by this. """
2. Multiple Inheritance and Method Resolution Order
I often check the __mro__ of classes interactively when I am studing them. Example 12-8 has some examples using familiar classes.
Example 12-8. Inspecting the __mro__ attributes in several classes.
>>> bool.__mro__ (<class 'bool'>, <class 'int'>, <class 'object'>) >>> >>> def print_mro(cls): ... print(', '.join(c.__name__ for c in cls.__mro__)) ... >>> print_mro(bool) bool, int, object >>> >>> import numbers >>> print_mro(numbers.Integral) Integral, Rational, Real, Complex, Number, object >>> >>> import io >>> print_mro(io.BytesIO) BytesIO, _BufferedIOBase, _IOBase, object >>> print_mro(io.BytesIO) BytesIO, _BufferedIOBase, _IOBase, object """ bool inherits methods and attributes from int and object. """
end...