Jak poprawnie dyktować podklasy i nadpisywać __getitem__ i __setitem__


84

Debuguję kod i chcę się dowiedzieć, kiedy uzyskuje się dostęp do określonego słownika. Cóż, w rzeczywistości jest to klasa, która jest podklasą dicti implementuje kilka dodatkowych funkcji. W każdym razie chciałbym zrobić podklasę dictsiebie i dodać nadpisanie __getitem__oraz __setitem__wygenerować wyjście debugowania. Teraz mam

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

' name_label'jest kluczem, który ostatecznie zostanie ustawiony i którego chcę użyć do zidentyfikowania wyniku. Następnie zmieniłem klasę, którą obsługuję, na podklasę DictWatchzamiast dicti zmieniłem wywołanie superkonstruktora. Mimo wszystko wydaje się, że nic się nie dzieje. Myślałem, że jestem sprytny, ale zastanawiam się, czy powinienem iść w innym kierunku.

Dzięki za pomoc!


Czy próbowałeś użyć drukowania zamiast dziennika? Czy mógłbyś również wyjaśnić, jak tworzysz / konfigurujesz swój dziennik?
pajton

2
Nie dict.__init__bierze *args?
Tom Russell

4
Wygląda trochę na dobrego kandydata na dekoratora.
Tom Russell

Odpowiedzi:


39

To, co robisz, powinno absolutnie działać. Przetestowałem twoją klasę i poza brakującym nawiasem otwierającym w twoich instrukcjach dziennika, działa dobrze. Przychodzą mi do głowy tylko dwie rzeczy. Po pierwsze, czy dane wyjściowe instrukcji log są ustawione poprawnie? Może być konieczne umieszczenie logging.basicConfig(level=logging.DEBUG)u góry skryptu.

Po drugie, __getitem__i __setitem__są wywoływane tylko podczas []dostępu. Upewnij się więc, że masz dostęp tylko DictWatchprzez d[key], a nie d.get()id.set()


Właściwie to nie są dodatkowe pareny, ale brakujący paren otwierający(str(dict.get(self, 'name_label')), str(key), str(val)))
cobbal

3
Prawdziwe. Do OP: W przyszłości możesz po prostu zrobić log.info ('% s% s% s', a, b, c) zamiast operatora formatowania napisów w Pythonie.
BrainCore

Problemem okazał się poziom logowania. Debuguję kod kogoś innego i początkowo testowałem w innym pliku, który ma inny poziom zestawu debugowania. Dzięki!
Michael Mior

73

Innym problemem podczas tworzenia podklas dictjest to, że element wbudowany __init__nie wywołuje update, a element wbudowany updatenie wywołuje __setitem__. Tak więc, jeśli chcesz, aby wszystkie operacje setitem przechodziły przez twoją __setitem__funkcję, powinieneś upewnić się, że zostanie wywołana samodzielnie:

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print 'GET', key
        return val

    def __setitem__(self, key, val):
        print 'SET', key, val
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)

    def update(self, *args, **kwargs):
        print 'update', args, kwargs
        for k, v in dict(*args, **kwargs).iteritems():
            self[k] = v

9
Jeśli używasz Pythona 3, będziesz chciał zmienić ten przykład, aby printbył to print()funkcja, a update()metoda używa items()zamiast iteritems().
Al Sweigart

Wypróbowałem twój sol, ale wygląda na to, że działa tylko dla jednego poziomu indeksowania (tj. Dict [key] and not dict [key1] [key2] ...) *
Andrew Naguib

d [klucz1] zwraca coś, być może słownik. Drugi klucz to indeksuje. Ta technika nie działa, chyba że zwrócona rzecz obsługuje również zachowanie zegarka.
Matt Anderson

1
@AndrewNaguib: Dlaczego ma działać z zagnieżdżonymi tablicami? Tablica zagnieżdżona również nie działa z normalnym dyktowaniem Pythona (jeśli sam jej nie zaimplementowałeś)
Igor Chubin

1
@AndrewNaguib: __getitem__musiałby przetestować vali zrobić to tylko warunkowo - tj.if isinstance(val, dict): ...
martineau

14

Rozważ podklasy UserDictlub UserList. Te klasy są przeznaczone do podklasy, podczas gdy normalne dicti listnie są, i zawierają optymalizacje.


9
Dla odniesienia, dokumentacja w Pythonie 3.6 mówi: „Potrzeba tej klasy została częściowo wyparta przez możliwość bezpośredniego tworzenia podklas z dict; jednak praca z tą klasą może być łatwiejsza, ponieważ podstawowy słownik jest dostępny jako atrybut”.
Sean

Przykład @andrew może być pomocny.
Vasantha Ganesh K


9

To nie powinno tak naprawdę zmienić wyniku (co powinno działać, dla dobrych wartości progowych logowania): twój init powinien być:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

zamiast tego, ponieważ wywołanie metody z DictWatch ([(1,2), (2,3)]) lub DictWatch (a = 1, b = 2) zakończy się niepowodzeniem.

(lub lepiej nie definiuj konstruktora)


Martwię się tylko o dict[key]formę dostępu, więc to nie jest problem.
Michael Mior

1

Wszystko, co będziesz musiał zrobić, to

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

Przykładowe użycie do użytku osobistego

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

Uwaga : testowano tylko w python3


0

Aby uzupełnić odpowiedź na pasztet irlandzki, oto przykład pokazujący różnicę między dicta UserDict:

Trudno jest poprawnie nadpisać dyktando:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDictdziedziczą z collections.abc.MutableMapping, więc jest znacznie łatwiejsza do dostosowania:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

Podobnie, trzeba tylko wdrożyć __getitem__automatycznie być zgodny z key in my_dict, my_dict.get...

Uwaga: UserDictnie jest podklasą dict, więc isinstance(UserDict(), dict)zawiedzie (ale isinstance(UserDict(), collections.abc.MutableMapping)będzie działać)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.