Existe-t-il un moyen de sous-classer ensemble dict et collections.abc.MutableMapping?

26

Supposons par exemple que je souhaite sous dict- classer et que toutes les clés soient en majuscule:

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

Cela fonctionne dans une certaine mesure,

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

mais, bien sûr, uniquement pour les méthodes dont je me suis souvenu de mettre en œuvre, par exemple

>>> 'a' in ex
False

n'est pas le comportement souhaité.

Maintenant, la manière paresseuse de remplir toutes les méthodes qui peuvent être dérivées des méthodes "de base" serait de se mélanger collections.abc.MutableMapping. Seulement, ça ne marche pas ici. Je suppose que les méthodes en question ( __contains__dans l'exemple) sont déjà fournies par dict.

Existe-t-il un moyen d'avoir mon gâteau et de le manger? Un peu de magie pour MutableMappingne voir que les méthodes que j'ai remplacées afin de réimplémenter les autres en fonction de celles-ci?

Paul Panzer
la source
Vous n'aurez peut-être pas besoin de l'utiliser MutableMapping. Voir Dictionnaire insensible à la casse .
martineau
@martineau merci, comme je l'ai dit, ce n'était qu'un exemple.
Paul Panzer
Vous pourriez utiliser os._Environ.
Peter Wood

Réponses:

26

Ce que vous pourriez faire:

Cela ne fonctionnera probablement pas bien (c'est-à-dire pas la conception la plus propre), mais vous pourriez hériter de MutableMapping d' abord, puis de dict en second.

Ensuite, MutableMapping utiliserait toutes les méthodes que vous avez implémentées (car elles sont les premières de la chaîne de recherche):

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

Meilleure façon:

L'approche la plus propre (facile à comprendre et à tester) consiste à simplement hériter de MutableMapping puis à implémenter les méthodes requises en utilisant un dict régulier comme magasin de données de base (avec une composition plutôt qu'un héritage):

>>> 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
la source
Merci! J'aurais juré avoir essayé les deux commandes ... Par intérêt, lorsque j'utilise la méthode "pourrait faire" en remplaçant tous les supers par des dicts explicites, cela semble fonctionner, sauf les lenretours 0. D'où ça vient?
Paul Panzer
2
Le super () appel de __len _ () __ va à l'autre dans le MRO: (D, MutableMapping, dict). Il s'agit de la méthode MutableMappiing .__ len __ () qui renvoie toujours 0. Elle n'était pas destinée à être appelée directement - elle est toujours censée être remplacée. C'est pourquoi vous devez appeler dict.__len__(self)directement. Et c'est l'une des raisons pour lesquelles j'ai dit "cela ne fonctionnera probablement pas bien" ;-)
Raymond Hettinger