Eine Python-Klasse, die sich wie ein Diktat verhält

113

Ich möchte eine benutzerdefinierte Klasse schreiben, die sich wie dictfolgt verhält - also erbe ich von dict.

Meine Frage lautet jedoch: Muss ich dictin meiner __init__()Methode ein privates Mitglied erstellen ? Ich verstehe den Sinn davon nicht, da ich das dictVerhalten bereits habe, wenn ich einfach von erbe dict.

Kann jemand darauf hinweisen, warum die meisten Vererbungsschnipsel wie die folgenden aussehen?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Anstelle der einfacheren ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

Ich glaube, ich vermute, dass die Antwort auf die Frage so ist, dass Benutzer nicht direkt auf Ihr Wörterbuch zugreifen können (dh sie müssen die von Ihnen angegebenen Zugriffsmethoden verwenden).

Was ist jedoch mit dem Array-Zugriffsoperator []? Wie würde man das umsetzen? Bisher habe ich kein Beispiel gesehen, das zeigt, wie der []Operator überschrieben wird .

Wenn []in der benutzerdefinierten Klasse keine Zugriffsfunktion bereitgestellt wird, arbeiten die geerbten Basismethoden mit einem anderen Wörterbuch.

Ich habe das folgende Snippet ausprobiert, um mein Verständnis der Python-Vererbung zu testen:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Ich habe folgenden Fehler erhalten:

KeyError: <integrierte Funktions-ID>

Was ist mit dem obigen Code falsch?

Wie korrigiere ich die Klasse, myDictdamit ich solchen Code schreiben kann?

md = myDict()
md['id'] = 123

[Bearbeiten]

Ich habe das obige Codebeispiel bearbeitet, um den dummen Fehler zu beseitigen, den ich gemacht habe, bevor ich von meinem Schreibtisch weggerannt bin. Es war ein Tippfehler (ich hätte ihn anhand der Fehlermeldung erkennen sollen).

skyeagle
quelle

Antworten:

104
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Ricky Wilson
quelle
37
Wenn Sie in eine Unterklasse wechseln möchten dict, sollten Sie das Objekt selbst verwenden (using super), anstatt es einfach an die Instanz zu delegieren. __dict__Dies bedeutet im Wesentlichen, dass Sie für jede Instanz zwei Diktate erstellen.
Aaron Hall
8
self .__ dict__ ist nicht dasselbe wie der eigentliche Wörterbuchinhalt. Jedes Python-Objekt hat unabhängig von seinem Typ ein Objekt, _dict__das alle Objektattribute (Methoden, Felder usw.) enthält. Sie möchten nicht damit herumspielen, es sei denn, Sie möchten Code schreiben, der sich selbst
ändert
85

So was

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Jetzt können Sie die integrierten Funktionen wie dict.get()as verwenden self.get().

Sie müssen kein verstecktes verpacken self._dict. Ihre Klasse bereits ist ein dict.

S.Lott
quelle
3
Dies. Es macht keinen Sinn, von zu erben, dictohne zuerst den Konstruktor aufzurufen.
Sykora
1
Beachten Sie, dass Ihr geerbtes dictObjekt tatsächlich zwei Diktatinstanzen enthält: Das erste ist der geerbte Container und das zweite ist das Diktat, das die Klassenattribute enthält. Sie können dies vermeiden, indem Sie Slots verwenden .
Ankostis
1
Das __dict__wird eigentlich nur beim ersten Zugriff erstellt. Solange Benutzer nicht versuchen, es zu verwenden, ist es in Ordnung. __slots__wäre aber schön.
Aaron Hall
9
Verwenden Sie Leerzeichen nach Kommas, die Sie wild haben! ;-)
James Burke
10

Der Vollständigkeit halber hier der Link zu der Dokumentation, die von @ björn-pollex für das neueste Python 2.x (2.7.7 zum Zeitpunkt des Schreibens) erwähnt wurde:

Containertypen emulieren

(Entschuldigung, dass Sie die Kommentarfunktion nicht verwenden. Ich darf dies nur nicht durch Stackoverflow tun.)

he1ix
quelle
3

Das Problem mit diesem Codestück:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... ist, dass Ihre 'add'-Methode (... und jede Methode, die Sie Mitglied einer Klasse werden möchten) ein explizites' self 'als erstes Argument deklarieren muss, wie:

def add(self, 'id', 23):

Um die Überladung des Operators für den Zugriff auf Elemente per Schlüssel zu implementieren, suchen Sie in den Dokumenten nach den magischen Methoden __getitem__und __setitem__.

Da Python Duck Typing verwendet, gibt es möglicherweise keinen Grund, Ihre benutzerdefinierte Diktatklasse aus der Diktatklasse der Sprache abzuleiten - ohne mehr darüber zu wissen, was Sie tun möchten (z. B. wenn Sie eine Instanz davon übergeben müssen Klasse in einen Code an einem Ort, der kaputt geht, es sei denn isinstance(MyDict(), dict) == True), ist es möglicherweise besser, nur die API zu implementieren, die Ihre Klasse ausreichend diktiert und dort stoppt.

bgporter
quelle
3

Hier ist eine alternative Lösung:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2
张小诚
quelle
Dies ist schlecht, da Sie keine benutzerdefinierte Methode definieren und es auch andere Probleme gibt, wie in anderen Antworten angegeben.
Shital Shah
Das ist genau das, was ich brauchte. Danke dir!
Jakebrinkmann
2

Ich sehe wirklich nirgendwo die richtige Antwort darauf

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Alles, was Sie wirklich tun müssen, ist, Ihre eigenen zu definieren __init__- das ist wirklich alles, was es auch gibt.

Ein weiteres Beispiel (etwas komplexer):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Dieses letzte Beispiel ist praktisch, wenn Sie eine Art Logik einbetten möchten.

Wie auch immer ... kurz gesagt, class GiveYourClassAName(dict)es reicht aus, um Ihre Klasse wie ein Diktat zu verhalten. Jede Diktieroperation, die Sie ausführen, ist selfwie ein reguläres Diktat.

Danny Meijer
quelle
1

Das ist meine beste Lösung. Ich habe das oft benutzt.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

Sie können wie folgt verwenden:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
Madogan
quelle
0

Erben Sie niemals von dem in Python integrierten Diktat! Zum Beispiel wird die updateMethode nicht verwendet __setitem__, sie trägt viel zur Optimierung bei. Verwenden Sie UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
user2674414
quelle
1
Basierend auf dem, was jemand niemals von dem eingebauten Diktat erben sollte? Aus der Dokumentation ( docs.python.org/3/library/collections.html#collections.UserDict ): "Die Notwendigkeit für diese Klasse wurde teilweise durch die Möglichkeit ersetzt, direkt aus dem Diktat eine Unterklasse zu erstellen. Diese Klasse kann jedoch einfacher sein arbeiten mit, weil auf das zugrunde liegende Wörterbuch als Attribut zugegriffen werden kann. " Ebenfalls auf derselben Seite: Das Sammlungsmodul lautet "Veraltet seit Version 3.3, wird in Version 3.9 entfernt: Abstrakte Basisklassen für Sammlungen in das Modul collection.abc verschoben." ... ohne UserDict.
NumesSanguis
Ich vermute, dass dies von treyhunner.com/2019/04/…
Tripleee