¿Hay alguna forma de subclasificar dict y collections.abc.MutableMapping juntos?

26

Por ejemplo, supongamos que quiero subclase dicty tener todas las claves en mayúscula:

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())

Esto funciona hasta cierto punto,

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

pero, por supuesto, solo para los métodos que recordé implementar, por ejemplo

>>> 'a' in ex
False

No es el comportamiento deseado.

Ahora, la forma perezosa de completar todos los métodos que se pueden derivar de los "básicos" se estaría mezclando collections.abc.MutableMapping. Solo que no funciona aquí. Supongo que los métodos en cuestión ( __contains__en el ejemplo) ya los proporciona dict.

¿Hay alguna manera de tener mi pastel y comerlo? ¿Alguna magia para dejar MutableMappingver solo los métodos que he anulado para que reimplemente los otros basados ​​en esos?

Paul Panzer
fuente
Es posible que no necesite usar MutableMapping. Ver Diccionario insensible a mayúsculas y minúsculas .
Martineau
@martineau gracias, como dije fue solo un ejemplo.
Paul Panzer
Podrías usar os._Environ.
Peter Wood

Respuestas:

26

Lo que puedes hacer:

Esto probablemente no funcionará bien (es decir, no es el diseño más limpio), pero podría heredar de MutableMapping primero y luego de dict segundo.

Entonces MutableMapping usaría cualquier método que haya implementado (porque son los primeros en la cadena de búsqueda):

>>> 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

Mejor manera:

El enfoque más limpio (fácil de entender y probar) es simplemente heredar de MutableMapping y luego implementar los métodos requeridos usando un dict regular como el almacén de datos base (con composición en lugar de herencia):

>>> 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
Raymond Hettinger
fuente
¡Gracias! Podría haber jurado que probé ambas órdenes ... Por interés, cuando uso el método "podría hacer" intercambiando todos los correos electrónicos por correos electrónicos superexplícitos dict, parece funcionar, excepto las lendevoluciones 0. ¿De dónde viene eso?
Paul Panzer
2
La llamada a super () de __len _ __ () pasa al siguiente en el MRO: (D, MutableMapping, dict). Ese es el método MutableMappiing .__ len __ () que siempre devuelve 0. No se debe llamar directamente; siempre se supone que debe anularse. Por eso tienes que llamar dict.__len__(self)directamente. Y esa es una de las razones por las que dije "esto probablemente no funcionará bien" ;-)
Raymond Hettinger