Почему я не могу создать дефолт по умолчанию, заказанный dict, наследуя OrderedDict и defaultdict?
Моя первая попытка объединить функции двух словарей в модуле collections
заключалась в создании класса, который их наследует:
from collections import OrderedDict, defaultdict
class DefaultOrderedDict(defaultdict, OrderedDict):
def __init__(self, default_factory=None, *a, **kw):
super().__init__(default_factory, *a, **kw)
Однако я не могу назначить элемент этому словарю:
d = DefaultOrderedDict(lambda: 0)
d['a'] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.3/collections/__init__.py", line 64, in __setitem__
self.__map[key] = link = Link()
AttributeError: 'DefaultOrderedDict' object has no attribute '_OrderedDict__map'
В самом деле, этот вопрос о том, как создать подобный объект, имеет ответы, которые достигают его, расширяя класс OrderedDict
и вручную переуступая дополнительные методы defaultdict
. Использование множественного наследования будет более чистым. Почему это не работает?
Ответы
Ответ 1
Возможно, вы исходите из фона Java, но множественное наследование не делает то, что вы ожидаете от него в Python. Вызов super из init по умолчаниюOrderedDict вызывает super() как init defaultdict и никогда не init для OrderedDict. Атрибут map сначала определяется в функции __init для OrderedDict. Реализация следующая (из источника):
def __init__(self, *args, **kwds):
'''Initialize an ordered dictionary. The signature is the same as
regular dictionaries, but keyword arguments are not recommended because
their insertion order is arbitrary.
'''
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
self.__root = root = [] # sentinel node
root[:] = [root, root, None]
self.__map = {}
self.__update(*args, **kwds)
Обратите внимание, что это не связано с тем, что атрибут является закрытым. Минимальный пример с множественным наследованием может проиллюстрировать это:
class Foo:
def __init__(self):
self.foo=2
class Bar:
def __init__(self):
self.bar=1
class FooBar(Foo,Bar):
def __init__(self):
super().__init__()
fb = FooBar()
fb.foo
>>2
fb.bar
>>AttributeError: 'FooBar' object has no attribute 'bar'
Итак, конструктор Бар никогда не назывался. Порядок разрешения метода Pythons идет слева направо, пока не найдет класс с именем функции, которое он ищет (в данном случае init), а затем игнорирует все остальные классы справа (в этом случае Bar)
Ответ 2
Причина в том, что метод init
defaultdict
вместо вызова __init__
следующего класса в вызовах MRO init PyDict_Type
поэтому некоторые из атрибутов типа __map
, которые установлены в OrderedDict __init__
, никогда не инициализируются, следовательно, ошибка.
>>> DefaultOrderedDict.mro()
[<class '__main__.DefaultOrderedDict'>,
<class 'collections.defaultdict'>,
<class 'collections.OrderedDict'>,
<class 'dict'>, <class 'object'>]
И defaultdict
не имеют собственного метода __setitem__
:
>>> defaultdict.__setitem__
<slot wrapper '__setitem__' of 'dict' objects>
>>> dict.__setitem__
<slot wrapper '__setitem__' of 'dict' objects>
>>> OrderedDict.__setitem__
<unbound method OrderedDict.__setitem__>
Итак, когда вы вызвали d['a']
= 1, в поисках __setitem__
Python достиг заказа OrderedDict __setitem__
, и их доступ к неинициализированному атрибуту __map
поднял ошибку:
Исправить будет вызов __init__
на defaultdict
и OrderedDict
явно:
class DefaultOrderedDict(defaultdict, OrderedDict):
def __init__(self, default_factory=None, *a, **kw):
for cls in DefaultOrderedDict.mro()[1:-2]:
cls.__init__(self, *a, **kw)