So ordnen Sie diktieren und überschreiben __getitem__ & __setitem__

84

Ich debugge Code und möchte herausfinden, wann auf ein bestimmtes Wörterbuch zugegriffen wird. Nun, es ist eigentlich eine Klasse, die eine Unterklasse bildet dictund einige zusätzliche Funktionen implementiert. Was ich jedenfalls tun möchte, ist, dictmich selbst zu unterordnen und Override hinzuzufügen __getitem__und __setitem__eine Debugging-Ausgabe zu erzeugen. Gerade habe ich

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'ist ein Schlüssel, der irgendwann gesetzt wird, mit dem ich die Ausgabe identifizieren möchte. Ich habe dann die Klasse, die ich instrumentiere, in Unterklasse DictWatchanstatt dictgeändert und den Aufruf an den Superkonstruktor geändert. Trotzdem scheint nichts zu passieren. Ich dachte, ich wäre schlau, aber ich frage mich, ob ich eine andere Richtung einschlagen sollte.

Danke für die Hilfe!

Michael Mior
quelle
Haben Sie versucht, print anstelle von log zu verwenden? Können Sie auch erklären, wie Sie Ihr Protokoll erstellen / konfigurieren?
Pyjton
2
Nicht dict.__init__nehmen *args?
Tom Russell
4
Sieht ein bisschen aus wie ein guter Kandidat für einen Dekorateur.
Tom Russell

Antworten:

39

Was Sie tun, sollte unbedingt funktionieren. Ich habe Ihre Klasse getestet und abgesehen von einer fehlenden öffnenden Klammer in Ihren Protokollanweisungen funktioniert es einwandfrei. Ich kann mir nur zwei Dinge vorstellen. Ist die Ausgabe Ihrer Protokollanweisung korrekt eingestellt? Möglicherweise müssen Sie ein logging.basicConfig(level=logging.DEBUG)oben in Ihr Skript einfügen.

Zweitens __getitem__und __setitem__werden nur während der []Zugriffe aufgerufen . Stellen Sie also sicher, dass Sie nur DictWatchüber d[key]und nicht über d.get()und zugreifend.set()

BrainCore
quelle
Eigentlich sind es keine zusätzlichen Parens, sondern ein fehlender Eröffnungsparen(str(dict.get(self, 'name_label')), str(key), str(val)))
Cobbal
3
Wahr. Zum OP: Zum späteren Nachschlagen können Sie einfach log.info ('% s% s% s', a, b, c) anstelle eines Python-Zeichenfolgenformatierungsoperators ausführen.
BrainCore
Die Protokollierungsstufe war schließlich das Problem. Ich debugge den Code eines anderen und habe ursprünglich in einer anderen Datei getestet, die eine andere Debugging-Stufe enthält. Vielen Dank!
Michael Mior
73

Ein weiteres Problem bei der Unterklasse dictbesteht darin, dass das integrierte __init__Gerät nicht aufruft updateund das integrierte updateGerät nicht aufruft __setitem__. Wenn Sie also möchten, dass alle Setitem-Operationen Ihre __setitem__Funktion durchlaufen , sollten Sie sicherstellen, dass sie selbst aufgerufen werden:

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
Matt Anderson
quelle
9
Wenn Sie Python 3 verwenden, möchten Sie dieses Beispiel so ändern, dass printdie print()Funktion und die update()Methode items()anstelle von verwendet werden iteritems().
Al Sweigart
Ich habe Ihr Sol ausprobiert, aber es scheint, dass es nur für eine Indizierungsebene
Andrew Naguib
d [key1] gibt etwas zurück, vielleicht ein Wörterbuch. Der zweite Schlüssel indiziert das. Diese Technik kann nur funktionieren, wenn das zurückgegebene Objekt auch das Überwachungsverhalten unterstützt.
Matt Anderson
1
@ AndrewNaguib: Warum sollte es mit verschachtelten Arrays funktionieren? Verschachtelte Arrays funktionieren auch nicht mit normalem Python-Diktat (wenn Sie es nicht selbst implementiert haben)
Igor Chubin
1
@ AndrewNaguib: __getitem__müsste testen valund nur bedingt tun - dhif isinstance(val, dict): ...
Martineau
14

Betrachten Sie Unterklassen UserDictoder UserList. Diese Klassen sollen in Unterklassen unterteilt werden, während die normalen dictund listnicht, und Optimierungen enthalten.

Andrew Pate
quelle
9
In der Dokumentation in Python 3.6 heißt es als Referenz : "Die Notwendigkeit für diese Klasse wurde teilweise durch die Möglichkeit ersetzt, direkt aus dem Diktat eine Unterklasse zu erstellen. Es kann jedoch einfacher sein, mit dieser Klasse zu arbeiten, da auf das zugrunde liegende Wörterbuch als Attribut zugegriffen werden kann."
Sean
@ Andrew ein Beispiel könnte hilfreich sein.
Vasantha Ganesh K
2
@VasanthaGaneshK treyhunner.com/2019/04/…
SirDorius
9

Das sollte das Ergebnis nicht wirklich ändern (was für gute Protokollierungsschwellenwerte funktionieren sollte): Ihr Init sollte sein:

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

Stattdessen schlägt dies fehl, wenn Sie Ihre Methode mit DictWatch ([(1,2), (2,3)]) oder DictWatch (a = 1, b = 2) aufrufen.

(oder besser, definieren Sie keinen Konstruktor dafür)

makapuf
quelle
Ich mache mir nur Sorgen um die dict[key]Form des Zugriffs, daher ist dies kein Problem.
Michael Mior
1

Alles was Sie tun müssen, ist

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

Ein Beispiel für die Verwendung für meinen persönlichen Gebrauch

### 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")

Hinweis : Nur in Python3 getestet

ravi404
quelle
0

Um die Antwort von Andrew Pate zu vervollständigen, ist hier ein Beispiel, das einen Unterschied zwischen dictund zeigt UserDict:

Es ist schwierig, das Diktat richtig zu überschreiben:

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}

UserDicterben von collections.abc.MutableMapping, ist also viel einfacher anzupassen:

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}

Ebenso müssen Sie nur implementieren, __getitem__um automatisch mit ,, ... kompatibel zu sein key in my_dict.my_dict.get

Hinweis: UserDictist keine Unterklasse von dict, isinstance(UserDict(), dict)wird also fehlschlagen (wird aber isinstance(UserDict(), collections.abc.MutableMapping)funktionieren)

Conchylicultor
quelle