Python-Zuordnungen: Ein umfassender Leitfaden


Eine der wichtigsten Datenstrukturen, die Sie zu Beginn Ihrer Python-Lernreise kennen lernen, ist das Wörterbuch. Wörterbücher sind die gebräuchlichsten und bekanntesten Mappings von Python. Es gibt jedoch andere Zuordnungen in der Standardbibliothek von Python und in Modulen von Drittanbietern. Zuordnungen weisen gemeinsame Merkmale auf, und wenn Sie diese gemeinsamen Merkmale verstehen, können Sie sie effektiver nutzen.

In diesem Tutorial erfahren Sie Folgendes:

  • Grundlegende Merkmale einer Zuordnung
  • Vorgänge, die den meisten Zuordnungen gemeinsam sind
  • Abstrakte Basisklassen Mapping und MutableMapping
  • Benutzerdefinierte veränderliche und unveränderliche Zuordnungen und wie man sie erstellt

In diesem Tutorial wird davon ausgegangen, dass Sie mit den in Python integrierten Datentypen, insbesondere Wörterbüchern, und mit den Grundlagen der objektorientierten Programmierung vertraut sind.

Die Hauptmerkmale von Python-Zuordnungen verstehen

Eine Zuordnung ist eine Sammlung, die es Ihnen ermöglicht, einen Schlüssel nachzuschlagen und seinen Wert abzurufen. Die Schlüssel in Zuordnungen können Objekte verschiedenster Typen sein. Allerdings gibt es in den meisten Zuordnungen Objekttypen, die nicht als Schlüssel verwendet werden können, wie Sie später in diesem Tutorial erfahren werden.

Im vorherigen Absatz wurden Zuordnungen als Sammlungen beschrieben. Eine Sammlung ist ein iterierbarer Container mit einer definierten Größe. Allerdings verfügen Mappings auch über zusätzliche Funktionen. Sie werden jedes dieser Mapping-Merkmale anhand von Beispielen aus den wichtigsten Mapping-Typen von Python erkunden.

Das charakteristischste Merkmal von Mappings ist die Möglichkeit, einen Wert mithilfe eines Schlüssels abzurufen. Sie können ein Wörterbuch verwenden, um diesen Vorgang zu demonstrieren:

>>> points = {
...     "Denise": 3,
...     "Igor": 2,
...     "Sarah": 3,
...     "Trevor": 1,
... }
>>> points["Sarah"]
3
>>> points["Matt"]
Traceback (most recent call last):
  ...
KeyError: 'Matt'

Das Wörterbuch points enthält vier Elemente mit jeweils einem Schlüssel und einem Wert. Sie können den Schlüssel in den eckigen Klammern verwenden, um den diesem Schlüssel zugeordneten Wert abzurufen. Wenn der Schlüssel jedoch nicht im Wörterbuch vorhanden ist, löst der Code einen KeyError aus.

Sie können eine der Zuordnungen im Modul collections der Standardbibliothek verwenden, um einen Standardwert für Schlüssel zuzuweisen, die nicht in der Sammlung vorhanden sind. Der Typ defaultdict enthält ein Callable, das jedes Mal aufgerufen wird, wenn Sie versuchen, auf einen nicht vorhandenen Schlüssel zuzugreifen. Wenn Sie möchten, dass der Standardwert Null ist, können Sie eine lambda-Funktion verwenden, die 0 als erstes Argument in defaultdict zurückgibt:

>>> from collections import defaultdict
>>> points_default = defaultdict(
...     lambda: 0,
...     points,
... )

>>> points_default
defaultdict(<function <lambda> at 0x104a95da0>, {'Denise': 3,
    'Igor': 2, 'Sarah': 3, 'Trevor': 1})
>>> points_default["Sarah"]
3
>>> points_default["Matt"]
0
>>> points_default
defaultdict(<function <lambda> at 0x103e6c700>, {'Denise': 3,
    'Igor': 2, 'Sarah': 3, 'Trevor': 1, 'Matt': 0})

Der defaultdict-Konstruktor hat in diesem Beispiel zwei Argumente. Das erste Argument ist das aufrufbare Argument, das verwendet wird, wenn ein Standardwert benötigt wird. Das zweite Argument ist das Wörterbuch, das Sie zuvor erstellt haben. Sie können jedes gültige Argument verwenden, wenn Sie dict() als zweites Argument in defaultdict() aufrufen, oder dieses Argument weglassen, um ein leeres defaultdict zu erstellen .

Wenn Sie auf einen Schlüssel zugreifen, der im Wörterbuch fehlt, wird der Schlüssel hinzugefügt und ihm der Standardwert zugewiesen. Sie können das gleiche points_default-Objekt auch mit dem aufrufbaren int als erstem Argument erstellen, da der Aufruf von int() ohne Argumente 0 zurückgibt.

Alle Zuordnungen sind auch Sammlungen, das heißt, es handelt sich um iterierbare Container mit einer definierten Länge. Sie können diese Eigenschaften mit einer anderen Zuordnung in der Standardbibliothek von Python, collections.Counter, untersuchen:

>>> from collections import Counter
>>> letters = Counter("learning python")
>>> letters
Counter({'n': 3, 'l': 1, 'e': 1, 'a': 1, 'r': 1, 'i': 1, 'g': 1,
    ' ': 1, 'p': 1, 'y': 1, 't': 1, 'h': 1, 'o': 1})

Die Buchstaben in der Zeichenfolge "learning python" werden in Counter in Schlüssel umgewandelt, und die Häufigkeit des Vorkommens jedes Buchstabens wird als Wert für jeden Schlüssel verwendet.

Sie können bestätigen, dass diese Zuordnung iterierbar ist, eine definierte Länge hat und ein Container ist:

>>> for letter in letters:
...     print(letter)
...
l
e
a
r
n
i
g

p
y
t
h
o

>>> len(letters)
13

>>> "n" in letters
True
>>> "x" in letters
False

Sie können das Counter-Objekt letters in einer for-Schleife verwenden, um zu bestätigen, dass es iterierbar ist. Alle Zuordnungen sind iterierbar. Die Iteration durchläuft jedoch die Schlüssel und nicht die Werte. Später in diesem Tutorial erfahren Sie, wie Sie die Werte oder sowohl Schlüssel als auch Werte durchlaufen.

Die integrierte Funktion len() gibt die Anzahl der Elemente in der Zuordnung zurück. Dies entspricht der Anzahl der eindeutigen Zeichen in der Originalzeichenfolge, einschließlich des Leerzeichens. Die Größe des Objekts ist gegeben, da len() einen Wert zurückgibt.

Sie können das Schlüsselwort in verwenden, um zu bestätigen, welche Elemente in der Zuordnung enthalten sind. Diese Prüfung allein reicht nicht aus, um zu bestätigen, dass es sich bei der Zuordnung um einen Container handelt. Sie können jedoch auch direkt auf die spezielle Methode .__contains__() des Objekts zugreifen:

>>> letters.__contains__("n")
True

Wie Sie sehen, bestätigt das Vorhandensein dieser speziellen Methode, dass es sich bei letters um einen Container handelt.

Die spezielle Methode .__getitem__() in Zuordnungen

Die Merkmale, die Sie im ersten Abschnitt kennengelernt haben, werden mithilfe spezieller Methoden innerhalb von Klassendefinitionen definiert. Daher verfügen Zuordnungen über eine spezielle Methode .__iter__(), um sie iterierbar zu machen, eine spezielle Methode .__contains__(), um sie als Container zu definieren, und eine spezielle Methode .__len__() spezielle Methode, um ihnen eine Größe zu geben.

Zuordnungen verfügen außerdem über die spezielle Methode .__getitem__(), um sie abonnierbar zu machen. Ein Objekt ist abonnierbar, wenn Sie nach dem Objekt eckige Klammern hinzufügen können, z. B. my_object[item]. In einer Zuordnung ist der Wert, den Sie in den eckigen Klammern verwenden, der Schlüssel in einem Schlüssel-Wert-Paar und wird verwendet, um den Wert abzurufen, der dem Schlüssel entspricht.

Die spezielle Methode .__getitem__() stellt die Schnittstelle für die eckige Klammernotation bereit. Im Python-Wörterbuch und anderen Zuordnungen wird dieser Datenabruf mithilfe einer Hash-Tabelle implementiert, was den Datenzugriff effizient macht. Weitere Informationen zu Hash-Tabellen und ihrer Implementierung in Pythons Wörterbüchern finden Sie unter „Erstellen einer Hash-Tabelle in Python mit TDD“.

Wenn Sie Ihre eigene Zuordnung erstellen, müssen Sie die spezielle Methode .__getitem__() implementieren, um Werte aus Schlüsseln abzurufen. In den meisten Fällen besteht die beste Option darin, das Python-Wörterbuch oder andere in der Standardbibliothek von Python implementierte Zuordnungen zu verwenden, um den effizienten Datenzugriff zu nutzen, der bereits in diesen Datenstrukturen implementiert ist.

Sie werden diese Ideen später in diesem Tutorial erkunden, wenn Sie eine benutzerdefinierte Zuordnung erstellen.

Schlüssel, Werte und Elemente in Zuordnungen

Kehren Sie zu einer der Zuordnungen zurück, die Sie zuvor in diesem Tutorial verwendet haben, dem Wörterbuch points:

>>> points = {
...     "Denise": 3,
...     "Igor": 2,
...     "Sarah": 3,
...     "Trevor": 1,
... }

Das Wörterbuch besteht aus vier Schlüsseln, denen vier Werte zugeordnet sind. Jedes Mapping wird durch diese Schlüssel-Wert-Paare charakterisiert. Jedes Schlüssel-Wert-Paar ist ein Element. Daher verfügt dieses Wörterbuch über vier Elemente. Sie können dies mit len(points) bestätigen, das die Ganzzahl 4 zurückgibt.

Python-Zuordnungen verfügen über drei Methoden namens .keys(), .values() und .items(). Sie können damit beginnen, die ersten beiden davon zu erkunden:

>>> points.keys()
dict_keys(['Denise', 'Igor', 'Sarah', 'Trevor'])

>>> points.values()
dict_values([3, 2, 3, 1])

Diese Methoden sind nützlich, wenn Sie nur auf die Schlüssel oder nur auf die Werte in einem Wörterbuch zugreifen müssen. Die Methode .items() gibt die in Tupeln gepaarten Elemente des Mappings zurück:

>>> points.items()
dict_items([('Denise', 3), ('Igor', 2), ('Sarah', 3), ('Trevor', 1)])

Das von .items() zurückgegebene Objekt ist nützlich, wenn Sie als Iterable auf das Schlüssel-Wert-Paar zugreifen müssen, beispielsweise wenn Sie die Zuordnung durchlaufen und jeweils auf Schlüssel und Wert zugreifen müssen Artikel:

>>> for name, number in points.items():
...     print(f"Number of points for {name}: {number}")
...
Number of points for Denise: 3
Number of points for Igor: 2
Number of points for Sarah: 3
Number of points for Trevor: 1

Sie können auch bestätigen, dass andere Zuordnungen über diese Methoden verfügen:

>>> from collections import Counter
>>> letters = Counter("learning python")
>>> letters
Counter({'n': 3, 'l': 1, 'e': 1, 'a': 1, 'r': 1, 'i': 1, 'g': 1,
    ' ': 1, 'p': 1, 'y': 1, 't': 1, 'h': 1, 'o': 1})

>>> letters.keys()
dict_keys(['l', 'e', 'a', 'r', 'n', 'i', 'g', ' ', 'p', 'y', 't',
    'h', 'o'])

>>> letters.values()
dict_values([1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1])

>>> letters.items()
dict_items([('l', 1), ('e', 1), ('a', 1), ('r', 1), ('n', 3),
    ('i', 1), ('g', 1), (' ', 1), ('p', 1), ('y', 1), ('t', 1),
    ('h', 1), ('o', 1)])

Diese Methoden geben weder eine Liste noch ein Tupel zurück. Stattdessen geben sie dict_keys-, dict_values- oder dict_items-Objekte zurück. Sogar die für das Counter-Objekt aufgerufenen Methoden geben dieselben drei Datentypen zurück, da viele Zuordnungen auf der dict-Implementierung basieren.

Die Objekte dict_keys, dict_values und dict_items sind Wörterbuchansichten. Diese Objekte enthalten keine eigenen Daten, bieten jedoch eine Ansicht der im Mapping gespeicherten Daten. Um mit dieser Idee zu experimentieren, können Sie einer Variablen eine der Ansichten zuweisen und dann die Daten in der ursprünglichen Zuordnung ändern:

>>> values_in_points = points.values()
>>> values_in_points
dict_values([3, 2, 3, 1])

>>> points["Igor"] += 10

>>> values_in_points
dict_values([3, 12, 3, 1])

Sie weisen das von .values() zurückgegebene dict_values-Objekt values_in_points zu. Wenn Sie das Wörterbuch aktualisieren, ändern sich auch die Werte in values_in_points.

Die Unterscheidung zwischen Schlüsseln, Werten und Elementen ist bei der Arbeit mit Zuordnungen von wesentlicher Bedeutung. Sie werden später in diesem Tutorial noch einmal auf die Methoden für den Zugriff darauf zurückkommen, wenn Sie Ihre eigene Zuordnung erstellen.

Vergleich zwischen Zuordnungen, Sequenzen und Mengen

Zu Beginn dieses Tutorials haben Sie erfahren, dass eine Zuordnung eine Sammlung ist, in der Sie über einen damit verknüpften Schlüssel auf einen Wert zugreifen können. Zuordnungen sind nicht die einzige Sammlung in Python. Sequenzen und Mengen sind ebenfalls Sammlungen. Zu den gängigen Sequenzen gehören Listen, Tupel und Zeichenfolgen.

Es ist hilfreich, die Ähnlichkeiten und Unterschiede zwischen diesen Kategorien zu verstehen, um Zuordnungen besser zu verstehen. Alle Sammlungen sind iterierbare Container mit einer definierten Länge. Objekte, die in eine dieser drei Kategorien fallen, haben diese Eigenschaften gemeinsam.

Zuordnungen und Sequenzen sind abonnierbar. Sie können die eckige Klammernotation verwenden, um innerhalb von Zuordnungen und Sequenzen auf Werte zuzugreifen. Dieses Merkmal wird durch die spezielle Methode .__getitem__() definiert. Sets hingegen können nicht abonniert werden:

>>> # Mapping
>>> points = {
...     "Denise": 3,
...     "Igor": 2,
...     "Sarah": 3,
...     "Trevor": 1,
... }
>>> points["Igor"]
2

>>> # Sequence
>>> numbers = [4, 10, 34]
>>> numbers[1]
10

>>> # Set
>>> numbers_set = {4, 10, 34}
>>> numbers_set[1]
Traceback (most recent call last):
  ...
TypeError: 'set' object is not subscriptable

>>> numbers_set.__getitem__
Traceback (most recent call last):
  ...
AttributeError: 'set' object has no attribute '__getitem__'.
    Did you mean: '__getstate__'?

Sie können die Notation in eckigen Klammern verwenden, um auf Werte aus einem Wörterbuch und einer Liste zuzugreifen. Das Gleiche gilt für alle Mappings und Sequenzen. Da Mengen jedoch nicht über die spezielle Methode .__getitem__() verfügen, können sie nicht indiziert werden.

Bei der Verwendung der eckigen Klammernotation gibt es jedoch Unterschiede zwischen Zuordnungen und Sequenzen. Sequenzen sind geordnete Strukturen, und die Notation mit eckigen Klammern ermöglicht die Indizierung mithilfe von Ganzzahlen, die die Position des Elements in der Sequenz darstellen. Bei Sequenzen können Sie auch ein Slice in die eckigen Klammern einfügen. Beim Subskribieren einer Sequenz sind nur Ganzzahlen und Slices zulässig.

Zuordnungen müssen nicht geordnet sein und Sie können die Notation in eckigen Klammern nicht verwenden, um auf ein Element basierend auf seiner Position in der Struktur zuzugreifen. Stattdessen verwenden Sie den Schlüssel in einem Schlüssel-Element-Paar innerhalb der eckigen Klammern. Außerdem sind die Objekte, die Sie innerhalb der eckigen Klammern in einer Zuordnung verwenden können, nicht auf Ganzzahlen und Slices beschränkt.

Für die meisten Zuordnungen können Sie jedoch keine veränderlichen Objekte oder unveränderlichen Strukturen verwenden, die veränderliche Objekte enthalten. Diese Anforderung wird durch die Hash-Tabelle gestellt, die zur Implementierung von Wörterbüchern und anderen Zuordnungen verwendet wird:

>>> {[0, 0]: None, [1, 1]: None}
Traceback (most recent call last):
  ...
TypeError: unhashable type: 'list'

>>> from collections import Counter
>>> number_groups = ([1, 2, 3], [1, 2, 3], [2, 3, 4])
>>> Counter(number_groups)
Traceback (most recent call last):
  ...
TypeError: unhashable type: 'list'

Wie in diesem Beispiel gezeigt, können Sie eine Liste nicht als Schlüssel in einem Wörterbuch verwenden, da Listen veränderbar und nicht hashbar sind. Und obwohl number_groups ein Tupel ist, können Sie damit kein Counter-Objekt erstellen, da das Tupel Listen enthält.

Zuordnungen sind keine geordnete Datenstruktur. Elemente in Wörterbüchern behalten jedoch die Reihenfolge bei, in der sie hinzugefügt wurden. Diese Funktion ist seit Python 3.6 vorhanden und wurde der formalen Sprachbeschreibung in Python 3.7 hinzugefügt. Auch wenn die Reihenfolge der Wörterbuchelemente beibehalten wird, sind Wörterbücher keine geordnete Struktur wie Sequenzen. Hier ist eine Demonstration dieses Unterschieds:

>>> [1, 2] == [2, 1]
False
>>> {"one": 1, "two": 2} == {"two": 2, "one": 1}
True

Die beiden Listen sind nicht gleich, da sich die Werte an unterschiedlichen Positionen befinden. Die beiden Wörterbücher sind jedoch gleich, da sie dieselben Schlüssel-Wert-Paare haben, auch wenn die Reihenfolge, in der sie enthalten sind, unterschiedlich ist.

In den meisten Zuordnungen, die auf dem Python-Wörterbuch basieren, müssen Schlüssel eindeutig sein. Dies ist eine weitere Anforderung an die Hash-Tabelle, die zum Implementieren von Wörterbüchern und anderen Zuordnungen verwendet wird. Elemente in Sequenzen müssen jedoch nicht eindeutig sein. Viele Sequenzen haben wiederholte Werte.

Die Anforderung, eindeutige hashbare Objekte als Schlüssel in Python-Zuordnungen zu verwenden, ergibt sich aus der Implementierung von Wörterbüchern. Es handelt sich hierbei nicht um eine inhärente Anforderung für Mappings. Die meisten Zuordnungen basieren jedoch auf dem Python-Wörterbuch und haben daher dieselben Anforderungen.

Sets haben auch eindeutige Werte, die hashbar sein müssen. Set-Elemente haben viele Gemeinsamkeiten mit Wörterbuchschlüsseln, da sie ebenfalls mithilfe einer Hash-Tabelle implementiert werden. Allerdings haben Elemente in einer Menge kein Schlüssel-Wert-Paar und Sie können nicht mit der Notation in eckigen Klammern auf ein Element einer Menge zugreifen.

Erkundung der abstrakten Basisklassen Mapping und MutableMapping

Python verfügt über abstrakte Basisklassen, die Schnittstellen für Datentypkategorien wie Zuordnungen definieren. In diesem Abschnitt erfahren Sie mehr über die abstrakten Basisklassen Mapping und MutableMapping, die Sie im Modul collections.abc finden.

Diese Klassen können verwendet werden, um zu überprüfen, ob ein Objekt eine Instanz einer Zuordnung ist:

>>> from collections import Counter
>>> from collections.abc import Mapping, MutableMapping
>>> points = {
...     "Denise": 3,
...     "Igor": 2,
...     "Sarah": 3,
...     "Trevor": 1,
... }

>>> isinstance(points, Mapping)
True
>>> isinstance(points, MutableMapping)
True

>>> letters = Counter("learning python")
>>> isinstance(letters, MutableMapping)
True

Das Wörterbuch points ist ein Mapping und ein MutableMapping. Alle MutableMapping-Objekte sind auch Mapping-Objekte. Das Counter-Objekt gibt auch True zurück, wenn Sie prüfen, ob es sich um ein MutableMapping handelt. Sie könnten stattdessen prüfen, ob points ein dict und letters ein Counter-Objekt ist.

Wenn Sie jedoch lediglich ein Objekt als Mapping benötigen, ist es vorzuziehen, die abstrakten Basisklassen zu verwenden. Diese Idee passt gut zur Duck-Typing-Philosophie von Python, da Sie prüfen, was ein Objekt tun kann, und nicht, welchen Typ es hat.

Die abstrakten Basisklassen können auch für Typhinweise und zum Erstellen benutzerdefinierter Zuordnungen durch Vererbung verwendet werden. Wenn Sie jedoch eine benutzerdefinierte Zuordnung erstellen müssen, stehen Ihnen auch andere Optionen zur Verfügung, über die Sie später in diesem Tutorial mehr erfahren.

Merkmale der abstrakten Basisklasse Mapping

Die abstrakte Basisklasse Mapping definiert die Schnittstelle für alle Zuordnungen, indem sie mehrere Methoden bereitstellt und sicherstellt, dass erforderliche spezielle Methoden enthalten sind.

Die erforderlichen speziellen Methoden, die Sie beim Erstellen einer Zuordnung definieren müssen, sind die folgenden:

  • .__getitem__(): Definiert, wie auf Werte mithilfe der eckigen Klammernotation zugegriffen wird.
  • .__iter__(): Definiert, wie die Zuordnung durchlaufen wird.
  • .__len__(): Definiert die Größe der Zuordnung.

Die abstrakte Basisklasse Mapping stellt außerdem die folgenden Methoden bereit:

  • .__contains__: Definiert, wie die Mitgliedschaft in der Zuordnung bestimmt wird.
  • .__eq__(): Definiert, wie die Gleichheit zweier Objekte bestimmt wird.
  • .__ne__(): Definiert, wie festgestellt wird, wann zwei Objekte nicht gleich sind.
  • .keys(): Definiert, wie auf die Schlüssel im Mapping zugegriffen wird.
  • .values(): Definiert, wie auf die Werte im Mapping zugegriffen wird.
  • .items(): Definiert, wie auf die Schlüssel-Wert-Paare im Mapping zugegriffen wird.
  • .get(): Definiert eine alternative Möglichkeit, mithilfe von Schlüsseln auf Werte zuzugreifen. Mit dieser Methode können Sie einen Standardwert festlegen, der verwendet wird, wenn der Schlüssel nicht in der Zuordnung vorhanden ist.

Jede Zuordnung in Python umfasst mindestens diese Methoden. Im folgenden Abschnitt erfahren Sie mehr über die Methoden, die auch in veränderlichen Zuordnungen enthalten sind.

Merkmale der abstrakten Basisklasse MutableMapping

Die abstrakte Basisklasse Mapping enthält keine Methoden, die zum Vornehmen von Änderungen an der Zuordnung erforderlich sind. Es erstellt eine unveränderliche Zuordnung. Es gibt jedoch eine zweite abstrakte Basisklasse namens MutableMapping, um die mutable-Version zu erstellen.

MutableMapping erbt von Mapping. Daher umfasst es alle in Mapping vorhandenen Methoden, verfügt jedoch über zwei zusätzliche erforderliche Spezialmethoden:

  • .__setitem__(): Definiert, wie ein neuer Wert für einen Schlüssel festgelegt wird.
  • .__delitem__(): Definiert, wie ein Element im Mapping gelöscht wird.

Die abstrakte Basisklasse MutableMapping fügt außerdem diese Methoden hinzu:

  • .pop(): Definiert, wie ein Schlüssel aus einer Zuordnung entfernt und sein Wert zurückgegeben wird.
  • .popitem(): Definiert, wie das zuletzt hinzugefügte Element in einer Zuordnung entfernt und zurückgegeben wird.
  • .clear(): Definiert, wie alle Elemente aus der Zuordnung entfernt werden.
  • .update(): Definiert, wie ein Wörterbuch mithilfe von Daten aktualisiert wird, die als Argument an diese Methode übergeben werden.
  • .setdefault(): Definiert, wie ein Schlüssel mit einem Standardwert hinzugefügt wird, wenn der Schlüssel noch nicht in der Zuordnung vorhanden ist.

Möglicherweise kennen Sie viele dieser Methoden aus der Python-Datenstruktur dict. Sie finden diese Methoden in allen veränderlichen Zuordnungen. Zusätzlich zu diesem Satz können Mappings auch über andere Methoden verfügen. Beispielsweise verfügt ein Counter-Objekt über eine Methode .most_common(), die den häufigsten Schlüssel zurückgibt.

Im Rest dieses Tutorials verwenden Sie Mapping und MutableMapping, um eine benutzerdefinierte Zuordnung zu erstellen und viele dieser Methoden zu verwenden.

Erstellen einer benutzerdefinierten Zuordnung

In den folgenden Abschnitten dieses Tutorials erstellen Sie eine benutzerdefinierte Klasse für eine benutzerdefinierte Zuordnung. Sie erstellen einen Kurs zum Erstellen von Menüelementen für eine lokale Pizzeria. Dem Restaurantbesitzer ist aufgefallen, dass viele Kunden die falschen Pizzen bestellen und sich dann beschweren. Ein Teil des Problems besteht darin, dass mehrere Menüpunkte unbekannte Namen verwenden und Kunden oft Fehler machen, wenn sie Pizzanamen verwenden, die mit demselben Buchstaben beginnen.

Der Pizzeriabesitzer hat beschlossen, nur Pizzen aufzunehmen, die mit unterschiedlichen Buchstaben beginnen. Sie erstellen eine Zuordnung, um sicherzustellen, dass Schlüssel nicht mit demselben Buchstaben beginnen. Sie sollten auch in der Lage sein, auf den mit jedem Schlüssel verknüpften Wert zuzugreifen, indem Sie den vollständigen Schlüssel oder nur den ersten Buchstaben verwenden. Daher geben menu["Margherita"] und menu["m"] denselben Wert zurück. Der mit jedem Schlüssel verknüpfte Wert ist der Preis der Pizza.

In diesem Abschnitt erstellen Sie eine Klasse, die von der abstrakten Basisklasse Mapping erbt. Dadurch erhalten Sie einen guten Einblick in die Funktionsweise von Mappings. In späteren Abschnitten arbeiten Sie an anderen Möglichkeiten, dieselbe Klasse zu erstellen.

Sie können damit beginnen, eine neue Klasse zu erstellen, die von Mapping erbt und ein Wörterbuch als einziges Argument akzeptiert:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu: dict):
        self._menu = menu

Die Klasse PizzaMenu erbt von Mapping und ihre spezielle Methode .__init__() akzeptiert ein Wörterbuch, das dem ._menu zugewiesen ist Datenattribut. Der führende Unterstrich in ._menu gibt an, dass auf dieses Attribut nicht außerhalb der Klasse zugegriffen werden soll.

Sie können diese Klasse in einer REPL-Sitzung testen:

>>> from pizza_menu import PizzaMenu
>>> menu = PizzaMenu({"Margherita": 9.5, "Pepperoni": 10.5})
Traceback (most recent call last):
  ...
TypeError: Can't instantiate abstract class PizzaMenu without
    an implementation for abstract methods '__getitem__',
    '__iter__', '__len__'

Sie versuchen, eine Instanz von PizzaMenu mithilfe eines Wörterbuchs zu erstellen, das zwei Elemente enthält. Der Code löst jedoch einen TypeError aus. Da PizzaMenu von der abstrakten Basisklasse Mapping erbt, muss es über die drei erforderlichen Spezialmethoden .__getitem__(), .__iter__( ) und .__len__().

Das PizzaMenu-Objekt enthält das Datenattribut ._menu, bei dem es sich um ein Wörterbuch handelt. Daher können Sie die Eigenschaften dieses Wörterbuchs verwenden, um diese speziellen Methoden zu definieren:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu: dict):
        self._menu = menu

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

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

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

Sie definieren .__getitem__() so, dass das Objekt beim Zugriff auf den Wert eines Schlüssels in PizzaMenu den Wert zurückgibt, der diesem Schlüssel im ._menu entspricht. Wörterbuch. Die Definition von .__iter__() stellt sicher, dass das Durchlaufen eines PizzaMenu-Objekts dem Durchlaufen des ._menu-Wörterbuchs und entspricht. __len__() definiert die Größe des PizzaMenu-Objekts als die Größe des ._menu-Wörterbuchs.

Sie können die Klasse in einer neuen REPL-Sitzung testen:

>>> from pizza_menu import PizzaMenu
>>> menu = PizzaMenu({"Margherita": 9.5, "Pepperoni": 10.5})
>>> menu
<pizza_menu.PizzaMenu object at 0x102fb6b10>

Das funktioniert jetzt. Sie haben eine Instanz von PizzaMenu erstellt. Allerdings ist die Ausgabe beim Anzeigen des Objekts nicht hilfreich. Es empfiehlt sich, die spezielle Methode .__repr__() für eine benutzerdefinierte Klasse zu definieren, die eine programmiererfreundliche Zeichenfolgendarstellung des Objekts bereitstellt. Sie können auch die spezielle Methode .__str__() definieren, um eine benutzerfreundliche Zeichenfolgendarstellung bereitzustellen:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    # ...

    def __repr__(self):
        return f"{self.__class__.__name__}({self._menu})"

    def __str__(self):
        return str(self._menu)

Die spezielle Methode .__repr__() erzeugt eine Ausgabe, die zum Neuerstellen des Objekts verwendet werden kann. Sie könnten den Klassennamen direkt in der Zeichenfolge verwenden, aber stattdessen verwenden Sie self.__class__.__name__, um den Klassennamen dynamisch abzurufen. Diese Version stellt sicher, dass die Methode .__repr__() auch für Unterklassen von PizzaMenu wie vorgesehen funktioniert.

Sie können die Ausgabe dieser Methoden in einer neuen REPL-Sitzung bestätigen:

>>> from pizza_menu import PizzaMenu
>>> menu = PizzaMenu({"Margherita": 9.5, "Pepperoni": 10.5})
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5})
>>> print(menu)
{'Margherita': 9.5, 'Pepperoni': 10.5}

Sie erstellen eine Instanz der Klasse aus einem Wörterbuch und zeigen das Objekt an. Klicken Sie unten, um eine alternative .__init__()-Spezialmethode für PizzaMenu anzuzeigen.

Die Methode .__init__(), die Sie für PizzaMenu erstellt haben, akzeptiert ein Wörterbuch als Argument. Daher können Sie ein PizzaMenu nur aus einem anderen Wörterbuch erstellen. Sie können die Methode .__init__() so ändern, dass sie dieselben Argumenttypen akzeptiert, die Sie zum Erstellen einer Instanz eines Wörterbuchs verwenden können, wenn Sie dict() verwenden.

Es gibt vier Arten von Argumenten, die Sie beim Erstellen eines Wörterbuchs mit dict() verwenden können:

  1. Keine Argumente: Sie erstellen ein leeres Wörterbuch, wenn Sie dict() ohne Argumente aufrufen.
  2. Mapping: Sie können jedes Mapping als Argument in dict() verwenden, wodurch aus dem Mapping ein neues Wörterbuch erstellt wird.
  3. Iterable: Sie können in dict() ein Iterable verwenden, das Objektpaare als Argument hat. Das erste Element in jedem Paar wird zum Schlüssel und das zweite Element ist sein Wert im neuen Wörterbuch.
  4. **kwargs: Sie können eine beliebige Anzahl von Schlüsselwortargumenten verwenden, wenn Sie dict() aufrufen. Die Schlüsselwörter werden zu Wörterbuchschlüsseln und die Argumentwerte werden zu Wörterbuchwerten.

Sie können diesen flexiblen Ansatz direkt in PizzaMenu replizieren:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu=None, /, **kwargs):
        self._menu = dict(menu or {}, **kwargs)

    # ...

Mit dieser Version können Sie ein PizzaMenu-Objekt auf unterschiedliche Weise erstellen. Wenn Schlüsselwortargumente vorhanden sind, rufen Sie dict() mit entweder menu oder einem leeren Wörterbuch als erstem Argument auf. Das Schlüsselwort or verwendet eine Kurzschlussauswertung, sodass menu verwendet wird, wenn es wahr ist, und das leere Wörterbuch, wenn menu falsch ist. Wenn keine Schlüsselwortargumente vorhanden sind, rufen Sie dict() entweder mit dem ersten Argument oder mit dem leeren Wörterbuch auf, wenn menu fehlt:

>>> from pizza_menu import PizzaMenu
>>> menu = PizzaMenu({"Margherita": 9.5, "Pepperoni": 10.5})
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5})

>>> menu = PizzaMenu(Margherita=9.5, Pepperoni=10.5)
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5})

>>> menu = PizzaMenu([("Margherita", 9.5), ("Pepperoni", 10.5)])
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5})

>>> menu = PizzaMenu()
>>> menu
PizzaMenu({})

Sie initialisieren eine PizzaMenu-Instanz mit einem Wörterbuch, Schlüsselwortargumenten, einer Liste von Tupeln und schließlich ohne Argumente.

Im Rest dieses Tutorials verwenden Sie die einfachere Spezialmethode .__init__(), damit Sie sich auf andere Aspekte der Zuordnung konzentrieren können.

Ihr nächster Schritt besteht darin, diese Klasse so anzupassen, dass sie der Anforderung entspricht, dass keine Schlüssel mit demselben Buchstaben beginnen dürfen.

Verhindern Sie, dass Pizzanamen mit demselben Buchstaben beginnen

Der Pizzeriabesitzer möchte keine Pizzanamen, die mit demselben Buchstaben beginnen. Sie lösen eine Ausnahme aus, wenn Sie versuchen, eine PizzaMenu-Instanz mit ungültigen Namen zu erstellen:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu: dict):
        self._menu = {}
        first_letters = set()
        for key, value in menu.items():
            first_letter = key[0].lower()
            if first_letter in first_letters:
                raise ValueError(
                    f"'{key}' is invalid."
                    " All pizzas must have unique first letters"
                )
            first_letters.add(first_letter)
            self._menu[key] = value

    # ...

Sie erstellen einen Satz namens first_letters innerhalb von .__init__(). Während Sie das Wörterbuch durchlaufen, konvertieren Sie den ersten Buchstaben in einen Kleinbuchstaben und prüfen, ob der Buchstabe bereits in der Menge first_letters enthalten ist. Da kein erster Buchstabe wiederholt werden kann, löst der Code in der Schleife einen Fehler aus, wenn er einen wiederholten Buchstaben findet.

Wenn der Code keinen Fehler auslöst, fügen Sie den ersten Buchstaben zum Satz hinzu, um sicherzustellen, dass es später in der Iteration keine ungültigen Namen gibt. Sie fügen den Wert auch dem Wörterbuch ._menu hinzu, das Sie als leeres Wörterbuch am Anfang der Methode .__init__() initialisieren.

Sie können dieses Verhalten in einer neuen REPL-Sitzung überprüfen. Sie erstellen ein Wörterbuch mit vorgeschlagenen Namen, um es als Argument für PizzaMenu() zu verwenden:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Meat Feast": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Pizza Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
Traceback (most recent call last):
  ...
ValueError: 'Meat Feast' is invalid. All pizzas must have
    unique first letters

Die Namen in proposed_pizzas enthalten ungültige Einträge. Es gibt zwei Pizzen, die mit M beginnen, und zwei, die mit P beginnen. Sie können die Pizzen umbenennen und es erneut versuchen:

>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5, 'Hawaiian': 11.5,
    'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Napoletana': 11.5,
    'Bianca': 10.5})

Da die Pizzanamen nun keine wiederholten Anfangsbuchstaben mehr enthalten, können Sie eine Instanz von PizzaMenu erstellen.

Wenn Sie durch die Aufnahme einer hawaiianischen Pizza beleidigt sind, lesen Sie weiter. Wenn Ihnen Ananas auf Pizza zusagt, können Sie diesen Abschnitt überspringen!

Mit einem Zusatz zu .__init__() können Sie sicherstellen, dass keine hawaiianischen Pizzen auf Ihrer Speisekarte stehen:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu: dict):
        self._menu = {}
        first_letters = set()
        for key, value in menu.items():
            if key.lower() in ("hawaiian", "pineapple"):
                raise ValueError(
                    "What?! Hawaiian pizza is not allowed"
                )
            first_letter = key[0].lower()
            if first_letter in first_letters:
                raise ValueError(
                    f"'{key}' is invalid."
                    " All pizzas must have unique first letters"
                )
            first_letters.add(first_letter)
            self._menu[key] = value

    # ...

Beim Durchlaufen der Wörterbuchschlüssel fügen Sie eine zusätzliche Bedingung hinzu, um die hawaiianische Pizza auszuschließen. Sie verbieten aus Sicherheitsgründen auch Ananaspizza:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
Traceback (most recent call last):
  ...
ValueError: What?! Hawaiian pizza is not allowed

Hawaiianische Pizzen sind jetzt verboten.

Im nächsten Abschnitt fügen Sie PizzaMenu weitere Funktionen hinzu, damit Sie auf einen Wert zugreifen können, indem Sie entweder den vollständigen Pizzanamen oder nur den Anfangsbuchstaben verwenden.

Fügen Sie eine alternative Möglichkeit zum Zugriff auf Werte in PizzaMenu hinzu

Da alle Pizzen eindeutige Anfangsbuchstaben haben, können Sie die Klasse so ändern, dass Sie den ersten Buchstaben verwenden können, um auf einen Wert von PizzaMenu zuzugreifen. Angenommen, Sie möchten menu["Margherita"] oder menu["m"] verwenden, um auf den Preis einer Margherita-Pizza zuzugreifen.

Sie könnten jeden ersten Buchstaben als Schlüssel in ._menu hinzufügen und ihm denselben Wert zuweisen wie dem Schlüssel mit dem vollständigen Pizzanamen. Dadurch werden jedoch Daten dupliziert. Sie müssen auch vorsichtig sein, wenn Sie den Preis einer Pizza ändern, um sicherzustellen, dass Sie den mit dem Einzelbuchstabenschlüssel verknüpften Wert ändern.

Stattdessen können Sie ein Wörterbuch erstellen, das den ersten Buchstaben dem Pizzanamen zuordnet, der mit diesem Buchstaben beginnt. Sie sammeln bereits die Anfangsbuchstaben jeder Pizza in einem Satz, um sicherzustellen, dass es keine Wiederholungen gibt. Sie können first_letter so umgestalten, dass es ein Wörterbuch statt einer Menge ist. Wörterbuchschlüssel sind ebenfalls eindeutig und können daher anstelle eines Satzes verwendet werden:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    def __init__(self, menu: dict):
        self._menu = {}
        self._first_letters = {}
        for key, value in menu.items():
            first_letter = key[0].lower()
            if first_letter in self._first_letters:
                raise ValueError(
                    f"'{key}' is invalid."
                    " All pizzas must have unique first letters"
                )
            self._first_letters[first_letter] = key
            self._menu[key] = value

    # ...

Sie ersetzen den Satz durch ein Wörterbuch und definieren es als Datenattribut der Instanz, da Sie dieses Wörterbuch an anderer Stelle in der Klassendefinition verwenden müssen. Sie können immer noch überprüfen, ob der erste Buchstabe eines Pizzanamens bereits im Wörterbuch enthalten ist. Allerdings können Sie jetzt auch den vollständigen Namen der Pizza verknüpfen, indem Sie ihn als Wert hinzufügen.

Sie müssen auch .__getitem__() ändern, um die Verwendung eines einzelnen Buchstabens in den eckigen Klammern zu ermöglichen, wenn Sie über ein PizzaMenu-Objekt auf einen Wert zugreifen:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    # ...

    def __getitem__(self, key):
        if key not in self._menu and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.get(key[0].lower(), key)
        return self._menu[key]

    # ...

Die spezielle Methode .__getitem__() kann jetzt ein aus einem Buchstaben bestehendes Argument akzeptieren. Wenn das dem Parameter key zugewiesene Argument nicht in ._menu enthalten ist und kein einzelnes Zeichen ist, lösen Sie eine Ausnahme aus, da der Schlüssel ungültig ist.

Dann rufen Sie .get() für self._first_letters auf, bei dem es sich um ein Wörterbuch handelt. Sie beziehen in diesem Aufruf den Parameter key als Standardwert ein. Wenn key ein einzelner Buchstabe in ._first_letters ist, gibt .get() seinen Wert in diesem Wörterbuch zurück. Dieser Wert wird key neu zugewiesen. Wenn das Argument von .__getitem__() jedoch kein Element von ._first_letters ist, bleibt der Parameter key unverändert, da es sich um den Standardwert handelt in .get().

Sie können diese Änderung in einer neuen REPL-Sitzung bestätigen:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)

>>> menu["Margherita"]
9.5
>>> menu["m"]
9.5

Die Klasse ist jetzt flexibler. Sie können den Preis einer Pizza anhand des vollständigen Namens oder nur des Anfangsbuchstabens ermitteln.

Im nächsten Abschnitt werden Sie andere Methoden erkunden, die Sie in einem Mapping erwarten.

Überschreiben Sie andere für PizzaMenu erforderliche Methoden

Die Merkmale, die allen Zuordnungen gemeinsam sind, haben Sie weiter oben in diesem Tutorial kennengelernt. Da PizzaMenu eine Unterklasse von Mapping ist, erbt es Methoden, die alle Zuordnungen haben.

Sie können überprüfen, ob sich ein PizzaMenu-Objekt wie erwartet verhält, wenn Sie allgemeine Vorgänge ausführen:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
>>> another_menu = PizzaMenu(proposed_pizzas)

>>> menu is another_menu
False

>>> menu == another_menu
True

>>> for item in menu:
...     print(item)
...
Margherita
Pepperoni
Hawaiian
Feast of Meat
Capricciosa
Napoletana
Bianca

>>> "Margherita" in menu
True

>>> "m" in menu
True

Sie erstellen zwei PizzaMenu-Objekte aus demselben Wörterbuch. Die Objekte sind unterschiedlich und daher gibt das Schlüsselwort is beim Vergleich der beiden Objekte False zurück. Der Gleichheitsoperator == gibt jedoch True zurück. Die Objekte sind also gleich, wenn alle Elemente in ._menu gleich sind. Da Sie .__eq__() nicht definiert haben, verwendet Python .__iter__(), um beide Objekte zu durchlaufen und ihre Werte zu vergleichen.

In der REPL-Sitzung bestätigen Sie außerdem, dass beim Durchlaufen eines PizzaMenu wie bei anderen Zuordnungen auch die Tasten durchlaufen werden.

Abschließend bestätigen Sie, dass Sie mit dem Schlüsselwort in überprüfen können, ob ein Pizzaname Mitglied des PizzaMenu-Objekts ist. Da Sie .__contains__() nicht definiert haben, verwendet Python die spezielle Methode .__getitem__(), um nach dem Pizzanamen zu suchen.

Dies zeigt jedoch auch, dass der Buchstabe m ein Element des Menüs ist, da Sie .__getitem__() geändert haben, um sicherzustellen, dass Sie einen einzelnen Buchstaben in der Notation mit eckigen Klammern verwenden können. Wenn Sie es vorziehen, keine einzelnen Buchstaben als Mitglieder des PizzaMenu-Objekts aufzunehmen, können Sie die spezielle Methode .__contains__() definieren:

from collections.abc import Mapping

class PizzaMenu(Mapping):
    # ...

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

Wenn die Methode .__contains__() vorhanden ist, verwendet Python sie, um die Mitgliedschaft zu prüfen. Dies umgeht .__getitem__() und prüft, ob der Schlüssel ein Mitglied des in ._menu gespeicherten Wörterbuchs ist. Sie können bestätigen, dass einzelne Buchstaben in einer neuen REPL-Sitzung nicht mehr als Mitglieder des Objekts betrachtet werden:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)

>>> "Margherita" in menu
True

>>> "m" in menu
False

"Margherita" ist immer noch Mitglied des PizzaMenu-Objekts, aber "m" ist kein Mitglied mehr.

Sie können auch die Methoden erkunden, die die Schlüssel, Werte und Elemente der Zuordnung zurückgeben:

>>> menu.keys()
KeysView(PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5,
    'Hawaiian': 11.5, 'Feast of Meat': 12.5, 'Capricciosa': 12.5,
    'Napoletana': 11.5, 'Bianca': 10.5}))

>>> menu.values()
ValuesView(PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5,
    'Hawaiian': 11.5, 'Feast of Meat': 12.5, 'Capricciosa': 12.5,
    'Napoletana': 11.5, 'Bianca': 10.5}))

>>> menu.items()
ItemsView(PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5,
    'Hawaiian': 11.5, 'Feast of Meat': 12.5, 'Capricciosa': 12.5,
    'Napoletana': 11.5, 'Bianca': 10.5}))

Die Methoden .keys(), .values() und .items() existieren, da sie von der abstrakten Basisklasse geerbt werden. aber sie zeigen nicht die erwarteten Werte an. Stattdessen zeigen sie das gesamte Objekt, also die von .__repr__() zurückgegebene Zeichenfolgendarstellung.

Sie können jedoch diese Ansichten durchlaufen, um die richtigen Werte abzurufen:

>>> for key in menu.keys():
...    print(key)
...
Margherita
Pepperoni
Hawaiian
Feast of Meat
Capricciosa
Napoletana
Bianca

>>> for value in menu.values():
...     print(value)
...
9.5
10.5
11.5
12.5
12.5
11.5
10.5

>>> for item in menu.items():
...     print(item)
...
('Margherita', 9.5)
('Pepperoni', 10.5)
('Hawaiian', 11.5)
('Feast of Meat', 12.5)
('Capricciosa', 12.5)
('Napoletana', 11.5)
('Bianca', 10.5)

Diese Methoden funktionieren wie vorgesehen, ihre Zeichenfolgendarstellungen zeigen jedoch nicht die erwarteten Daten an. Sie können die Methoden .keys(), .values() und .items() im PizzaMenu überschreiben Klassendefinition, wenn Sie diese Anzeige ändern möchten, dies ist jedoch nicht erforderlich.

Mit keiner der Methoden in der abstrakten Basisklasse Mapping können Sie den Inhalt der Zuordnung ändern. Dies ist eine unveränderliche Zuordnung. Im nächsten Abschnitt ändern Sie PizzaMenu in eine veränderbare-Zuordnung.

Erstellen einer benutzerdefinierten veränderlichen Zuordnung

Zu Beginn dieses Tutorials haben Sie die zusätzlichen Methoden kennengelernt, die in der Schnittstelle MutableMapping enthalten sind. Sie können mit der Konvertierung der Klasse PizzaMenu, die Sie im vorherigen Abschnitt erstellt haben, in eine veränderbare Zuordnung beginnen, indem Sie von der abstrakten Basisklasse MutableMapping erben, ohne vorerst weitere Änderungen vorzunehmen:

from collections.abc import MutableMapping

class PizzaMenu(MutableMapping):
    # ...

Sie können versuchen, in einer neuen REPL-Sitzung eine Instanz dieser Klasse zu erstellen:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
Traceback (most recent call last):
  ...
TypeError: Can't instantiate abstract class PizzaMenu without an
    implementation for abstract methods '__delitem__', '__setitem__'

Für die unveränderliche Zuordnung, die Sie im vorherigen Abschnitt erstellt haben, waren drei spezielle Methoden erforderlich. Veränderbare Zuordnungen haben zwei weitere: .__delitem__() und .__setitem__(). Daher müssen Sie diese bei der Unterklassifizierung von MutableMapping einbeziehen.

Elemente im Pizza-Menü ändern, hinzufügen und löschen

Sie können beginnen, indem Sie .__delitem__() zur Klassendefinition hinzufügen:

from collections.abc import MutableMapping

class PizzaMenu(MutableMapping):
    # ...

    def __delitem__(self, key):
        if key not in self._menu and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.pop(key[0].lower(), key)
        del self._menu[key]

Die spezielle Methode .__delitem__() folgt einem ähnlichen Muster wie .__getitem__(). Wenn ein Schlüssel kein einzelner Buchstabe ist und kein Mitglied von ._menu ist, löst die Methode einen KeyError aus. Als nächstes rufen Sie .first_letters.pop() auf und geben key als Standardwert an. Die Methode .pop() entfernt ein Element und gibt es zurück. Sie gibt jedoch den Standardwert zurück, wenn das Element nicht im Wörterbuch enthalten ist.

Wenn es sich bei einem Schlüssel daher um einen einzelnen Buchstaben handelt, der in ._first_letters steht, wird er aus diesem Wörterbuch entfernt. Die letzte Zeile entfernt den Pizza-Eintrag aus ._menu. Dadurch wird der Pizzaname aus beiden Wörterbüchern entfernt.

Die Methode .__setitem__() bedarf weiterer Diskussion, da es mehrere Optionen gibt, die Sie berücksichtigen müssen:

  • Wenn Sie beim Festlegen eines neuen Werts den vollständigen Namen einer vorhandenen Pizza verwenden, sollte .__setitem__() den Wert des vorhandenen Elements in ._menu ändern.
  • Wenn Sie einen einzelnen Buchstaben verwenden, der einer vorhandenen Pizza entspricht, sollte .__setitem__() auch den Wert des vorhandenen Elements in ._menu ändern.
  • Wenn Sie einen Pizzanamen verwenden, der noch nicht in ._menu vorhanden ist, muss der Code die Eindeutigkeit des ersten Buchstabens prüfen, bevor das neue Element zu ._menu hinzugefügt wird.

Sie können diese Punkte in die Definition von .__setitem__() einbeziehen:

from collections.abc import MutableMapping

class PizzaMenu(MutableMapping):
    # ...

    def __setitem__(self, key, value):
        first_letter = key[0].lower()
        if len(key) == 1:
            key = self._first_letters.get(first_letter, key)
        if key in self._menu:
            self._menu[key] = value
        elif first_letter in self._first_letters:
            raise ValueError(
                f"'{key}' is invalid."
                " All pizzas must have unique first letters"
            )
        else:
            self._first_letters[first_letter] = key
            self._menu[key] = value

Diese Methode führt die folgenden Aktionen aus:

  • Es weist first_letter den ersten Buchstaben des Schlüssels zu.
  • Wenn der Schlüssel aus einem einzelnen Buchstaben besteht, wird der vollständige Name der Pizza abgerufen und dem Schlüssel zugewiesen. Dieser Schlüssel wird im Rest dieser Methode verwendet. Wenn keine passende Pizza vorhanden ist, bleibt der key unverändert, um das Hinzufügen einer neuen Pizza mit einem aus einem Buchstaben bestehenden Namen zu ermöglichen.
  • Wenn der Schlüssel ein vollständiger Pizzaname ist, der in ._menu steht, wird der neue Wert diesem Schlüssel zugewiesen.
  • Wenn sich der Schlüssel nicht in ._menu befindet, sein erster Buchstabe jedoch in ._first_letters steht, ist dieser Pizzaname ungültig, da er mit einem Buchstaben beginnt, der bereits verwendet wird. Die Methode löst einen ValueError aus.
  • Schließlich besteht die verbleibende Option darin, einen neuen und gültigen Schlüssel zu erhalten. Der erste Buchstabe wird zu ._first_letters hinzugefügt, und der Pizzaname und der Preis werden als Schlüssel-Wert-Paar in ._menu hinzugefügt.

Beachten Sie, dass Sie den Code, der den ValueError auslöst, zweimal wiederholen. Sie können diese Wiederholung vermeiden, indem Sie eine neue Methode hinzufügen:

from collections.abc import MutableMapping

class PizzaMenu(MutableMapping):
    def __init__(self, menu: dict):
        self._menu = {}
        self._first_letters = {}
        for key, value in menu.items():
            first_letter = key[0].lower()
            if first_letter in self._first_letters:
                self._raise_duplicate_key_error(key)
            self._first_letters[first_letter] = key
            self._menu[key] = value

    def _raise_duplicate_key_error(self, key):
        raise ValueError(
            f"'{key}' is invalid."
            " All pizzas must have unique first letters"
        )

    # ...

    def __setitem__(self, key, value):
        first_letter = key[0].lower()
        if len(key) == 1:
            key = self._first_letters.get(first_letter, key)
        if key in self._menu:
            self._menu[key] = value
        elif first_letter in self._first_letters:
            self._raise_duplicate_key_error(key)
        else:
            self._first_letters[first_letter] = key
            self._menu[key] = value

Die neue Methode ._raise_duplicate_key_error() kann immer dann aufgerufen werden, wenn ein ungültiger Name vorliegt. Sie verwenden dies in .__init__() und .__setitem__().

Sie können jetzt versuchen, ein PizzaMenu-Objekt in einer neuen REPL-Sitzung zu mutieren:

>>> from pizza_menu import PizzaMenu
>>> proposed_pizzas = {
...     "Margherita": 9.50,
...     "Pepperoni": 10.50,
...     "Hawaiian": 11.50,
...     "Feast of Meat": 12.50,
...     "Capricciosa": 12.50,
...     "Napoletana": 11.50,
...     "Bianca": 10.50,
... }

>>> menu = PizzaMenu(proposed_pizzas)
>>> menu
PizzaMenu({'Margherita': 9.5, 'Pepperoni': 10.5, 'Hawaiian': 11.5,
    'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Napoletana': 11.5,
    'Bianca': 10.5})

>>> menu["m"] = 10.25
>>> menu
PizzaMenu({'Margherita': 10.25, 'Pepperoni': 10.5, 'Hawaiian': 11.5,
'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Napoletana': 11.5,
'Bianca': 10.5})

>>> menu["Pepperoni"] += 1.25
>>> menu
PizzaMenu({'Margherita': 10.25, 'Pepperoni': 11.75, 'Hawaiian': 11.5,
    'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Napoletana': 11.5,
    'Bianca': 10.5})

Jetzt können Sie den Wert eines Elements ändern, indem Sie beim Zugriff entweder einen einzelnen Buchstaben oder den vollständigen Namen verwenden. Sie können der Zuordnung auch neue Werte hinzufügen:

>>> menu["Regina"] = 13
>>> menu
PizzaMenu({'Margherita': 10.25, 'Pepperoni': 11.75, 'Hawaiian': 11.5,
    'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Napoletana': 11.5,
    'Bianca': 10.5, 'Regina': 13})

Die veränderliche Zuordnung PizzaMenu verfügt über ein neues Element, das am Ende hinzugefügt wird, da Wörterbücher die Einfügereihenfolge beibehalten. Sie können jedoch keine neue Pizza hinzufügen, wenn sie denselben Anfangsbuchstaben wie eine Pizza hat, die bereits auf der Speisekarte steht:

>>> menu["Plain Pizza"] = 10
Traceback (most recent call last):
  ...
ValueError: 'Plain Pizza' is an invalid name. All pizzas must
    have unique first letters

Sie können Elemente auch aus der Zuordnung löschen:

>>> del menu["Hawaiian"]
>>> menu
PizzaMenu({'Margherita': 10.25, 'Pepperoni': 11.75, 'Feast of Meat': 12.5,
    'Capricciosa': 12.5, 'Napoletana': 11.5, 'Bianca': 10.5})

>>> menu["h"]
Traceback (most recent call last):
  ...
KeyError: 'h'

>>> menu["Hawaiian"]
Traceback (most recent call last):
  ...
KeyError: 'Hawaiian'

Sobald Sie die hawaiianische Pizza entfernt haben, erhalten Sie einen KeyError, wenn Sie versuchen, darauf zuzugreifen, indem Sie entweder einen einzelnen Buchstaben oder den vollständigen Namen verwenden.

Verwenden Sie andere Methoden, die Zuordnungen verändern

Die abstrakte Basisklasse MutableMapping fügt der Klasse außerdem weitere Methoden hinzu, z. B. .pop() und .update(). Sie können überprüfen, ob diese wie erwartet funktionieren:

>>> menu.pop("n")
11.5
>>> menu
PizzaMenu({'Margherita': 10.25, 'Pepperoni': 11.75,
    'Feast of Meat': 12.5, 'Capricciosa': 12.5, 'Bianca': 10.5})

>>> menu.update({"Margherita": 11.5, "c": 14})
>>> menu
PizzaMenu({'Margherita': 11.5, 'Pepperoni': 11.75,
    'Feast of Meat': 12.5, 'Capricciosa': 14, 'Bianca': 10.5})

>>> menu.update({"Festive Pizza": 16})
Traceback (most recent call last):
  ...
ValueError: 'Festive Pizza' is an invalid name. All pizzas must
    have unique first letters

Sie können einen einzelnen Buchstaben in .pop() verwenden, wodurch die Napoletana-Pizza entfernt wird. Die Methode .update() funktioniert auch mit vollständigen Pizzanamen oder einzelnen Buchstaben. Der Preis der Capricciosa-Pizza wird aktualisiert, da Sie beim Aufruf von .update() den Schlüssel "c" angeben.

Sie können .update() auch nicht verwenden, um einen ungültigen Pizzanamen hinzuzufügen. Die festliche Pizza wurde abgelehnt, da es bereits einen anderen Pizzanamen gibt, der mit F beginnt.

Dies zeigt, dass Sie nicht alle diese Methoden definieren müssen, da die speziellen Methoden, die Sie bereits definiert haben, möglicherweise ausreichen. Als Übung können Sie in diesem Beispiel überprüfen, ob die von MutableMapping hinzugefügten Methoden nicht überschrieben werden müssen.

Hier ist die endgültige Version der Klasse PizzaMenu, die von MutableMapping erbt:

from collections.abc import MutableMapping

class PizzaMenu(MutableMapping):
    def __init__(self, menu: dict):
        self._menu = {}
        self._first_letters = {}
        for key, value in menu.items():
            first_letter = key[0].lower()
            if first_letter in self._first_letters:
                self._raise_duplicate_key_error(key)
            self._first_letters[first_letter] = key
            self._menu[key] = value

    def _raise_duplicate_key_error(self, key):
        raise ValueError(
            f"'{key}' is invalid."
            " All pizzas must have unique first letters"
        )

    def __getitem__(self, key):
        if key not in self._menu and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.get(key[0].lower(), key)
        return self._menu[key]

    def __setitem__(self, key, value):
        first_letter = key[0].lower()
        if len(key) == 1:
            key = self._first_letters.get(first_letter, key)
        if key in self._menu:
            self._menu[key] = value
        elif first_letter in self._first_letters:
            self._raise_duplicate_key_error(key)
        else:
            self._first_letters[first_letter] = key
            self._menu[key] = value

    def __delitem__(self, key):
        if key not in self._menu and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.pop(key[0].lower(), key)
        del self._menu[key]

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

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

    def __repr__(self):
        return f"{self.__class__.__name__}({self._menu})"

    def __str__(self):
        return str(self._menu)

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

Diese Version enthält alle in diesem Abschnitt des Tutorials besprochenen Methoden.

Im nächsten Abschnitt dieses Tutorials schreiben Sie eine weitere Version dieser Klasse.

Erben von dict und collections.UserDict

Wenn Sie von Mapping oder MutableMapping erben, müssen Sie alle erforderlichen Methoden definieren. Für Mapping müssen Sie mindestens .__getitem__(), .__iter__() und .__len__() definieren und MutableMapping erfordert außerdem .__setitem__() und .__delitem__(). Bei der Definition des Mappings haben Sie die volle Kontrolle.

In den vorherigen Abschnitten haben Sie aus diesen abstrakten Basisklassen eine Klasse erstellt. Dies ist nützlich, um zu verstehen, was innerhalb einer Zuordnung geschieht.

Wenn Sie jedoch eine benutzerdefinierte Zuordnung erstellen, möchten Sie diese häufig anhand eines Wörterbuchs modellieren. Es gibt weitere Optionen zum Erstellen benutzerdefinierter Zuordnungen. Im folgenden Abschnitt erstellen Sie die benutzerdefinierte Zuordnung für das Pizzamenü neu, indem Sie direkt von dict erben.

Eine Klasse, die von dict erbt

In früheren Python-Versionen war es nicht möglich, integrierte Typen wie dict in Unterklassen zu unterteilen. Dies ist jedoch nicht mehr der Fall. Dennoch gibt es Herausforderungen beim Erben von dict.

Sie können damit beginnen, die von dict zu erbende Klasse neu zu erstellen und die ersten beiden Methoden zu definieren. Alle von Ihnen definierten Methoden ähneln denen im vorherigen Abschnitt, weisen jedoch kleine und wichtige Unterschiede auf. Sie können den Klassennamen unterscheiden, indem Sie die neue Klasse PizzaMenuDict aufrufen:

class PizzaMenuDict(dict):
    def __init__(self, menu: dict):
        _menu = {}
        self._first_letters = {}
        for key, value in menu.items():
            first_letter = key[0].lower()
            if first_letter in self._first_letters:
                self._raise_duplicate_key_error(key)
            self._first_letters[first_letter] = key
            _menu[key] = value
        super().__init__(_menu)

    def _raise_duplicate_key_error(self, key):
        raise ValueError(
            f"'{key}' is invalid."
            " All pizzas must have unique first letters"
        )

Die Klasse erbt jetzt von dict. Der ._raise_duplicate_key_error() ist identisch mit der Version in PizzaMenu, die Sie zuvor geschrieben haben. Die Methode .__init__() weist einige Änderungen auf:

  • Das interne Wörterbuch ist kein Datenattribut self._menu mehr, sondern eine lokale Variable _menu, da es an keiner anderen Stelle in der Klasse mehr benötigt wird.
  • Diese lokale Variable ._menu wird mit super() in der letzten Zeile an den dict-Initialisierer übergeben.

Da es sich bei einem PizzaMenuDict-Objekt um ein Wörterbuch handelt, können Sie mithilfe von self innerhalb der Methoden direkt über das Objekt auf die Daten des Wörterbuchs zugreifen. Alle Operationen auf self verwenden Methoden, die in PizzaMenuDict definiert sind. Wenn in PizzaMenuDict jedoch keine Methoden definiert sind, werden die dict-Methoden verwendet.

Daher ist PizzaMenuDict jetzt ein Wörterbuch, das sicherstellt, dass beim Initialisieren des Objekts keine Elemente vorhanden sind, die mit demselben Buchstaben beginnen. Es verfügt außerdem über ein zusätzliches Datenattribut, ._first_letters. Sie können bestätigen, dass die Initialisierung wie erwartet funktioniert:

>>> from pizza_menu import PizzaMenuDict
>>> menu = PizzaMenuDict({"Margherita": 9.5, "Pepperoni": 10.5})
>>> menu
{'Margherita': 9.5, 'Pepperoni': 10.5}

>>> menu = PizzaMenuDict({"Margherita": 9.5, "Meat Feast": 10.5})
Traceback (most recent call last):
  ...
ValueError: 'Meat Feast' is an invalid name.
    All pizzas must have unique first letters

Sie erhalten eine Fehlermeldung, wenn Sie versuchen, ein PizzaMenuDict-Objekt mit zwei Pizzen zu erstellen, die mit M beginnen. Allerdings sind keine der anderen speziellen Methoden definiert. Daher verfügt diese Klasse noch nicht über alle erforderlichen Funktionen:

>>> menu["m"]
Traceback (most recent call last):
  ...
KeyError: 'm'

Sie können nicht mit einem einzelnen Buchstaben auf einen Wert zugreifen. Sie können jedoch die Methode .__getitem__() implementieren, die der Methode ähnelt, die Sie im vorherigen Abschnitt in PizzaMenu definiert haben, aber nicht mit ihr identisch ist:

class PizzaMenuDict(dict):
    # ...

    def __getitem__(self, key):
        if key not in self and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.get(key[0].lower(), key)
        return super().__getitem__(key)

Es gibt zwei Unterschiede zum .__getitem__() in PizzaMenu im vorherigen Abschnitt, da das Datenattribut ._menu in dieser Version nicht mehr vorhanden ist :

  1. Die if-Anweisung in PizzaMenu.__getitem__() prüft, ob key ein Mitglied von self._menu ist. Die entsprechende bedingte Anweisung in PizzaMenuDict.__getitem__() prüft jedoch direkt auf Mitgliedschaft in self.
  2. Die return-Anweisung in PizzaMenu.__getitem__() gibt self._menu[key] zurück. Allerdings ruft die letzte Zeile in PizzaMenuDict.__getitem__() die spezielle Methode .__getitem__() der Oberklasse unter Verwendung des geänderten key auf und gibt sie zurück. Die Oberklasse ist dict.

Die Methode .__getitem__() in PizzaMenuDict behandelt also die Einzelbuchstaben-Groß- und Kleinschreibung und ruft dann .__getitem__() im dict auf Klasse.

Sie werden das gleiche Muster in .__setitem__() bemerken:

class PizzaMenuDict(dict):
    # ...

    def __setitem__(self, key, value):
        first_letter = key[0].lower()
        if len(key) == 1:
            key = self._first_letters.get(first_letter, key)
        if key in self:
            super().__setitem__(key, value)
        elif first_letter in self._first_letters:
            self._raise_duplicate_key_error(key)
        else:
            self._first_letters[first_letter] = key
            super().__setitem__(key, value)

Wann immer Sie die Daten in der Zuordnung aktualisieren müssen, rufen Sie dict.__setitem__() auf, anstatt die Werte im Datenattribut ._menu festzulegen, wie Sie es in PizzaMenü.

Sie müssen auch .__delitem__() auf ähnliche Weise definieren:

class PizzaMenuDict(dict):
    # ...

    def __delitem__(self, key):
        if key not in self and len(key) > 1:
            raise KeyError(key)
        key = self._first_letters.pop(key[0].lower(), key)
        super().__delitem__(key)

Die letzte Zeile der Methode ruft die Methode .__delitem__() in dict auf.

Beachten Sie, dass Sie keine speziellen Methoden wie .__repr__(), .__str__(), .__iter__() oder .__iter__() definieren müssen .__len__(), wie Sie es tun mussten, wenn Sie von den abstrakten Basisklassen Mapping und MutableMapping erben. Da ein PizzaMenuDict eine Unterklasse von dict ist, können Sie sich auf die Wörterbuchmethoden verlassen, wenn Sie kein anderes Verhalten benötigen. Sie müssen eine neue REPL-Sitzung starten, da Sie Änderungen an der Klassendefinition vorgenommen haben:

>>> from pizza_menu import PizzaMenuDict
>>> menu = PizzaMenuDict({"Margherita": 9.5, "Pepperoni": 10.5})

>>> for pizza in menu:
...     print(pizza)
...
Margherita
Pepperoni

>>> menu
{'Margherita': 9.5, 'Pepperoni': 10.5}

>>> len(menu)
2

Die Iteration verwendet die Methode .__iter__() in dict. Die String-Darstellung und das Ermitteln der Objektlänge funktionieren ebenfalls wie erwartet, da die speziellen Methoden .__repr__() und .__len__() in dict ausreichend sind.

Methoden, die aktualisiert werden müssen

Es scheint, als ob weniger Arbeit nötig wäre, um von dict zu erben. Es gibt jedoch noch andere Methoden, auf die Sie achten müssen. Beispielsweise können Sie .pop() mit PizzaMenuDict erkunden:

>>> menu.pop("m")
Traceback (most recent call last):
  ...
KeyError: 'm'

Da Sie .pop() nicht in PizzaMenuDict definiert haben, verwendet die Klasse stattdessen die Methode dict. Allerdings verwendet dict.pop() dict.__getitem__() und umgeht daher die Methode .__getitem__(), die Sie speziell für PizzaMenuDict. Sie müssen .pop() in PizzaMenuDict überschreiben:

class PizzaMenuDict(dict):
    # ...

    def pop(self, key):
        key = self._first_letters.pop(key[0].lower(), key)
        return super().pop(key)

Sie stellen sicher, dass der key immer der vollständige Name der Pizza ist, bevor Sie die Methode .pop() der Oberklasse aufrufen und zurückgeben. Sie können bestätigen, dass dies in einer neuen REPL-Sitzung funktioniert:

>>> from pizza_menu import PizzaMenuDict
>>> menu = PizzaMenuDict({"Margherita": 9.5, "Pepperoni": 10.5})

>>> menu.pop("m")
9.5

>>> menu
{'Pepperoni': 10.5}

Jetzt können Sie in .pop() ein aus einem Buchstaben bestehendes Argument verwenden.

Sie müssen alle dict-Methoden durchgehen, um zu bestimmen, welche in PizzaMenuDict definiert werden müssen. Sie können versuchen, diesen Kurs als Übung abzuschließen. Sie werden feststellen, dass dieser Prozess diesen Ansatz länger und fehleranfälliger macht. Daher ist in diesem Pizzeria-Beispiel die direkte Vererbung von MutableMapping möglicherweise die bessere Option.

Möglicherweise haben Sie jedoch auch andere Anwendungen, bei denen Sie die Funktionalität eines Wörterbuchs erweitern, ohne seine vorhandenen Eigenschaften zu ändern. In solchen Fällen kann das Erben von dict die ideale Option sein.

Eine weitere Alternative: collections.UserDict

Im Modul collections finden Sie eine weitere Klasse, von der Sie erben können, um ein wörterbuchähnliches Objekt zu erstellen. Sie können von UserDict anstelle von MutableMapping oder dict erben. UserDict wurde in Python eingefügt, als es unmöglich war, direkt von dict zu erben. Allerdings ist UserDict nicht völlig veraltet, da die Unterklassenbildung von dict möglich ist.

UserDict erstellt einen Wrapper um ein Wörterbuch, anstatt dict in eine Unterklasse zu unterteilen. Ein UserDict-Objekt enthält ein Attribut namens .data, bei dem es sich um ein Wörterbuch handelt, das die Daten enthält. Dieses Attribut ähnelt dem Attribut ._menu, das Sie zu PizzaMenu hinzugefügt haben, als Sie von Mapping und MutableMapping geerbt haben.

Allerdings ist UserDict eine konkrete Klasse und keine abstrakte Basisklasse. Sie müssen also nicht die erforderlichen speziellen Methoden definieren, es sei denn, Sie benötigen ein anderes Verhalten.

Sie haben bereits zwei Versionen der Klasse geschrieben, um ein Menü für die Pizzeria zu erstellen, daher werden Sie in diesem Tutorial keine dritte Version schreiben. Auf diese Weise kann man nicht viel mehr über Mappings lernen. Wenn Sie jedoch mehr über die Gemeinsamkeiten und Unterschiede zwischen dem Erben von dict oder UserDict erfahren möchten, können Sie Benutzerdefinierte Python-Wörterbücher lesen: Erben von dict vs. UserDict.

Abschluss

Das Python-Wörterbuch ist die am häufigsten verwendete Zuordnung und eignet sich in den meisten Fällen, in denen Sie eine Zuordnung benötigen. Es gibt jedoch andere Zuordnungen in der Standardbibliothek und in Bibliotheken von Drittanbietern. Möglicherweise haben Sie auch Anwendungen, bei denen Sie eine benutzerdefinierte Zuordnung erstellen müssen.

In diesem Tutorial haben Sie Folgendes gelernt:

  • Grundlegende Merkmale einer Zuordnung
  • Vorgänge, die den meisten Zuordnungen gemeinsam sind
  • Abstrakte Basisklassen Mapping und MutableMapping
  • Benutzerdefinierte veränderliche und unveränderliche Zuordnungen und wie man sie erstellt

Wenn Sie die gemeinsamen Merkmale aller Mappings verstehen und wissen, was hinter den Kulissen passiert, wenn Sie Mapping-Objekte erstellen und verwenden, können Sie Wörterbücher und andere Mappings in Ihren Python-Programmen effektiver nutzen.