Klasse die sich ähnlich wie ein Dictionary verhält

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Hallo zusammen,

ich brauche eine Klasse die sich grundsätzlich wie ein Dictionary verhält, aber auch noch zusätzliche Funktionen auf die internen Daten implementiert.

Ich habe drei Ansätze, die alle funktionieren, bin mir aber nicht sicher welcher zu bevorzugen ist.
Vielleicht gibt es ja auch noch weitere Möglichkeiten?

1. dict als Basisklasse
2. auf das interne __dict__ Object zugreifen
3. ein eigener Dictionary für die internen Daten

Ein generisches Beispiel aller drei Versionen:

Code: Alles auswählen

class MyDictLikeClassV1(dict):
    def some_function(self):
        print(f"Hier passiert etwas mit {self} was ein normaler Dictionary nicht hergibt")


class MyDictLikeClassV2:
    def __init__(self, a_dictionary=None):
        if a_dictionary:
            self.__dict__.update(a_dictionary)

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

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

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

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

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

    def __repr__(self):
        return f"<{self.__class__.__name__}: {repr(self.__dict__)}>"

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

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

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

    def some_function(self):
        print(f"Hier passiert etwas mit {self} was ein normaler Dictionary nicht hergibt")


class MyDictLikeClassV3:
    def __init__(self, a_dictionary=None):
        if a_dictionary:
            self.__data = a_dictionary

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

    def __setitem__(self, key, value):
        self.__data[key] = value

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

    def __contains__(self, key):
        return key in self.__data

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

    def __repr__(self):
        return f"<{self.__class__.__name__}: {repr(self.__data)}>"

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

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

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

    def some_function(self):
        print(f"Hier passiert etwas mit {self} was ein normaler Dictionary nicht hergibt")
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Deine Klasse in Wörterbuch ist, mit etwas Zusatzfunktionalität, dann ist Vererbung das richtige. Sieht man auch daran, dass das mit zwei Zeilen Code erledigt ist.

Ansonsten würde man auch nicht alles selbst implementieren, sondern von collections.abc.MutableMapping ableiten, damit man wirklich alle Methoden hat, die eine Wörterbuch auch hat.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@Sirius3, Vielen Dank!

Ich werde wohl einfach von dict ableiten.

Ich hab trotzdem mal versucht von MutableMapping abzuleiten. Leider habe ich wenig Informationen dazu gefunden.
So funktioniert es zwar, bin mir aber nicht sicher , ob es der richtige Weg ist.

Code: Alles auswählen

class MyDictLikeClassV2(MutableMapping):
    def __init__(self, a_dictionary=None):
        if a_dictionary:
            self.__dict__.update(a_dictionary)

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

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

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

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

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

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

    def __repr__(self):
        return f"<{self.__class__.__name__}: {repr(self.__dict__)}>"

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

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

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

    def some_function(self):
        print(f"Hier passiert etwas mit {self} was ein normaler Dictionary nicht hergibt")
narpfel
User
Beiträge: 691
Registriert: Freitag 20. Oktober 2017, 16:10

@rogerb: Wobei man nicht von den Builtin-Typen ableiten sollte, weil die Methoden von `dict`, die ein neues `dict` zurückgeben, hardcoded ein `dict` erzeugen und nicht eine Instanz der Kindklasse:

Code: Alles auswählen

In [20]: class Dict(dict):
    ...:     pass
    ...:

In [21]: a = Dict({1: 2})

In [22]: b = Dict([(3, 4)])

In [23]: type(a), type(b)
Out[23]: (__main__.Dict, __main__.Dict)

In [24]: type(a | b)
Out[24]: dict
Dafür gibt es `collections.UserDict`.

Zu deinem Beispiel 2: Das würde man so nicht machen, `__dict__` ist dazu da, die Attribute von einem Objekt zu enthalten. Und auch wenn man von `MutableMapping` erbt, würde man das Dictionary als Attribut haben. Zusätzlich stellt `MutableMapping` schon viele der Methode bereit, die du manuell implementiert hast, du brauchst nur `__{get,set,del}item__`, `__iter__` und `__len__` (und `__init__`) selbst schreiben:

Code: Alles auswählen

In [44]: class Dict(MutableMapping):
    ...:     def __init__(self, *args, **kwargs):
    ...:         if len(args) > 1:
    ...:             raise TypeError(f"Dict expected at most 1 arguments, got {len(args)}")
    ...:         elif args:
    ...:             self._dict = dict(args[0])
    ...:         else:
    ...:             self._dict = {}
    ...:         self._dict.update(kwargs)
    ...:     def __getitem__(self, key):
    ...:         return self._dict[key]
    ...:     def __setitem__(self, key, value):
    ...:         self._dict[key] = value
    ...:     def __delitem__(self, key):
    ...:         del self._dict[key]
    ...:     def __iter__(self):
    ...:         return iter(self._dict)
    ...:     def __len__(self):
    ...:         return len(self._dict)

In [45]: d = Dict([(1, 2)])

In [52]: list(d.items())
Out[52]: [(1, 2)]
Hast du mal in die Doku von `collections.abc` geschaut? Da steht beschrieben, welche Methoden man braucht und welche man gratis bekommt.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@narpfel,

auch dir Vielen Dank! Ja, man liest leider immer wieder wiedersprüchliche Informationen dazu.
Und bei UserDict liest man ja auch, dass es grundsätzlich in Ordnung ist von 'dict' abzuleiten.
Ich bin dann aber auch prompt in Probleme gelaufen, die ich mir nicht erklären konnte.

Generisches Beispiel:

Code: Alles auswählen

class SimpleDict(dict):
    def doprint(self):
        print(self)
        print(self.__dict__)

d = SimpleDict({"A": 1, "B": 2, "C": 3})
d.doprint()
Ausgabe:

Code: Alles auswählen

{'A': 1, 'B': 2, 'C': 3}
{}
Mit deiner Erklärung macht das natürlich Sinn.

Mit MutableMapping funktioniert die doprint() Methode dann wie erwartet:

Code: Alles auswählen

<__main__.SimpleDict object at 0x0000029EF4985190>
{'A': 1, 'B': 2, 'C': 3}
Ja, ich hatte nachgeschaut, welche Methoden ich für MutableMapping brauche, hab dann aber wie man sieht, zu viele weitere implementiert.

Danke für dein Beispiel. Jetzt ist das auch klar!
narpfel
User
Beiträge: 691
Registriert: Freitag 20. Oktober 2017, 16:10

@rogerb: Nochmal: `__dict__` ist dafür da, die Attribute von einem Objekt zu speichern, und nicht dafür gedacht, da irgendwelche Werte drinnen zu speichern:

Code: Alles auswählen

In [53]: class Foo:
    ...:     def __init__(self):
    ...:         self.__dict__ = {"foo": 42, "bar": 27}
    ...:

In [54]: foo = Foo()

In [55]: foo.foo
Out[55]: 42

In [56]: foo.bar
Out[56]: 27
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@narpfel, ja, ich hatte es verstanden.

Ich habe selber ja auch in dem kleinen Beispiel nichts in '__dict__' gespeichert.
Ich wollte dir nur zeigen, dass deine Erklärung (... weil die Methoden von `dict`, die ein neues `dict` zurückgeben, hardcoded ein `dict` erzeugen und nicht eine Instanz der Kindklasse... )
zu dem Fehler passt, in den ich vorher beim erben von 'dict' gelaufen war.

Das führt nämlich dazu, dass __dict__ einfach ein leerer Dictionary ist. Wie gesagt, ich hab da nichts überschrieben.
Dies ist nur ein Beispiel, welches das Problem beim beim erben von dict zeigt.

Code: Alles auswählen

class SimpleDict(dict):
    def doprint(self):
        print(self)
        print(self.__dict__)

d = SimpleDict({"A": 1, "B": 2, "C": 3})
d.doprint()

"""
{'A': 1, 'B': 2, 'C': 3}
{}
"""


Ich habe es jetzt so umgesetzt wie du vorgeschlagen hattest. Die Dict-Klasse erbt von MutableMapping mit den entsprechenden methoden.
In meiner Dict-Klasse habe ich ein Instanzattribut '_dict' welches die Daten enthält.

Also, ich habe es fast komplett von dir übernommen und nur noch eine __repr__ Methode und ein paar andere eigene Funktionen hinzugefügt.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1240
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Nimm einfach ein UserDict aus dem Modul collections, dann sind das 3 Zeilen Code.

Code: Alles auswählen

from collections import UserDict


class MyDict(UserDict):
    def some_function(self):
        print(f"Hier passiert etwas mit {self} was ein normaler Dictionary nicht hergibt")

MyDict({"a": 42}).some_function()
Hier passiert etwas mit {'a': 42} was ein normaler Dictionary nicht hergibt
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@DeaD_EyE, Danke! Das ist nochmal einfacher.
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

Das Problem beim Erben von eingebauten Datentypen besteht u.a. darin, dass vieles in C programmiert ist und überladene Methoden in abgeleiteten Klassen teilweise ignoriert werden.
Antworten