Есть ли способ создания подклассов из dict и collection.abc.MutableMapping вместе?

26

Давайте для примера предположим, что я хочу создать подкласс dictи сделать все ключи заглавными:

class capdict(dict):
    def __init__(self,*args,**kwds):
        super().__init__(*args,**kwds)
        mod = [(k.capitalize(),v) for k,v in super().items()]
        super().clear()
        super().update(mod)
    def __getitem__(self,key):
        return super().__getitem__(key.capitalize())
    def __setitem__(self,key,value):
        super().__setitem__(key.capitalize(),value)
    def __delitem__(self,key):
        super().__detitem__(key.capitalize())

Это работает до такой степени,

>>> ex = capdict(map(reversed,enumerate("abc")))
>>> ex
{'A': 0, 'B': 1, 'C': 2}
>>> ex['a']
0

но, конечно, только для методов, которые я запомнил, например, для реализации

>>> 'a' in ex
False

не желаемое поведение.

Теперь ленивый способ заполнения всех методов, которые могут быть получены из «основных», будет смешиваться collections.abc.MutableMapping. Только здесь это не работает. Я предполагаю, потому что рассматриваемые методы ( __contains__в примере) уже предоставлены dict.

Есть ли способ получить мой пирог и съесть его? Какая-то магия, позволяющая MutableMappingвидеть только те методы, которые я переопределил, чтобы переопределить другие, основанные на них?

Пол Панцер
источник
Возможно, вам не нужно использовать MutableMapping. Смотрите словарь без учета регистра .
Мартино
@martineau спасибо, как я уже сказал, это только пример.
Пол Панцер
Вы могли бы использовать os._Environ.
Питер Вуд

Ответы:

26

Что вы могли бы сделать:

Это, вероятно, не сработает хорошо (т.е. не самый чистый дизайн), но вы могли бы наследовать сначала от MutableMapping, а затем от dict во- вторых.

Тогда MutableMapping будет использовать любые методы, которые вы реализовали (потому что они являются первыми в цепочке поиска):

>>> class D(MutableMapping, dict):
        def __getitem__(self, key):
            print(f'Intercepted a lookup for {key!r}')
            return dict.__getitem__(self, key)


>>> d = D(x=10, y=20)
>>> d.get('x', 0)
Intercepted a lookup for 'x'
10
>>> d.get('z', 0)
Intercepted a lookup for 'z'
0

Лучший путь:

Самый чистый подход (простой для понимания и тестирования) состоит в том, чтобы просто наследовать от MutableMapping, а затем реализовать необходимые методы, используя обычный dict в качестве основного хранилища данных (с композицией, а не наследованием):

>>> class CapitalizingDict(MutableMapping):
        def __init__(self, *args, **kwds):
            self.store = {}
            self.update(*args, **kwds)
        def __getitem__(self, key):
            key = key.capitalize()
            return self.store[key]
        def __setitem__(self, key, value):
            key = key.capitalize()
            self.store[key] = value
        def __delitem__(self, key):
            del self.store[key]
        def __len__(self):
            return len(self.store)
        def __iter__(self):
            return iter(self.store)
        def __repr__(self):
            return repr(self.store)


>>> d = CapitalizingDict(x=10, y=20)
>>> d
{'X': 10, 'Y': 20}
>>> d['x']
10
>>> d.get('x', 0)
10
>>> d.get('z', 0)
0
>>> d['w'] = 30
>>> d['W']
30
Раймонд Хеттингер
источник
Спасибо! Я мог бы поклясться, что попробовал оба заказа ... Из интереса, когда я использую метод "мог сделать", заменяя все supers на явные dicts, тогда это, кажется, работает, кроме lenвозвратов 0. Откуда это?
Пол Панцер
2
Супер () вызов из __len _ () __ переходит к следующему в ТОиР: (D, MutableMapping, dict). Это метод MutableMappiing .__ len __ (), который всегда возвращает 0. Он не был предназначен для прямого вызова - он всегда должен быть переопределен. Вот почему вы должны позвонить dict.__len__(self)напрямую. И это одна из причин, по которой я сказал: «Скорее всего, это не сработает» ;-)
Рэймонд Хеттингер