Seite 1 von 1

Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 11:37
von rogerb
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")

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 11:44
von Sirius3
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.

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 12:58
von rogerb
@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")

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 13:26
von narpfel
@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.

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 14:08
von rogerb
@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!

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 14:53
von narpfel
@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

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 15:24
von rogerb
@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.

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 15:54
von DeaD_EyE
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

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 16:02
von rogerb
@DeaD_EyE, Danke! Das ist nochmal einfacher.

Re: Klasse die sich ähnlich wie ein Dictionary verhält

Verfasst: Montag 12. Juli 2021, 18:04
von kbr
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.