53 Stimmen

Unterklassifizierung des Python-Wörterbuchs zur Überschreibung von __setitem__

Ich baue eine Klasse, die Unterklassen dict und übersteuert __setitem__ . Ich möchte sicher sein, dass meine Methode in allen Fällen aufgerufen wird, in denen Wörterbucheinträge möglicherweise gesetzt werden können.

Ich habe drei Situationen entdeckt, in denen Python (in diesem Fall 2.6.4) mein überschriebenes __setitem__ Methode, wenn Werte gesetzt werden, und ruft stattdessen PyDict_SetItem direkt

  1. Im Konstruktor
  2. In der setdefault Methode
  3. In der update Methode

Das ist ein sehr einfacher Test:

class MyDict(dict):
    def __setitem__(self, key, value):
        print "Here"
        super(MyDict, self).__setitem__(key, str(value).upper())

>>> a = MyDict(abc=123)
>>> a['def'] = 234
Here
>>> a.update({'ghi': 345})
>>> a.setdefault('jkl', 456)
456
>>> print a
{'jkl': 456, 'abc': 123, 'ghi': 345, 'def': '234'}

Sie sehen, dass die überschriebene Methode nur aufgerufen wird, wenn die Elemente explizit gesetzt werden. Um Python dazu zu bringen, immer meine __setitem__ Methode musste ich diese drei Methoden neu implementieren, etwa so:

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

    def __setitem__(self, key, value):
        print "Here"
        super(MyUpdateDict, self).__setitem__(key, value)

    def update(self, *args, **kwargs):
        if args:
            if len(args) > 1:
                raise TypeError("update expected at most 1 arguments, got %d" % len(args))
            other = dict(args[0])
            for key in other:
                self[key] = other[key]
        for key in kwargs:
            self[key] = kwargs[key]

    def setdefault(self, key, value=None):
        if key not in self:
            self[key] = value
        return self[key]

Gibt es andere Methoden, die ich überschreiben muss, um zu wissen, dass Python wird immer rufen Sie meine __setitem__ Methode?

UPDATE

Per gs Vorschlag, habe ich versucht, UserDict (eigentlich, IterableUserDict, da ich über die Schlüssel iterieren möchte) wie folgt unterzuordnen:

from UserDict import *;
class MyUserDict(IterableUserDict):
    def __init__(self, *args, **kwargs):
        UserDict.__init__(self,*args,**kwargs)

    def __setitem__(self, key, value):
        print "Here"
        UserDict.__setitem__(self,key, value)

Diese Klasse scheint korrekt meine __setitem__ en setdefault aber er ruft es nicht auf update oder wenn dem Konstruktor Anfangsdaten übergeben werden.

UPDATE 2

Peter Hansens Vorschlag veranlasste mich, dictobject.c genauer zu betrachten, und ich stellte fest, dass die Aktualisierungsmethode etwas vereinfacht werden könnte, da der eingebaute Wörterbuchkonstruktor ohnehin nur die eingebaute Aktualisierungsmethode aufruft. Jetzt sieht es so aus:

def update(self, *args, **kwargs):
    if len(args) > 1:
        raise TypeError("update expected at most 1 arguments, got %d" % len(args))
    other = dict(*args, **kwargs)
    for key in other:
        self[key] = other[key]

55voto

Ian Clelland Punkte 41301

Ich beantworte damit meine eigene Frage, denn ich habe beschlossen, dass ich wirklich tun eine Unterklasse von Dict wählen, anstatt eine neue Zuordnungsklasse zu erstellen, und UserDict verweist in einigen Fällen immer noch auf das zugrundeliegende Dict-Objekt, anstatt die bereitgestellte __setitem__ .

Nach dem Lesen und erneuten Lesen des Python 2.6.4 Quelltextes (hauptsächlich Objects/dictobject.c aber ich habe überall nachgeschaut, um zu sehen, wo die verschiedenen Methoden verwendet werden), so verstehe ich den folgenden Code es ausreichend, um mein __setitem__ jedes Mal aufzurufen, wenn das Objekt geändert wird, und sich ansonsten genau wie ein Python-Dict zu verhalten:

Peter Hansens Vorschlag hat mich dazu veranlasst, mir die folgenden Punkte genauer anzusehen dictobject.c und mir wurde klar, dass die Aktualisierungsmethode in meiner ursprünglichen Antwort ein wenig vereinfacht werden könnte, da der eingebaute Wörterbuchkonstruktor ohnehin nur die eingebaute Aktualisierungsmethode aufruft. Also wurde das zweite Update in meiner Antwort dem Code unten hinzugefügt (von einer hilfreichen Person ;-).

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

    def __setitem__(self, key, value):
        # optional processing here
        super(MyUpdateDict, self).__setitem__(key, value)

    def update(self, *args, **kwargs):
        if args:
            if len(args) > 1:
                raise TypeError("update expected at most 1 arguments, "
                                "got %d" % len(args))
            other = dict(args[0])
            for key in other:
                self[key] = other[key]
        for key in kwargs:
            self[key] = kwargs[key]

    def setdefault(self, key, value=None):
        if key not in self:
            self[key] = value
        return self[key]

Ich habe es mit diesem Code getestet:

def test_updates(dictish):
    dictish['abc'] = 123
    dictish.update({'def': 234})
    dictish.update(red=1, blue=2)
    dictish.update([('orange', 3), ('green',4)])
    dictish.update({'hello': 'kitty'}, black='white')
    dictish.update({'yellow': 5}, yellow=6)
    dictish.setdefault('brown',7)
    dictish.setdefault('pink')
    try:
        dictish.update({'gold': 8}, [('purple', 9)], silver=10)
    except TypeError:
        pass
    else:
        raise RunTimeException("Error did not occur as planned")

python_dict = dict([('b',2),('c',3)],a=1)
test_updates(python_dict)

my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
test_updates(my_dict)

und es klappt. Alle anderen Implementierungen, die ich versucht habe, sind irgendwann gescheitert. Ich werde immer noch alle Antworten akzeptieren, die mir zeigen, dass ich etwas übersehen habe, aber ansonsten werde ich in ein paar Tagen das Häkchen neben dieser Frage setzen und sie als die richtige Antwort bezeichnen :)

4voto

mluebke Punkte 8100

Was ist Ihr Anwendungsfall für die Unterklassifizierung von dict?

Sie müssen dies nicht tun, um ein dict-ähnliches Objekt zu implementieren, und es könnte in Ihrem Fall einfacher sein, eine gewöhnliche Klasse zu schreiben und dann Unterstützung für die erforderliche Teilmenge der dict-Schnittstelle hinzuzufügen.

Der beste Weg, das zu erreichen, was Sie wollen, ist wahrscheinlich die abstrakte Basisklasse MutableMapping. PEP 3119 -- Einführung von abstrakten Basisklassen

Dies wird Ihnen auch bei der Beantwortung der Frage helfen: "Gibt es noch andere Methoden, die ich außer Kraft setzen muss? Sie müssen alle abstrakten Methoden außer Kraft setzen. Für MutableMapping: Zu den abstrakten Methoden gehören setitem , delitem . Konkrete Methoden sind pop, popitem, clear, update.

4voto

rizac Punkte 61

Ich fand Ians Antwort und Kommentare sehr hilfreich und klar. Ich würde nur darauf hinweisen, dass vielleicht ein erster Aufruf der Superklasse __init__ Methode könnte sicherer sein, wenn sie nicht notwendig ist: Vor kurzem musste ich eine benutzerdefinierte OrderedDict (Ich arbeite mit Python 2.7): Nach der Implementierung und Änderung meines Codes gemäß der vorgeschlagenen MyUpdateDict Implementierung habe ich herausgefunden, dass durch einfaches Ersetzen von

class MyUpdateDict(dict):

mit:

from collections import OrderedDict
class MyUpdateDict(OrderedDict):

dann ist der oben gepostete Testcode fehlgeschlagen:

Traceback (most recent call last):
File "Desktop/test_updates.py", line 52, in <module>
    my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
File "Desktop/test_updates.py", line 5, in __init__
    self.update(*args, **kwargs)
File "Desktop/test_updates.py", line 18, in update
    self[key] = other[key]
File "Desktop/test_updates.py", line 9, in __setitem__
    super(MyUpdateDict, self).__setitem__(key, value)
File "/usr/lib/python2.7/collections.py", line 59, in __setitem__
    root = self.__root
AttributeError: 'MyUpdateDict' object has no attribute '_OrderedDict__root'

Blick auf sammlungen.py code es stellt sich heraus, dass OrderedDict braucht seine __init__ die aufgerufen werden muss, um die erforderlichen benutzerdefinierten Attribute zu initialisieren und einzurichten.

Daher genügt es, einen ersten Aufruf an die super __init__ Methode,

from collections import OrderedDict
class MyUpdateDict(Orderedict):
def __init__(self, *args, **kwargs):
    super(MyUpdateDict, self).__init__() #<-- HERE call to super __init__
    self.update(*args, **kwargs)

haben wir eine allgemeinere Lösung, die offenbar sowohl für dict als auch für OrderedDict funktioniert.

Ich kann nicht sagen, ob diese Lösung allgemein gültig ist, da ich sie nur mit OrderedDict getestet habe. Es ist jedoch wahrscheinlich, dass ein Aufruf des super __init__ Methode ist entweder harmlos oder eher notwendig als schädlich, wenn man versucht, andere dict-Unterklassen zu erweitern

0voto

Object.keyname = value anstelle von object["keyname"] = value verwenden

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X