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
undMutableMapping
- 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.
Hinweis: Es gibt Aliase für diese und andere abstrakte Basisklassen im typing
-Modul. Diese sind jedoch seit Python 3.9 veraltet und sollten nicht verwendet werden.
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.
Hinweis: Sie müssen jedes Mal, wenn Sie Änderungen an der Klassendefinition in pizza_menu.py
vornehmen, eine neue Sitzung starten, wenn Sie Pythons Standard-REPL verwenden. Sie können die Importanweisung nicht erneut in derselben REPL-Sitzung schreiben, da dadurch die aktualisierte Klasse nicht importiert wird. Es ist auch möglich, importlib
aus der Standardbibliothek zu verwenden, um ein Modul neu zu laden, aber es ist einfacher, eine neue REPL zu starten.
Alternativ können Sie F6 drücken, um die importierten Module neu zu laden, wenn Sie eine andere REPL wie bpython verwenden.
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.
Hinweis: Einige Programmierer halten es für pythonischer, den Klassennamen mithilfe eines prägnanteren type(self).__name__
-Ausdrucks abzurufen, anstatt direkt auf .__class__
zuzugreifen. Attribut.
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:
- Keine Argumente: Sie erstellen ein leeres Wörterbuch, wenn Sie
dict()
ohne Argumente aufrufen. - Mapping: Sie können jedes Mapping als Argument in
dict()
verwenden, wodurch aus dem Mapping ein neues Wörterbuch erstellt wird. - 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. **kwargs
: Sie können eine beliebige Anzahl von Schlüsselwortargumenten verwenden, wenn Siedict()
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. DieserSchlüssel
wird im Rest dieser Methode verwendet. Wenn keine passende Pizza vorhanden ist, bleibt derkey
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 einenValueError
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 mitsuper()
in der letzten Zeile an dendict
-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 :
- Die
if
-Anweisung inPizzaMenu.__getitem__()
prüft, obkey
ein Mitglied vonself._menu
ist. Die entsprechende bedingte Anweisung inPizzaMenuDict.__getitem__()
prüft jedoch direkt auf Mitgliedschaft inself
. - Die
return
-Anweisung inPizzaMenu.__getitem__()
gibtself._menu[key]
zurück. Allerdings ruft die letzte Zeile inPizzaMenuDict.__getitem__()
die spezielle Methode.__getitem__()
der Oberklasse unter Verwendung des geändertenkey
auf und gibt sie zurück. Die Oberklasse istdict
.
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
undMutableMapping
- 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.