Почему я не могу создать дефолт по умолчанию, заказанный 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)