Esiste un modo di sottoclasse da dict e collections.abc.MutableMapping insieme?


26

Per esempio, supponiamo che io voglia sottoclassare dicte avere tutte le chiavi in ​​maiuscolo:

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

Questo funziona fino a un certo punto,

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

ma, ovviamente, solo per i metodi che mi sono ricordato di implementare, per esempio

>>> 'a' in ex
False

non è il comportamento desiderato.

Ora, il modo pigro di compilare tutti i metodi che possono essere derivati ​​da quelli "core" si mescolerebbe collections.abc.MutableMapping. Solo, non funziona qui. Presumo perché i metodi in questione ( __contains__nell'esempio) sono già forniti da dict.

C'è un modo per avere la mia torta e mangiarla? Qualche magia per far MutableMappingvedere solo i metodi che ho scavalcato in modo da reimplementare gli altri in base a quelli?


Potrebbe non essere necessario utilizzare MutableMapping. Vedi Dizionario senza distinzione tra maiuscole e minuscole .
martineau,

@martineau grazie, come ho già detto era solo un esempio.
Paul Panzer,

Puoi usare os._Environ.
Peter Wood,

Risposte:


26

Cosa potresti fare:

Questo probabilmente non funzionerà bene (cioè non il design più pulito), ma potresti ereditare prima da MutableMapping e poi da dict secondo.

Quindi MutableMapping userebbe qualunque metodo tu abbia implementato (perché sono i primi nella catena di ricerca):

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

Modo migliore:

L'approccio più pulito (facile da capire e testare) è quello di ereditare da MutableMapping e quindi implementare i metodi richiesti usando un dict regolare come archivio di dati di base (con composizione anziché ereditarietà):

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

Grazie! Avrei potuto giurare di aver provato entrambi gli ordini ... Per interesse, quando uso il metodo "could do" scambiando tutte le supers con esplicite dicts, allora sembra funzionare, tranne i lenritorni 0. Da dove viene?
Paul Panzer,

2
La chiamata super () da __len _ () __ passa a quello successivo nella MRO: (D, MutableMapping, dict). Questo è il metodo MutableMappiing .__ len __ () che restituisce sempre 0. Non è stato progettato per essere chiamato direttamente, ma deve sempre essere sostituito. Ecco perché devi chiamare dict.__len__(self)direttamente. E questo è uno dei motivi per cui ho detto "questo probabilmente non funzionerà bene" ;-)
Raymond Hettinger il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.