Tutorial

Verwenden des collections-Moduls in Python 3

Python

Der Autor hat den COVID-19 Relief Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

Einführung

Python 3 verfügt über eine Reihe von integrierten Datenstrukturen, einschließlich Tupel, Wörterbücher und Listen. Datenstrukturen bieten uns eine Möglichkeit, Daten zu organisieren und zu speichern. Das collections-Modul hilft uns, Datenstrukturen effizient zu füllen und zu manipulieren.

In diesem Tutorial gehen wir drei Klassen im collections-Modul durch, um Ihnen die Arbeit mit Tupeln, Wörterbüchern und Listen zu erleichtern. Wir verwenden namedtuples, um Tupel mit benannten Feldern zu erstellen, defaultdict, um Informationen in Wörterbüchern übersichtlich zu gruppieren, und deque, um Elemente effizient zu beiden Seiten eines listenartigen Objekts hinzuzufügen.

In diesem Turorial arbeiten wir in erster Linie mit einem Bestand von Fischen, den wir modifizieren müssen, wenn Fische zu einem fiktiven Aquarium hinzugefügt oder aus diesem entfernt werden.

Voraussetzungen

Um dieses Tutorial optimal zu nutzen, wird empfohlen, sich mit den Tupel-, Wörterbuch- und Listendatentypen vertraut zu machen; sowohl mit deren Syntax als auch mit der Art und Weise, Daten von ihnen abzurufen. Sie können für die notwendigen Hintergrundinformationen diese Tutorials durchsehen:

Hinzufügen von benannten Feldern zu Tupeln

Python-Tupeln sind eine unwandelbare oder unveränderliche, geordnete Sequenz von Elementen. Tupel werden häufig für die Darstellung von Spaltendaten verwendet, beispielsweise für Zeilen aus einer CSV-Datei oder Reihen aus einer SQL-Datenbank. Ein Aquarium könnte seinen Bestand an Fischen als eine Reihe von Tupeln erfassen.

Ein individueller Fischtupel:

("Sammy", "shark", "tank-a")

Dieses Tupel besteht aus drei Zeichenfolgenelementen.

Das Tupel ist zwar in gewisser Weise nützlich, aber es gibt nicht klar an, wofür jedes seiner Felder steht. In Wirklichkeit ist Element 0 ein Name, Element 1 eine Spezies und Element 2 das Haltebecken.

Erläuterung der Fischtupelfelder:

Name Spezies Becken
Sammy shark tank-a

Diese Tabelle verdeutlicht, dass jedes der drei Elemente des Tupels eine klare Bedeutung hat.

Mit namedtuple aus dem collections-Modul können Sie jedem Element eines Tupels explizite Namen hinzufügen, um diese Bedeutungen in Ihrem Python-Programm klarzustellen.

Wir verwenden namedtuple zum Erstellen einer Klasse, die jedes Element des Fischtupels klar benennt:

from collections import namedtuple

Fish = namedtuple("Fish", ["name", "species", "tank"])

from collections import namedtuple gibt Ihrem Python-Programm Zugriff auf die Factoryfunktion namedtuple. Der Funktionsaufruf namedtuple() gibt eine Klasse zurück, die an den Namen Fish gebunden ist. Die Funktion namedtuple() hat zwei Argumente: den gewünschten Namen unserer neuen Klasse "Fish" und eine Liste mit benannten Elementen ["name", "species", "tank"].

Wir können die Fish-Klasse verwenden, um das Fischtupel von vorhin zu repräsentieren:

sammy = Fish("Sammy", "shark", "tank-a")

print(sammy)

Wenn wir diesen Code ausführen, sehen wir die folgende Ausgabe:

Output
Fish(name='Sammy', species='shark', tank='tank-a')

sammy wird mit der Fish-Klasse instanziiert. sammy ist ein Tupel mit drei klar benannten Elementen.

Auf die Felder von sammy kann über ihren Namen oder mit einem traditionellen Tupelindex zugegriffen werden:

print(sammy.species)
print(sammy[1])

Wenn wir diese beiden print-Aufrufe ausführen, sehen wir die folgende Ausgabe:

Output
shark shark

Der Zugriff auf .species gibt denselben Wert zurück wie der Zugriff auf das zweite Element von sammy mit [1].

Die Verwendung von namedtuple aus dem collections-Modul macht Ihr Programm lesbarer, wobei die wichtigen Eigenschaften eines Tupels (dass sie unveränderlich und geordnet sind) bewahrt bleiben.

Darüber hinaus fügt die Factoryfunktion namedtuple mehrere zusätzliche Methoden zu Instanzen von Fish hinzu.

Verwenden Sie ._asdict(), um eine Instanz in ein Wörterbuch zu konvertieren:

print(sammy._asdict())

Wenn wir print ausführen, sehen Sie eine Ausgabe wie die folgende:

Output
{'name': 'Sammy', 'species': 'shark', 'tank': 'tank-a'}

Das Aufrufen von .asdict() auf sammy gibt ein Wörterbuch zurück, das jedem der drei Feldnamen ihre entsprechenden Werte zuordnet.

Python-Versionen, die älter als 3.8 sind, geben diese Zeile möglicherweise etwas anders aus. Sie könnten beispielsweise ein OrderedDict anstelle des hier gezeigten, einfachen Wörterbuchs sehen.

Anmerkung: In Python werden Methoden mit vorangehenden Unterstrichen gewöhnlich als „privat“ eingestuft. Weitere Methoden, die von namedtuple bereitgestellt werden (wie _asdict(), ._make(), ._replace(), usw.), sind jedoch öffentlich.

Sammeln von Daten in einem Wörterbuch

Es ist oft nützlich, Daten in Python-Wörterbüchern zu sammeln. defaultdict aus dem collections-Modul kann uns helfen, Informationen schnell und übersichtlich in Wörterbüchern zusammenzustellen.

defaultdict gibt nie einen KeyError aus. Wenn kein Schlüssel vorhanden ist, fügt defaultdict stattdessen einfach einen Platzhalterwert ein und gibt ihn zurück:

from collections import defaultdict

my_defaultdict = defaultdict(list)

print(my_defaultdict["missing"])

Wenn wir diesen Code ausführen, sehen wir eine Ausgabe wie die folgende:

Output
[]

defaultdict fügt einen Platzhalterwert ein und gibt ihn zurück, anstatt einen KeyError auszugeben. In diesem Fall haben wir den Platzhalterwert als Liste angegeben.

Reguläre Wörterbücher hingegen geben bei fehlenden Schlüsseln einen KeyError aus:

my_regular_dict = {}

my_regular_dict["missing"]

Wenn wir diesen Code ausführen, sehen wir eine Ausgabe wie die folgende:

Output
Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'missing'

Das reguläre Wörterbuch my_regular_dict gibt einen KeyError aus, wenn wir versuchen, auf einen Schlüssel zuzugreifen, der nicht vorhanden ist.

defaultdict verhält sich anders als ein reguläres Wörterbuch. Statt einen KeyError auf einen fehlenden Schlüssel auszugeben, ruft defaultdict den Platzhalterwert ohne Argumente auf, um ein neues Objekt zu erstellen. In diesem Fall list(), um eine leere Liste zu erstellen.

Um mit unserem fiktiven Aquarium-Beispiel fortzufahren, nehmen wir an, wir hätten eine Liste von Fischtupeln, die den Bestand eines Aquariums repräsentieren:

fish_inventory = [
    ("Sammy", "shark", "tank-a"),
    ("Jamie", "cuttlefish", "tank-b"),
    ("Mary", "squid", "tank-a"),
]

Es gibt drei Fische in dem Aquarium – Name, Spezies und Haltebecken sind in diesen drei Tupeln notiert.

Unser Ziel ist es, unseren Bestand nach Becken zu organisieren. Wir wollen die Liste der in jedem Becken vorhandenen Fische kennen. Anders ausgedrückt: Wir wollen ein Wörterbuch, das "tank-a" ["Jamie", "Mary"] und "tank-b" ["Jamie"] zuordnet.

Wir können defaultdict verwenden, um den Fisch nach Becken zu gruppieren:

from collections import defaultdict

fish_inventory = [
    ("Sammy", "shark", "tank-a"),
    ("Jamie", "cuttlefish", "tank-b"),
    ("Mary", "squid", "tank-a"),
]
fish_names_by_tank = defaultdict(list)
for name, species, tank in fish_inventory:
    fish_names_by_tank[tank].append(name)

print(fish_names_by_tank)

Nach Ausführung dieses Codes sehen wir die folgende Ausgabe:

Output
defaultdict(<class 'list'>, {'tank-a': ['Sammy', 'Mary'], 'tank-b': ['Jamie']})

fish_names_by_tank wird als ein defaultdict deklariert, das standardmäßig list() einfügt, anstatt einen KeyError auszugeben. Da dies garantiert, dass jeder Schlüssel in fish_names_by_tank auf eine list verweist, können wir frei .append() aufrufen, um Namen zu der Liste jedes Beckens hinzuzufügen.

defaultdict hilft Ihnen hier, weil es die Wahrscheinlichkeit unerwarteter KeyErrors reduziert. Die Reduzierung der unerwarteten KeyErrors bedeutet, dass Ihr Programm klarer und mit weniger Zeilen geschrieben werden kann. Konkreter gesagt: Mit dem defaultdict-Idiom können Sie manuelles Instanziieren einer leeren Liste für jedes Becken vermeiden.

Ohne defaultdict hätte der for-Schleifenkörper möglicherweise eher wie folgt ausgesehen:

More Verbose Example Without defaultdict
...

fish_names_by_tank = {}
for name, species, tank in fish_inventory:
    if tank not in fish_names_by_tank:
      fish_names_by_tank[tank] = []
    fish_names_by_tank[tank].append(name)

Die Verwendung eines regulären Wörterbuchs (statt eines defaultdict) bedeutet, dass der for-Schleifenkörper immer das Vorhandensein des gegebenen tank in fish_names_by_tank überprüfen muss. Erst nachdem wir überprüft haben, dass tank bereits in fish_names_by_tank vorhanden ist, oder gerade mit einem [] initialisiert wurde, können wir den Fischnamen ergänzen.

defaultdict kann dazu beitragen, beim Füllen der Wörterbücher den Standardcode zu reduzieren, da es nie einen KeyError ausgibt.

Verwenden von deque zum effizienten Hinzufügen von Elementen zu beiden Seiten einer Sammlung

Python-Listen sind eine wandelbare oder veränderliche, geordnete Sequenz von Elementen. Python kann Listen in konstanter Zeit ergänzen (die Länge der Liste hat keine Auswirkungen auf die Zeit, die zum Ergänzen benötigt wird), aber das Einfügen am Anfang einer Liste kann langsamer sein – die Zeitdauer erhöht sich beim Anwachsen der Liste.

Im Sinne der Big-O-Notation ist das Ergänzen einer Liste ein O(1)-Vorgang mit konstanter Zeit. Im Gegensatz ist das Einfügen am Anfang einer Liste langsamer mit einer O(n)​​​-Leistung.

Anmerkung: Softwareingenieure messen die Leistung von Vorgängen oft mit der sogenannten „Big O“-Notation. Wenn die Größe einer Eingabe keine Auswirkungen auf die Zeit hat, die zum Ausführen eines Vorgangs benötigt wird, spricht man von einem Ablauf in konstanter Zeit oder O(1) („Big O von 1“). Wie Sie oben gelernt haben, kann Python Listen mit konstanter Zeitleistung, auch als O(1) bekannt, ergänzen.

Manchmal beeinflusst die Größe einer Eingabe direkt die Zeit, die zum Ausführen eines Vorgangs benötigt wird. Das Einfügen am Anfang einer Python-Liste zum Beispiel läuft umso langsamer ab, je mehr Elemente in der Liste vorhanden sind. Die Big-O-Notation verwendet den Buchstaben n, um die Größe der Eingabe darzustellen. Das bedeutet, dass das Hinzufügen von Elementen am Anfang einer Python-Liste in „linearer Zeit“ oder O(n) („Big O von n“) abläuft.

Im Allgemeinen sind O(1)-Vorgänge schneller als O(n)-Vorgänge.

Wir können am Anfang einer Python-Liste einfügen:

favorite_fish_list = ["Sammy", "Jamie", "Mary"]

# O(n) performance
favorite_fish_list.insert(0, "Alice")

print(favorite_fish_list)

Wenn wir Folgendes ausführen, sehen wir eine Ausgabe wie die folgende:

Output
['Alice', 'Sammy', 'Jamie', 'Mary']

Die .insert(index, object)-Methode in der Liste ermöglicht uns, „Alice“ am Anfang von favorite_fish_list einzufügen. Jedoch hat das Einfügen am Anfang einer Liste eine O(n)-Leistung. Wenn die Länge der favorite_fish_list wächst, wird die Zeit, um einen Fisch am Anfang der Liste einzufügen, proportional anwachsen und immer länger dauern.

deque (ausgesprochen „Deck“) aus dem collections-Modul ist ein listenähnliches Objekt, das es uns ermöglicht, Elemente am Anfang oder Ende einer Sequenz mit konstanter Zeit (O(1))-Leistung einzufügen.

Geben Sie ein Element am Anfang eines deque ein:

from collections import deque

favorite_fish_deque = deque(["Sammy", "Jamie", "Mary"])

# O(1) performance
favorite_fish_deque.appendleft("Alice")

print(favorite_fish_deque)

Nach Ausführung dieses Codes sehen wir die folgende Ausgabe:

Output
deque(['Alice', 'Sammy', 'Jamie', 'Mary'])

Wir können ein deque anhand einer bereits vorhandenen Sammlung von Elementen instanziieren, in diesem Fall einer Liste mit drei bevorzugten Fischnamen. Das Aufrufen der appendleft-Methode von favorite_fish_deque ermöglicht uns, ein Element am Anfang unserer Sammlung mit O(1)-Leistung einzufügen. O(1)-Leistung bedeutet, dass die Zeit, die zum Hinzufügen eines Elements am Anfang von favorite_fish_deque benötigt wird, nicht zunimmt, selbst wenn favorite_fish_deque Tausende oder Millionen von Elementen enthält.

Anmerkung: Obwohl deque Einträge am Anfang einer Sequenz effizienter als eine Liste hinzufügt, führt deque nicht alle seine Vorgänge effizienter als eine Liste aus. Beispielsweise hat das Zugreifen auf ein zufälliges Element in einem deque eine O(n)-Leistung, das Zugreifen auf ein zufälliges Element in einer Liste jedoch eine O(1)-Leistung Verwenden Sie deque, wenn es wichtig ist, Elemente schnell zu beiden Seiten Ihrer Sammlung hinzuzufügen oder zu entfernen. Ein vollständiger Vergleich der Zeitleistung ist auf Pythons Wiki verfügbar.

Zusammenfassung

Das collections-Modul ist ein leistungsfähiger Teil der Python-Standardbibliothek, mit dem Sie übersichtlich und effizient Daten bearbeiten können. Dieses Tutorial behandelte drei der Klassen, die vom collections-Modul bereitgestellt werden, einschließlich namedtuple, defaultdict und deque.

Nun können Sie die Dokumentation des collection-Moduls nutzen, um mehr über andere verfügbare Klassen und Dienstprogramme zu erfahren. Um im Allgemeinen mehr über Python zu erfahren, können Sie unsere Tutorialreihe Codieren in Python 3 lesen.

Creative Commons License