Czy istnieje sposób na podklasę ze słownika i kolekcji.abc.MutableMapping?

26

Załóżmy na przykład, że chcę podklasę dicti mieć wszystkie klawisze wielkich liter:

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

Działa to do pewnego stopnia,

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

ale, oczywiście, tylko dla metod, które zapamiętałem na przykład

>>> 'a' in ex
False

nie jest pożądanym zachowaniem.

Teraz leniwy sposób wypełniania wszystkich metod, które można wyprowadzić z „rdzeniowych”, byłby wmieszany collections.abc.MutableMapping. Tylko, że tutaj nie działa. Zakładam, że metody, o których mowa ( __contains__w tym przykładzie) są już dostarczone przez dict.

Czy istnieje sposób na zjedzenie mojego ciasta i zjedzenie go? Jakaś magia pozwalająca MutableMappingzobaczyć tylko metody, które zastąpiłem, tak aby reimplementować inne w oparciu o te?

Paul Panzer
źródło
Może nie być konieczne użycie MutableMapping. Zobacz słownik bez rozróżniania wielkości liter .
martineau
@Martineau dzięki, jak powiedziałem, to był tylko przykład.
Paul Panzer
Możesz użyć os._Environ.
Peter Wood,

Odpowiedzi:

26

Co możesz zrobić:

To prawdopodobnie nie będzie działać dobrze (tzn nie najczystsze projekt), ale można dziedziczyć MutableMapping a potem z dict sekundę.

Następnie MutableMapping użyłby wszelkich zaimplementowanych metod (ponieważ są one pierwsze w łańcuchu wyszukiwania):

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

Lepszy sposób:

Najczystszym podejściem (łatwym do zrozumienia i przetestowania) jest po prostu odziedziczenie po MutableMapping, a następnie wdrożenie wymaganych metod przy użyciu zwykłego dykta jako podstawowego magazynu danych (z kompozycją zamiast dziedziczenia):

>>> 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
źródło
Dzięki! Mógłbym przysiąc, że wypróbowałem oba zamówienia ... Zainteresowanie, kiedy używam metody „mógłbym zrobić”, zamienia wszystkie supers na jawne, dictwtedy wydaje się, że działa, z wyjątkiem lenzwrotów 0. Skąd to pochodzi?
Paul Panzer
2
Super () wezwanie od __len _ () __ przechodzi do następnego w MRO: (D, MutableMapping, dict). Jest to metoda MutableMappiing .__ len __ () , która zawsze zwraca 0. Nie była przeznaczona do bezpośredniego wywoływania - zawsze powinna być nadpisana. Dlatego musisz zadzwonić dict.__len__(self)bezpośrednio. I to jest jeden z powodów, dla których powiedziałem: „to prawdopodobnie nie zadziała dobrze” ;-)
Raymond Hettinger