Pythonischer und sauberer Code mit namedtuple schreiben
Pythonischen und sauberen Code mit namedtuple schreiben[Bearbeiten | Quelltext bearbeiten]
by Leodanis Pozo Ramos Jun 16, 2025
Original: https://realpython.com/python-namedtuple/
Übersetzt mit DeepL.com
Inhaltsübersicht
Kennenlernen von namedtuple in Python Erstellen von Tupel-ähnlichen Klassen mit der Funktion namedtuple() Erforderliche Argumente bereitstellen Optionale Argumente verwenden Zusätzliche Funktionen von namedtuple-Klassen erforschen Erstellen von benannten Tupeln aus Iterablen mit ._make() Benannte Tupel in Dictionaries umwandeln mit ._asdict() Ersetzen von benannten Tupelfeldern mit ._replace() Named Tuple Attribute erforschen: ._fields und ._field_defaults Schreiben von Pythonic Code mit namedtuple Feldnamen anstelle von Indizes verwenden Mehrere Werte von Funktionen zurückgeben Reduzieren der Anzahl von Argumenten in Funktionen Tabellarische Daten aus Dateien und Datenbanken lesen Vergleich von namedtuple mit anderen Datenstrukturen namedtuple vs. Wörterbücher namedtuple vs. Datenklassen namedtuple vs typing.NamedTuple namedtuple vs tuple Unterklassifizierung von namedtuple-Klassen Schlussfolgerung Häufig gestellte Fragen
Pythons namedtuple im Modul collections erlaubt es, unveränderliche Sequenzen mit benannten Feldern zu erstellen, was eine lesbarere und pythonischere Art der Handhabung von Tupeln darstellt. Sie verwenden namedtuple, um auf Werte mit beschreibenden Feldnamen und Punktnotation zuzugreifen, was die Übersichtlichkeit und Wartbarkeit des Codes verbessert.
Am Ende dieses Tutorials werden Sie das verstehen:[Bearbeiten | Quelltext bearbeiten]
- Pythons namedtuple ist eine Fabrikfunktion, die Tupel-Unterklassen mit benannten Feldern erzeugt.
- Der Hauptunterschied zwischen tuple und namedtuple ist, dass namedtuple den Zugriff auf Attribute über benannte Felder erlaubt, was die Lesbarkeit verbessert.
- Der Sinn der Verwendung von namedtuple ist es, die Klarheit des Codes zu verbessern, indem der Zugriff auf Elemente über beschreibende Namen anstelle von ganzzahligen Indizes ermöglicht wird.
- Einige Alternativen zu namedTuple umfassen Wörterbücher, Datenklassen und typing.NamedTuple.
Tauchen Sie tiefer in die Erstellung von namedTuple-Klassen ein, erkunden Sie ihre leistungsstarken Funktionen und schreiben Sie Python-Code, der leichter zu lesen und zu warten ist.
Holen Sie sich Ihren Code: Klicken Sie hier, um den kostenlosen Beispielcode herunterzuladen, der Ihnen zeigt, wie Sie namedtuple verwenden können, um Python und sauberen Code zu schreiben.
Kennenlernen von namedtuple in Python[Bearbeiten | Quelltext bearbeiten]
Pythons namedtuple() ist eine Fabrikfunktion, die im Modul Sammlungen verfügbar ist. Sie erlaubt es, eine Tupel Unterklasse mit benannten Feldern zu erstellen. Mit diesen benannten Feldern können Sie auf die Werte in einem bestimmten benannten Tupel zugreifen, indem Sie die Punktnotation und Feldnamen verwenden - zum Beispiel mein_tupel.feldname.
Pythons namedtuple wurde entwickelt, um die Lesbarkeit des Codes zu verbessern, indem es eine Möglichkeit bietet, auf Werte mit Hilfe von beschreibenden Feldnamen zuzugreifen, anstatt mit Integer-Indizes, die oft keinen Kontext zu den Werten liefern. Diese Funktion macht den Code auch sauberer und besser wartbar.
Im Gegensatz dazu kann der Zugriff auf Werte nach Index in einem regulären Tupel frustrierend, schwer zu lesen und fehleranfällig sein. Dies gilt insbesondere, wenn das Tupel viele Felder enthält und weit entfernt von dem Ort konstruiert wird, an dem Sie es verwenden.
Hinweis: In diesem Tutorium werden Sie verschiedene Begriffe finden, die sich auf Pythons Namendupel, seine Factory-Funktion und seine Instanzen beziehen.
Um Verwirrung zu vermeiden, finden Sie hier eine Zusammenfassung, wie die einzelnen Begriffe im Laufe des Tutorials verwendet werden:
Begriff Bedeutung namedtuple() Die Fabrikfunktion namedtuple, namedtuple class Die Tupel-Unterklasse, die von namedtuple() zurückgegeben wird namedtuple-Instanz, benanntes Tupel Eine Instanz einer bestimmten namededtuple Klasse Sie werden diese Begriffe mit der entsprechenden Bedeutung im gesamten Lehrgang verwenden.
Neben der Bereitstellung von benannten Feldern bieten benannte Tupel in Python die folgenden Eigenschaften:
Sie sind unveränderliche Datenstrukturen Können einen Hash-Wert haben und als Wörterbuchschlüssel funktionieren Können in Sets gespeichert werden, wenn sie einen Hash-Wert haben Erzeugen eines einfachen Docstring unter Verwendung des Typs und der Feldnamen Bereitstellung einer hilfreichen String-Darstellung, die den Tupelinhalt in einem Name=Wert-Format anzeigt Unterstützung von Indexierung und Slicing Bieten zusätzliche Methoden und Attribute, wie ._make(), _asdict() und ._fields sind rückwärtskompatibel mit regulären Tupeln haben einen ähnlichen Speicherverbrauch wie reguläre Tupel Sie können namedtuple Instanzen überall dort verwenden, wo Sie ein tupelähnliches Objekt benötigen. Sie bieten den zusätzlichen Vorteil, dass Sie mit Feldnamen und Punktnotation auf Werte zugreifen können, was Ihren Code lesbarer und Pythonic macht.
Mit dieser kurzen Einführung in namedtuple und seine allgemeinen Eigenschaften sind Sie bereit zu erforschen, wie Sie sie erstellen und effektiv in Ihrem eigenen Code verwenden können.
Anzeigen entfernen Tupel-ähnliche Klassen mit der namedtuple() Funktion erstellen Man verwendet ein namedtuple(), um eine unveränderliche, tupelähnliche Sequenz mit benannten Feldern zu erstellen. Ein beliebtes Beispiel, das man oft in Ressourcen über namededtuple findet, ist die Definition einer Klasse zur Darstellung eines mathematischen Punktes.
Je nach Problemstellung werden Sie wahrscheinlich eine unveränderliche Datenstruktur zur Darstellung Ihrer Punkte verwenden wollen. Hier sehen Sie, wie Sie einen zweidimensionalen Punkt mit einem regulären Tupel erstellen können:
>>> # Create a 2D point as a regular tuple >>> point = (2, 4) >>> point (2, 4)
>>> # Access coordinate x >>> point[0] 2 >>> # Access coordinate y >>> point[1] 4
>>> # Try to update a coordinate value >>> point[0] = 100 Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment In diesem Beispiel erstellen Sie einen unveränderlichen, zweidimensionalen Punkt mit einem regulären Tupel. Dieser Code funktioniert. Sie haben einen Punkt mit zwei Koordinaten, auf die Sie per Index zugreifen können. Der Punkt ist unveränderlich, also kann man die Koordinaten nicht ändern. Halten Sie diesen Code dennoch für lesbar? Können Sie im Voraus sagen, was die Indizes 0 und 1 bedeuten?
Um die Übersichtlichkeit zu verbessern, können Sie ein namededtuple wie im folgenden Code verwenden. Beachten Sie, dass Sie die Funktion zuerst aus dem Modul Sammlungen importieren müssen:
>>> from collections import namedtuple
>>> # Create a namedtuple type, Point >>> Point = namedtuple("Point", "x y")
>>> point = Point(2, 4) >>> point Point(x=2, y=4)
>>> # Access the coordinates by field name >>> point.x 2 >>> point.y 4
>>> # Access the coordinates by index >>> point[0] 2 >>> point[1] 4
>>> point.x = 100 Traceback (most recent call last):
...
AttributeError: can't set attribute
>>> issubclass(Point, tuple) True Jetzt haben Sie eine Punkt-Klasse mit zwei entsprechend benannten Feldern, .x und .y. Ihr Punkt bietet standardmäßig eine beschreibende String-Darstellung: Punkt(x=2, y=4).
Sie können auf die Koordinaten mit der Punktnotation und den Feldnamen zugreifen, was bequem, lesbar und eindeutig ist. Sie können auch Indizes verwenden, um auf die Werte der einzelnen Koordinaten zuzugreifen, wenn Sie dies wünschen.
Hinweis: Wie bei regulären Tupeln sind benannte Tupel unveränderlich. Die Werte, die sie speichern, müssen jedoch nicht unbedingt unveränderlich sein.
Es ist durchaus möglich, ein Tupel oder ein benanntes Tupel zu erstellen, das veränderliche Werte enthält:
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name children") >>> john = Person("John Doe", ["Timmy", "Jimmy"]) >>> john Person(name='John Doe', children=['Timmy', 'Jimmy']) >>> id(john.children) 139695902374144
>>> john.children.append("Tina") >>> john Person(name='John Doe', children=['Timmy', 'Jimmy', 'Tina']) >>> id(john.children) 139695902374144
>>> hash(john) Traceback (most recent call last):
...
TypeError: unhashable type: 'list' Sie können benannte Tupel erstellen, die veränderbare Objekte enthalten. Anschließend können Sie die veränderbaren Objekte in dem zugrunde liegenden Tupel ändern. Das bedeutet jedoch nicht, dass Sie das Tupel selbst verändern. Das Tupel wird weiterhin dasselbe Objekt sein.
Schließlich sind Tupel oder benannte Tupel mit veränderlichen Werten nicht hashable, wie Sie im obigen Beispiel gesehen haben.
Da die namededtuple-Klassen Unterklassen von tuple sind, sind sie auch unveränderlich. Wenn man also versucht, den Wert einer Koordinate zu ändern, dann erhält man einen AttributeError.
Erforderliche Argumente bereitstellen Wie Sie bereits gelernt haben, ist namedtuple() eine Fabrikfunktion und keine Datenstruktur. Um eine neue namededtuple Klasse zu erstellen, müssen Sie der Funktion zwei Positionsargumente übergeben:
typename liefert den Klassennamen für die namedtuple Klasse, die von namedtuple() zurückgegeben wird. Diesem Argument muss ein String mit einem gültigen Python Identifier übergeben werden. Feldnamen stellt die Feldnamen bereit, die Sie für den Zugriff auf die Werte im Tupel verwenden werden. Sie können die Feldnamen mit einer der folgenden Optionen angeben: Eine Aufzählung von Strings, wie ["field1", "field2", ..., "fieldN"] Eine Zeichenkette, bei der die einzelnen Feldnamen durch Leerzeichen getrennt sind, z.B. "field1 field2 ... fieldN" Eine Zeichenkette, bei der die einzelnen Feldnamen durch Kommata getrennt sind, z.B. "field1, field2, ..., fieldN" Das Argument Typenname ist erforderlich, weil es den Namen der neu zu erstellenden Klasse definiert. Es ist vergleichbar mit der manuellen Zuweisung eines Namens zu einer Klassendefinition, wie der folgende Vergleich zeigt:
>>> from collections import namedtuple
>>> Point1 = namedtuple("Point", "x y") >>> Point1 <class '__main__.Point'>
>>> class Point: ... def __init__(self, x, y): ... self.x = x ... self.y = y ...
>>> Point2 = Point >>> Point2 <class '__main__.Point'> Im ersten Beispiel rufen Sie namedtuple() auf, um dynamisch eine neue Klasse mit dem Namen Punkt zu erzeugen, und weisen diese Klasse dann der Variablen Punkt1 zu. Im zweiten Beispiel definieren Sie eine Standardklasse, ebenfalls mit dem Namen Punkt, mit einer Klasse-Anweisung und weisen die resultierende Klasse der Variablen Punkt2 zu.
In beiden Fällen fungieren die Variablennamen Punkt1 und Punkt2 als Referenzen (oder Aliase) auf eine Klasse namens Punkt. Die Art und Weise, wie die Klasse erzeugt wird - dynamisch über namedtuple() oder manuell mit einer class-Anweisung - ist jedoch unterschiedlich. Der Parameter typename stellt sicher, dass die dynamisch erzeugte Klasse einen sinnvollen und gültigen Namen hat.
Um zu veranschaulichen, wie man Feldnamen bereitstellt, gibt es verschiedene Möglichkeiten, Punkte zu erstellen:
>>> # A list of strings for the field names >>> Point = namedtuple("Point", ["x", "y"]) >>> Point <class '__main__.Point'> >>> Point(2, 4) Point(x=2, y=4)
>>> # A string with comma-separated field names >>> Point = namedtuple("Point", "x, y") >>> Point <class '__main__.Point'> >>> Point(4, 8) Point(x=4, y=8)
>>> # A generator expression for the field names >>> Point = namedtuple("Point", (field for field in "xy")) >>> Point <class '__main__.Point'> >>> Point(8, 16) Point(x=8, y=16) In diesen Beispielen erstellen Sie zunächst einen Punkt mit einer Liste von Feldnamen. Dann wird eine Zeichenkette mit kommagetrennten Feldnamen verwendet. Schließlich verwenden Sie einen Generator-Ausdruck. Diese letzte Option mag in diesem Beispiel wie ein Overkill aussehen. Sie soll jedoch die Flexibilität der Funktion namedtuple() veranschaulichen.
Hinweis: Wenn Sie eine Iterable verwenden, um die Feldnamen bereitzustellen, sollten Sie eine sequenzähnliche Iterable verwenden, da die Reihenfolge der Felder wichtig ist, um zuverlässige Ergebnisse zu erzielen.
Die Verwendung eines Sets zum Beispiel würde zwar funktionieren, könnte aber unerwartete Ergebnisse liefern:
>>> Point = namedtuple("Point", {"x", "y"}) >>> Point(2, 4) Point(y=2, x=4) Wenn Sie eine ungeordnete Iterable verwenden, um die Felder für ein namededtuple bereitzustellen, können Sie unerwartete Ergebnisse erhalten. Im obigen Beispiel werden die Koordinatennamen vertauscht, was für Ihren Anwendungsfall möglicherweise nicht geeignet ist.
Sie können jeden gültigen Python-Bezeichner für die Feldnamen verwenden, mit zwei wichtigen Ausnahmen:
Namen, die mit einem Unterstrich (_) beginnen Python Schlüsselwörter Wenn Sie Feldnamen angeben, die eine dieser Regeln verletzen, erhalten Sie einen ValueError:
>>> Point = namedtuple("Point", ["x", "_y"]) Traceback (most recent call last):
...
ValueError: Field names cannot start with an underscore: '_y' In diesem Beispiel beginnt der zweite Feldname mit einem Unterstrich, so dass Sie einen ValueError erhalten, der Ihnen mitteilt, dass Feldnamen nicht mit diesem Zeichen beginnen können. Dieses Verhalten vermeidet Namenskonflikte mit den Methoden und Attributen von namedtuple, die mit einem führenden Unterstrich beginnen.
Schließlich kann man auch Instanzen eines benannten Tupels mit Schlüsselwortargumenten oder einem Wörterbuch erzeugen, wie im folgenden Beispiel:
>>> Point = namedtuple("Point", "x y")
>>> Point(x=2, y=4) Point(x=2, y=4)
>>> Point(**{"x": 4, "y": 8}) Point(x=4, y=8) Im ersten Beispiel verwenden Sie Schlüsselwortargumente, um ein Punkt-Objekt zu erstellen. Im zweiten Beispiel verwenden Sie ein Wörterbuch, dessen Schlüssel mit den Feldern von Punkt übereinstimmen. In diesem Fall müssen Sie ein Entpacken des Wörterbuchs durchführen, damit jeder Wert dem richtigen Argument zugeordnet wird.
Anzeigen entfernen Optionale Argumente verwenden Neben den beiden erforderlichen Positionsargumenten kann die Namendupel()-Fabrikfunktion auch die folgenden optionalen Nur-Schlüsselwort-Argumente annehmen:
rename defaults module Wenn Sie rename auf True setzen, werden alle ungültigen Feldnamen automatisch durch Positionsnamen wie _0 oder _1 ersetzt.
Angenommen, Ihr Unternehmen hat eine alte Python-Datenbankanwendung, die Daten über Passagiere verwaltet, die mit dem Unternehmen reisen:
database.py def get_column_names(table):
if table == "passenger": return ("id", "first_name", "last_name", "class") raise ValueError(f"unknown table {table}")
def get_passenger_by_id(passenger_id):
if passenger_id == 1234: return (1234, "John", "Doe", "Business") raise ValueError(f"no record with id={passenger_id}")
Diese Datei bietet zwei einfache Funktionen für die Interaktion mit einer fiktiven Passagierdatenbank. Eine Funktion gibt die Spaltennamen für die Passagier-Tabelle zurück, während die andere einen hartkodierten Passagierdatensatz auf der Grundlage seiner ID abruft. Dieser Code ist ein Stub und dient nur zur Demonstration, da er nicht mit einer echten Datenbank oder einem Produktionssystem verbunden ist.
Sie werden gebeten, das System zu aktualisieren. Du erkennst, dass du mit get_column_names() eine namededtuple Klasse erstellen kannst, die dir hilft, die Daten der Passagiere zu speichern.
Am Ende erhalten Sie den folgenden Code:
passenger.py from collections import namedtuple
from database import get_column_names
Passenger = namedtuple("Passenger", get_column_names("passenger")) Wenn Sie diesen Code jedoch ausführen, erhalten Sie einen Ausnahme-Traceback wie den folgenden:
Traceback (most recent call last):
...
ValueError: Type names and field names cannot be a keyword: 'class' Dieser Traceback sagt Ihnen, dass der 'class' Spaltenname kein gültiger Feldname für Ihr namedtuple Feld ist. Um dieses Problem zu beheben, können Sie rename verwenden:
passenger.py from collections import namedtuple
from database import get_column_names
Passenger = namedtuple("Passenger", get_column_names("passenger"), rename=True) Das Setzen von rename auf True bewirkt, dass namedtuple() ungültige Namen automatisch durch Positionsnamen im Format _0, _1 und so weiter ersetzt.
Nehmen wir nun an, dass Sie eine Zeile aus der Datenbank abrufen und Ihre erste Passagier-Instanz erstellen:
>>> from passenger import Passenger >>> from database import get_passenger_by_id
>>> Passenger(*get_passenger_by_id(1234)) Passenger(id=1234, first_name='John', last_name='Doe', _3='Business') In diesem Fall ist get_passenger_by_id() eine weitere Funktion, die in Ihrer hypothetischen Anwendung verfügbar ist. Sie ruft die Daten für einen bestimmten Passagier ab, die in einem Tupel verpackt sind. Das Endergebnis ist, dass der neu erstellte Passagier den Klassen-Namen durch _3 ersetzt hat, da Python-Schlüsselwörter nicht als Feldnamen verwendet werden können.
Das zweite optionale Argument für namedtuple() ist defaults. Dieses Argument ist standardmäßig auf Keine gesetzt, was bedeutet, dass die Felder keine Standardwerte haben werden. Man kann defaults auf eine Iterable von Werten setzen. In diesem Fall weist namedtuple() die Werte in der defaults Iterable den Feldern ganz rechts zu:
>>> from collections import namedtuple
>>> Developer = namedtuple( ... "Developer", ... "name level language", ... defaults=["Junior", "Python"] ... )
>>> Developer("John") Developer(name='John', level='Junior', language='Python') In diesem Beispiel haben die Felder .level und .language Standardwerte, was sie zu optionalen Argumenten macht. Da Sie keinen Standardwert für Name definieren, müssen Sie einen Wert angeben, wenn Sie eine Instanz von Entwickler erstellen. Beachten Sie, dass die Standardwerte auf die Felder ganz rechts angewendet werden.
Das letzte Argument von namedtuple() ist module. Wenn man einen gültigen Modulnamen für dieses Argument angibt, wird das .__module__ Attribut des resultierenden namedtuple auf diesen Wert gesetzt. Dieses Attribut enthält den Namen des Moduls, in dem eine bestimmte Funktion oder Callable definiert ist:
>>> Point = namedtuple("Point", "x y", module="custom") >>> Point <class 'custom.Point'> >>> Point.__module__ 'custom' Wenn Sie in diesem Beispiel .__module__ auf Point zugreifen, erhalten Sie 'custom' als Ergebnis. Dies zeigt an, dass die Klasse Punkt in Ihrem custom-Modul definiert ist.
Die Motivation für das Hinzufügen des Moduls-Arguments zu namedtuple() in Python 3.6 war, dass es möglich sein sollte, dass named tuples pickling durch verschiedene Python Implementierungen unterstützen.
Anzeigen entfernen Zusätzliche Funktionen der namedtuple-Klassen erforschen Neben den von tuple geerbten Methoden, wie .index() und .count(), bieten die namededtuple-Klassen auch drei zusätzliche Methoden: ._make(), ._asdict(), und ._replace(). Sie haben auch zwei Attribute: ._fields und ._field_defaults.
Beachten Sie, dass die Namen dieser Methoden und Attribute mit einem Unterstrich beginnen. Dies dient dazu, Namenskonflikte mit Feldern zu vermeiden. In den folgenden Abschnitten lernen Sie diese Methoden und Attribute kennen und erfahren, wie sie funktionieren.
Erstellen von benannten Tupeln aus Iterables mit ._make() Mit der Methode ._make() kann man namededtuple-Instanzen aus einer Iterable von Werten erzeugen:
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height") >>> Person._make(["Jane", 25, 1.75]) Person(name='Jane', age=25, height=1.75) In diesem Beispiel wird der Typ Person mit der Fabrikfunktion namedtuple() definiert. Dann rufen Sie ._make() mit einer Liste von Werten auf, die den einzelnen Feldern entsprechen. Beachten Sie, dass ._make() eine Klassenmethode ist, die als alternativer Klassenkonstruktor funktioniert. Sie gibt eine neue namededtuple Instanz zurück. Schließlich erwartet ._make() eine einzelne iterable als Argument, wie eine Liste im obigen Beispiel.
Umwandlung benannter Tupel in Wörterbücher mit ._asdict() Sie können vorhandene benannte Tupel mit ._asdict() in Wörterbücher umwandeln. Diese Methode gibt ein neues Wörterbuch zurück, das die Feldnamen als Schlüssel und die gespeicherten Elemente als Werte verwendet:
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height") >>> jane = Person("Jane", 25, 1.75) >>> jane._asdict() {'name': 'Jane', 'age': 25, 'height': 1.75} Wenn Sie ._asdict() für ein benanntes Tupel aufrufen, erhalten Sie ein neues dict Objekt, das die Feldnamen den entsprechenden Werten zuordnet. Die Reihenfolge der Schlüssel im resultierenden Wörterbuch entspricht der Reihenfolge der Felder im ursprünglichen benannten Tupel.
Ersetzen von benannten Tupelfeldern mit ._replace() Die Methode ._replace() nimmt Schlüsselwortargumente der Form Feldname=Wert entgegen und gibt eine neue namededtuple-Instanz zurück, bei der der Wert des Feldes auf den neuen Wert aktualisiert wurde:
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height") >>> jane = Person("Jane", 25, 1.75)
>>> # After Jane's birthday >>> jane = jane._replace(age=26) >>> jane Person(name='Jane', age=26, height=1.75) In diesem Beispiel aktualisieren Sie das Alter von Jane nach ihrem Geburtstag. Obwohl der Name von ._replace() vermuten lässt, dass die Methode das vorhandene benannte Tupel an Ort und Stelle verändert, ist das in der Praxis nicht der Fall. Das liegt daran, dass benannte Tupel unveränderlich sind, so dass ._replace() eine neue Instanz zurückgibt, die man der Variablen jane zuweist und damit das ursprüngliche Objekt überschreibt.
Ab Python 3.13 kann man die replace()-Funktion aus dem copy-Modul verwenden, um eine ähnliche Funktionalität zu erreichen:
>>> from copy import replace >>> replace(jane, age=27) Person(name='Jane', age=27, height=1.75) Dieser neue Ansatz bietet eine intuitivere Möglichkeit, Felder in benannten Tupeln zu aktualisieren, indem er die replace()-Funktion nutzt, um eine geänderte Kopie der ursprünglichen Instanz zu erstellen.
Erforschung von benannten Tupel-Attributen: ._fields und ._field_defaults Benannte Tupel haben auch zwei Attribute: ._fields und ._field_defaults. Das erste Attribut enthält ein Tupel von Strings, die die Feldnamen darstellen. Das zweite Attribut enthält ein Wörterbuch, das die Feldnamen auf ihre jeweiligen Standardwerte abbildet, falls vorhanden.
Im Fall von ._fields können Sie es verwenden, um Ihre namededtuple Klassen und Instanzen zu untersuchen. Sie können auch neue Klassen aus bestehenden Klassen erstellen:
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height")
>>> ExtendedPerson = namedtuple( ... "ExtendedPerson", ... [*Person._fields, "weight"] ... )
>>> jane = ExtendedPerson("Jane", 26, 1.75, 67) >>> jane ExtendedPerson(name='Jane', age=26, height=1.75, weight=67) >>> jane.weight 67 In diesem Beispiel erstellen Sie ein neues namededtuple namens ExtendedPerson, indem Sie die Felder von Person wiederverwenden und ein neues Feld namens .weight hinzufügen. Dazu greift man auf ._fields von Person zu und packt es zusammen mit dem zusätzlichen Feld .weight in eine neue Liste aus.
Man kann auch ._fields verwenden, um über die Felder und Werte in einer gegebenen namedtuple Instanz zu iterieren, indem man die eingebaute zip() Funktion wie unten gezeigt verwendet:
>>> Person = namedtuple("Person", "name age height weight") >>> jane = Person("Jane", 26, 1.75, 67)
>>> for field, value in zip(jane._fields, jane): ... print(field, "->", value) ... name -> Jane age -> 26 height -> 1.75 weight -> 67 In diesem Beispiel ergibt zip() Tupel der Form (Feld, Wert), was Ihnen den Zugriff auf beide Elemente des Feld-Wert-Paares in dem zugrunde liegenden benannten Tupel ermöglicht. Eine andere Möglichkeit, gleichzeitig über Felder und Werte zu iterieren, ist die Verwendung von ._asdict().items(). Probieren Sie es einfach mal aus!
Mit ._field_defaults können Sie namedtuple Klassen und Instanzen introspektieren, um herauszufinden, welche Felder Standardwerte haben.
Mit Standardwerten werden die Felder optional. Nehmen wir an, Ihre Klasse Person benötigt ein zusätzliches Feld für das Land, in dem die Person lebt. Nehmen wir nun an, dass Sie hauptsächlich mit Personen aus Kanada arbeiten. In diesem Fall können Sie "Kanada" als Standardwert von .country verwenden:
>>> Person = namedtuple( ... "Person", ... "name age height weight country", ... defaults=["Canada"] ... )
>>> Person._field_defaults {'country': 'Canada'} Eine schnelle Abfrage von ._field_defaults lässt Sie wissen, welche Felder Standardwerte haben. In diesem Beispiel können andere Programmierer in Ihrem Team sehen, dass die Klasse Person "Kanada" als praktischen Standardwert für .country bereitstellt.
Wenn Ihr benanntes Tupel keine Standardwerte hat, dann enthält .field_defaults ein leeres Wörterbuch:
>>> Person = namedtuple("Person", "name age height weight country") >>> Person._field_defaults {} Wenn man namedtuple() keine Liste von Standardwerten zur Verfügung stellt, dann ist das defaults-Argument None, und das ._field_defaults enthält ein leeres Wörterbuch.
Anzeigen entfernen Schreiben von Pythonic Code mit namededtuple Benannte Tupel wurden entwickelt, um Ihnen zu helfen, lesbaren, eindeutigen und wartbaren Code zu schreiben. Sie sind ein Werkzeug, mit dem Sie reguläre Tupel durch einen gleichwertigen und kompatiblen Datentyp ersetzen können, der aufgrund der benannten Felder eindeutig und lesbar ist.
Die Verwendung benannter Felder und der Punktnotation ist lesbarer und weniger fehleranfällig als die Verwendung eckiger Klammern und ganzzahliger Indizes, die nur wenige Informationen über das Objekt liefern, auf das sie verweisen.
In den folgenden Abschnitten werden Sie ein paar praktische Beispiele schreiben, die gute Möglichkeiten aufzeigen, wie Sie benannte Tupel anstelle von regulären Tupeln verwenden können, um Ihren Code pythonischer zu gestalten.
Verwendung von Feldnamen anstelle von Indizes Angenommen, Sie erstellen eine Malanwendung und müssen die Eigenschaften des Stifts entsprechend der Auswahl des Benutzers definieren. Sie verwenden ein reguläres Tupel, um die Eigenschaften des Stifts zu speichern:
>>> pen = (2, "Solid", True)
>>> # Later in your code >>> if pen[0] == 2 and pen[1] == "Solid" and pen[2]: ... print("Standard pen selected") ... Standard pen selected Die erste Zeile des Codes definiert ein Tupel mit drei Werten. Können Sie die Bedeutung der einzelnen Werte ableiten? Sie können wahrscheinlich erraten, dass der zweite Wert mit dem Linienstil zusammenhängt, aber was ist die Bedeutung von 2 und True?
Um den Code klarer zu machen, könnte man einen netten Kommentar hinzufügen, um den Kontext für öffnen zu erläutern, was dann in etwa so aussehen würde:
>>> # Tuple containing: line weight, line style, and beveled edges >>> pen = (2, "Solid", True) Wenn Sie den Kommentar lesen, verstehen Sie die Bedeutung der einzelnen Werte im Tupel. Was aber, wenn Sie oder ein anderer Programmierer pen weit entfernt von dieser Definition verwenden? Sie müssten zur Definition zurückkehren, um zu überprüfen, was jeder Wert bedeutet.
Hier ist eine alternative Implementierung von pen unter Verwendung eines namededtuple:
>>> from collections import namedtuple
>>> Pen = namedtuple("Pen", "width style beveled") >>> pen = Pen(2, "Solid", True)
>>> if pen.width == 2 and pen.style == "Solid" and pen.beveled: ... print("Standard pen selected") ... Standard pen selected Ihr Code zeigt nun eindeutig an, dass 2 für die Stiftbreite steht, "Solid" für den Linienstil, und so weiter. Diese neue Implementierung bedeutet einen großen Unterschied in der Lesbarkeit und Wartungsfreundlichkeit, weil Sie jetzt die Felder des Stifts an jeder beliebigen Stelle in Ihrem Code überprüfen können, ohne zur Definition zurückkehren zu müssen.
Rückgabe mehrerer Werte aus Funktionen Eine weitere Situation, in der Sie ein benanntes Tupel verwenden können, ist, wenn Sie mehrere Werte aus einer Funktion zurückgeben müssen. Dazu verwenden Sie die Rückgabe-Anweisung, gefolgt von einer Reihe durch Komma getrennter Werte, die praktisch ein Tupel sind. Auch hier besteht das Problem darin, dass es schwierig sein kann, die einzelnen Werte zu bestimmen, wenn Sie die Funktion aufrufen.
In dieser Situation kann die Rückgabe eines benannten Tupels Ihren Code lesbarer machen, da die zurückgegebenen Werte auch einen gewissen Kontext für ihren Inhalt bieten.
Python hat zum Beispiel eine eingebaute Funktion namens divmod(), die zwei Zahlen als Argumente nimmt und ein Tupel mit dem Quotienten und dem Rest zurückgibt, der sich aus der ganzzahligen Division der eingegebenen Zahlen ergibt:
>>> divmod(8, 4) (2, 0) Um sich die Bedeutung der einzelnen Zahlen zu merken, sollten Sie die Dokumentation zu divmod() lesen, denn die Zahlen selbst geben nicht viel Aufschluss über ihre jeweilige Bedeutung. Auch der Funktionsname ist nicht sehr hilfreich.
Hier ist eine Funktion, die ein namededtuple verwendet, um die Bedeutung jeder Zahl zu verdeutlichen, die divmod() zurückgibt:
>>> from collections import namedtuple
>>> def custom_divmod(a, b): ... DivMod = namedtuple("DivMod", "quotient remainder") ... return DivMod(*divmod(a, b)) ...
>>> custom_divmod(8, 4) DivMod(quotient=2, remainder=0) In diesem Beispiel fügen Sie jedem zurückgegebenen Wert einen Kontext hinzu, so dass jeder Programmierer, der Ihren Code liest, sofort erkennen kann, was die einzelnen Zahlen bedeuten.
Anzeigen entfernen Reduzieren der Anzahl der Argumente in Funktionen Eine geringe Anzahl von Argumenten in Ihrer Funktion ist eine bewährte Programmierpraxis. Dadurch wird die Funktionssignatur prägnant und der Testvorgang wird durch die geringere Anzahl von Argumenten und möglichen Kombinationen zwischen ihnen optimiert. Wenn Sie eine Funktion mit vielen Argumenten haben, können Sie einige von ihnen mit benannten Tupeln gruppieren.
Angenommen, Sie programmieren eine Anwendung zur Verwaltung der Daten Ihrer Kunden. Die Anwendung verwendet eine Datenbank, um Kundendaten zu speichern. Um diese Daten zu verarbeiten und die Datenbank zu aktualisieren, haben Sie mehrere Funktionen erstellt. Eine dieser Funktionen ist create_client(), die etwa wie folgt aussieht:
def create_client(db, name, plan):
db.add_client(name) db.complete_client_profile(name, plan)
Diese Funktion benötigt drei Argumente. Das erste Argument, db, steht für die Datenbank, mit der Sie arbeiten. Die beiden anderen Argumente beziehen sich auf bestimmte Clients. Hier haben Sie die Möglichkeit, die Anzahl der Argumente zu reduzieren, indem Sie ein benanntes Tupel verwenden, um die mandantenbezogenen Argumente zu gruppieren:
from collections import namedtuple
Client = namedtuple("Client", "name plan") client = Client("John Doe", "Premium")
def create_client(db, client):
db.add_client(client.name) db.complete_client_profile( client.name, client.plan )
Jetzt benötigt create_client() nur noch zwei Argumente: db und client. Innerhalb der Funktion verwenden Sie praktische und beschreibende Feldnamen, um die Argumente für db.add_client() und db.complete_client_profile() bereitzustellen. Ihre Funktion create_user() ist jetzt mehr auf den Client ausgerichtet.
Tabellarische Daten aus Dateien und Datenbanken lesen Ein großartiger Anwendungsfall für benannte Tupel ist die Verwendung zum Speichern von Datenbankeinträgen. Sie können namededtuple Klassen definieren, die die Spaltennamen als Feldnamen verwenden und die Daten aus den Zeilen der Datenbank in benannte Tupel ziehen. Etwas Ähnliches können Sie auch mit CSV-Dateien machen.
Nehmen wir an, Sie haben eine CSV-Datei mit Daten über die Mitarbeiter Ihres Unternehmens und möchten diese Daten in eine geeignete Datenstruktur zur weiteren Verarbeitung einlesen. Ihre CSV-Datei sieht wie folgt aus:
employees.csv name,job,email "Linda","Technical Lead","linda@example.com" "Joe","Senior Web Developer","joe@example.com" "Lara","Project Manager","lara@example.com" "David","Data Analyst","david@example.com" "Jane","Senior Python Developer","jane@example.com" Sie erwägen, das csv-Modul von Python und dessen DictReader zur Verarbeitung der Datei zu verwenden, aber Sie haben eine zusätzliche Anforderung. Sie müssen die Daten in einer unveränderlichen und leichtgewichtigen Datenstruktur speichern, und DictReader gibt Dictionaries zurück, die viel Speicher verbrauchen und veränderbar sind.
In dieser Situation ist ein namededtuple eine gute Wahl:
>>> import csv >>> from collections import namedtuple
>>> with open("employees.csv", mode="r", encoding="utf-8") as csv_file: ... reader = csv.reader(csv_file) ... Employee = namedtuple("Employee", next(reader), rename=True) ... for row in reader: ... employee = Employee(*row) ... print(employee) ... Employee(name='Linda', job='Technical Lead', email='linda@example.com') Employee(name='Joe', job='Senior Web Developer', email='joe@example.com') Employee(name='Lara', job='Project Manager', email='lara@example.com') Employee(name='David', job='Data Analyst', email='david@example.com') Employee(name='Jane', job='Senior Python Developer', email='jane@example.com') In diesem Beispiel öffnen Sie zunächst die Datei Mitarbeiter.csv mit einer mit-Anweisung. Dann ruft man csv.reader() auf, um einen Iterator über die Zeilen der CSV-Datei zu erhalten. Mit namedtuple() wird eine neue Mitarbeiter Klasse erstellt. Der Aufruf der eingebauten Funktion next() ruft die erste Datenzeile von reader ab, die den Kopf der CSV-Datei enthält.
Dieser CSV-Header liefert die Feldnamen für Ihr namededtuple. Außerdem setzen Sie rename auf True, um Probleme mit ungültigen Feldnamen zu vermeiden, die bei der Arbeit mit Datenbanktabellen und -abfragen, CSV-Dateien oder anderen Arten von Tabellendaten häufig auftreten können.
Schließlich erstellt die for-Schleife eine Mitarbeiter-Instanz aus jeder Zeile in der CSV-Datei und druckt die Liste der Mitarbeiter auf dem Bildschirm aus.
Vergleich von namedtuple mit anderen Datenstrukturen Bisher hast du gelernt, wie du benannte Tupel verwenden kannst, um deinen Code lesbarer, eindeutiger und pythonischer zu machen. Sie haben auch einige Beispiele kennengelernt, die Ihnen helfen, Möglichkeiten für die Verwendung von benannten Tupeln in Ihrem Code zu erkennen.
In diesem Abschnitt werden Sie einen kurzen Blick auf die Gemeinsamkeiten und Unterschiede zwischen Named Tuple-Klassen und anderen Python-Datenstrukturen wie Wörterbüchern, Datenklassen und typisierten Named Tuples werfen. Sie werden benannte Tupel mit anderen Datenstrukturen in Bezug auf die folgenden Aspekte vergleichen:
Lesbarkeit Veränderlichkeit Speicherverbrauch Leistung Am Ende der folgenden Abschnitte werden Sie besser vorbereitet sein, um die richtige Datenstruktur für Ihren speziellen Anwendungsfall zu wählen.
Anzeigen entfernen namedtuple vs. Dictionaries Der Dict-Datentyp ist eine grundlegende Datenstruktur in Python. Die Sprache selbst ist auf Dictionaries aufgebaut, daher sind sie überall zu finden. Da sie so gebräuchlich und nützlich sind, haben Sie sie wahrscheinlich schon oft in Ihrem Code verwendet. Aber was ist der Unterschied zwischen benannten Tupeln und Wörterbüchern?
In Bezug auf die Lesbarkeit könnte man sagen, dass Dictionaries genauso gut lesbar sind wie benannte Tupel. Auch wenn sie keine Möglichkeit bieten, auf Attribute mit der Punktnotation zuzugreifen, ist die Schlüsselsuche im Stil eines Wörterbuchs recht gut lesbar und einfach:
>>> jane = {"name": "Jane", "age": 25, "height": 1.75} >>> jane["age"] 25
>>> # Equivalent named tuple >>> from collections import namedtuple >>> Person = namedtuple("Person", "name age height") >>> jane = Person("Jane", 25, 1.75) >>> jane.age 25 In beiden Beispielen können Sie die Absicht des Codes schnell erfassen. Die Definition der benannten Tupel erfordert allerdings ein paar zusätzliche Codezeilen: eine Zeile für den Import der Fabrikfunktion namedtuple() und eine weitere für die Definition der namedtuple-Klasse, Person. Die Syntax für den Zugriff auf die Werte ist jedoch in beiden Fällen ziemlich einfach.
Ein großer Unterschied zwischen beiden Datenstrukturen ist, dass Wörterbücher veränderbar und benannte Tupel unveränderbar sind. Das bedeutet, dass man Dictionaries an Ort und Stelle ändern kann, aber man kann benannte Tupel nicht ändern:
>>> jane = {"name": "Jane", "age": 25, "height": 1.75} >>> jane["age"] = 26 >>> jane["age"] 26 >>> jane["weight"] = 67 >>> jane {'name': 'Jane', 'age': 26, 'height': 1.75, 'weight': 67}
>>> # Equivalent named tuple >>> Person = namedtuple("Person", "name age height") >>> jane = Person("Jane", 25, 1.75)
>>> jane.age = 26 Traceback (most recent call last):
...
AttributeError: can't set attribute
>>> jane.weight = 67 Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'weight' Sie können den Wert eines vorhandenen Schlüssels in einem Wörterbuch aktualisieren, aber Sie können etwas Ähnliches nicht in einem benannten Tupel tun. Sie können neue Schlüssel-Wert-Paare zu bestehenden Wörterbüchern hinzufügen, aber Sie können keine Feld-Wert-Paare zu bestehenden benannten Tupeln hinzufügen.
Hinweis: In benannten Tupeln können Sie ._replace() verwenden, um den Wert eines bestimmten Feldes zu aktualisieren, aber diese Methode erzeugt und gibt eine neue benannte Tupelinstanz zurück, anstatt die zugrunde liegende Instanz an Ort und Stelle zu aktualisieren.
Wenn Sie eine unveränderliche Datenstruktur benötigen, um ein bestimmtes Problem zu lösen, sollten Sie ein benanntes Tupel anstelle eines Wörterbuchs verwenden, um Ihre Anforderungen zu erfüllen.
Was den Speicherverbrauch angeht, so sind benannte Tupel eine recht leichtgewichtige Datenstruktur. Starten Sie Ihren Code-Editor oder IDE und erstellen Sie das folgende Skript:
namedtuple_dict_memory.py from collections import namedtuple from pympler import asizeof
Point = namedtuple("Point", "x y z") point = Point(1, 2, 3)
namedtuple_size = asizeof.asizeof(point) dict_size = asizeof.asizeof(point._asdict()) gain = 100 - namedtuple_size / dict_size * 100
print(f"namedtuple: {namedtuple_size} bytes ({gain:.2f}% smaller)") print(f"dict: {dict_size} bytes") Dieses kleine Skript verwendet asizeof.asizeof() von Pympler, um den Speicherbedarf eines benannten Tupels und seines äquivalenten Wörterbuchs zu ermitteln.
Hinweis: Pympler ist ein Werkzeug zur Überwachung und Analyse des Speicherverhaltens von Python-Objekten. Man kann es von der PyPI mit pip wie unten gezeigt installieren:
$ python -m pip install pympler Nachdem Sie diesen Befehl ausgeführt haben, wird Pympler in Ihrer aktuellen Python-Umgebung verfügbar sein, und Sie können das obige Skript ausführen.
Wenn du das Skript von deiner Kommandozeile aus ausführst, wirst du eine Ausgabe wie die folgende erhalten:
$ python namedtuple_dict_memory.py namedtuple: 160 bytes (62.26% smaller) dict: 424 bytes Diese Ausgabe bestätigt, dass benannte Tupel weniger Speicher verbrauchen als entsprechende Wörterbücher. Wenn der Speicherverbrauch für Sie eine Einschränkung darstellt, sollten Sie die Verwendung eines benannten Tupels anstelle eines Wörterbuchs in Betracht ziehen.
Hinweis: Wenn Sie benannte Tupel und Wörterbücher vergleichen, hängt der endgültige Unterschied im Speicherverbrauch von der Anzahl der Werte und deren Typen ab. Bei unterschiedlichen Werten erhalten Sie unterschiedliche Ergebnisse.
Schließlich müssen Sie eine Vorstellung davon haben, wie unterschiedlich benannte Tupel und Wörterbücher in Bezug auf die Leistung sind. Dazu testen Sie die Mitgliedschaft und die Zugriffsoperationen auf Attribute. Gehen Sie zurück zu Ihrem Code-Editor und erstellen Sie das folgende Skript:
namedtuple_dict_time.py from collections import namedtuple from time import perf_counter
def average_time(structure, test_func):
time_measurements = [] for _ in range(1_000_000): start = perf_counter() test_func(structure) end = perf_counter() time_measurements.append(end - start) return sum(time_measurements) / len(time_measurements) * int(1e9)
def time_dict(dictionary):
"x" in dictionary "missing_key" in dictionary 2 in dictionary.values() "missing_value" in dictionary.values() dictionary["y"]
def time_namedtuple(named_tuple):
"x" in named_tuple._fields "missing_field" in named_tuple._fields 2 in named_tuple "missing_value" in named_tuple named_tuple.y
Point = namedtuple("Point", "x y z") point = Point(x=1, y=2, z=3)
namedtuple_time = average_time(point, time_namedtuple) dict_time = average_time(point._asdict(), time_dict) gain = dict_time / namedtuple_time
print(f"namedtuple: {namedtuple_time:.2f} ns ({gain:.2f}x faster)") print(f"dict: {dict_time:.2f} ns") Dieses Skript führt Operationen aus, die sowohl in Wörterbüchern als auch in benannten Tupeln vorkommen. Zu diesen Operationen gehören Zugehörigkeitstests und der Zugriff auf Attribute. Wenn Sie das Skript auf Ihrem System ausführen, erhalten Sie eine Ausgabe ähnlich der folgenden:
$ namedtuple_dict_time.py namedtuple: 145.00 ns (1.49x faster) dict: 216.26 ns Diese Ausgabe zeigt, dass Operationen auf benannten Tupeln etwas schneller sind als ähnliche Operationen auf Wörterbüchern.
Anzeigen entfernen Namendupel vs. Datenklassen Python verfügt über Datenklassen, die - laut PEP 557 - ähnlich wie Named Tuples sind, aber veränderbar sind:
Datenklassen kann man sich als "veränderbare benannte Tupel mit Voreinstellungen" vorstellen. (Quelle)
Es wäre jedoch genauer zu sagen, dass Datenklassen wie veränderbare benannte Tupel mit Typ-Hinweisen sind. Der Vorgaben Teil ist kein wirklicher Unterschied, da benannte Tupel auch Vorgabewerte für ihre Felder haben können. Auf den ersten Blick sind die Hauptunterschiede also die Veränderbarkeit und die Typ-Hinweise.
Um eine Datenklasse zu erstellen, müssen Sie den @dataclass-Dekorator aus dem dataclasses-Modul importieren. Dann können Sie Ihre Datenklassen mit der regulären Klassendefinitionssyntax definieren:
>>> from dataclasses import dataclass
>>> @dataclass ... class Person: ... name: str ... age: int ... height: float ... weight: float ... country: str = "Canada" ...
>>> jane = Person("Jane", 25, 1.75, 67) >>> jane Person(name='Jane', age=25, height=1.75, weight=67, country='Canada') >>> jane.name 'Jane' >>> jane.name = "Jane Doe" >>> jane.name 'Jane Doe' In Bezug auf die Lesbarkeit gibt es keine wesentlichen Unterschiede zwischen Datenklassen und benannten Tupeln. Beide bieten ähnliche Zeichenkettendarstellungen und ermöglichen den Zugriff auf Attribute über die Punktnotation.
Was die Veränderbarkeit angeht, so sind Datenklassen per Definition veränderbar, so dass man den Wert ihrer Attribute bei Bedarf ändern kann. Allerdings haben sie noch ein Ass im Ärmel. Man kann das @dataclass Argument des frozen Dekorators auf True setzen, um sie unveränderlich zu machen:
>>> from dataclasses import dataclass
>>> @dataclass(frozen=True) ... class Person: ... name: str ... age: int ... height: float ... weight: float ... country: str = "Canada" ...
>>> jane = Person("Jane", 25, 1.75, 67) >>> jane.name = "Jane Doe" Traceback (most recent call last):
...
dataclasses.FrozenInstanceError: cannot assign to field 'name' Wenn Sie frozen im Aufruf von @dataclass auf True setzen, dann machen Sie die Datenklasse unveränderlich. Wenn Sie in diesem Fall versuchen, den Namen von Jane zu aktualisieren, erhalten Sie einen FrozenInstanceError.
Ein weiterer subtiler Unterschied zwischen benannten Tupeln und Datenklassen ist, dass letztere standardmäßig nicht iterierbar sind. Bleiben Sie bei dem Beispiel von Jane und versuchen Sie, über ihre Daten zu iterieren:
>>> for field in jane: ... print(field) ... Traceback (most recent call last):
...
TypeError: 'Person' object is not iterable Wenn man versucht, über eine Bare-Bones-Datenklasse zu iterieren, erhält man eine TypeError-Ausnahme. Dies entspricht dem Verhalten von normalen Python-Klassen. Glücklicherweise gibt es Möglichkeiten, dieses Verhalten zu umgehen.
Sie können zum Beispiel eine Datenklasse iterierbar machen, indem Sie die spezielle Methode .__iter__() bereitstellen. Hier sehen Sie, wie Sie das in Ihrer Person-Klasse machen:
>>> from dataclasses import astuple, dataclass
>>> @dataclass ... class Person: ... name: str ... age: int ... height: float ... weight: float ... country: str = "Canada" ... ... def __iter__(self): ... return iter(astuple(self)) ...
>>> jane = Person("Jane", 25, 1.75, 67) >>> for field in jane: ... print(field) ... Jane 25 1.75 67 Canada In diesem Beispiel implementieren Sie die Methode .__iter__(), um die Datenklasse iterierbar zu machen. Die Funktion astuple() konvertiert die Datenklasse in ein Tupel, das Sie an die eingebaute Funktion iter() übergeben, um einen Iterator zu erzeugen. Mit diesem Zusatz kann man anfangen, über Janes Daten zu iterieren.
Was den Speicherverbrauch betrifft, so sind benannte Tupel leichter als Datenklassen. Sie können dies bestätigen, indem Sie ein kleines Skript erstellen und ausführen, das dem Skript aus dem obigen Abschnitt ähnelt. Um das vollständige Skript zu sehen, erweitern Sie das Feld unten.
Hier sind die Ergebnisse der Ausführung des obigen Skripts:
$ python namedtuple_dataclass_memory.py namedtuple: 160 bytes (72.60% smaller) data class: 584 bytes Im Gegensatz zu namedtuple Klassen, behalten Datenklassen eine .__dict__ pro Instanz um schreibbare Instanzattribute zu speichern. Dies trägt zu einem größeren Speicherbedarf bei.
Als Nächstes können Sie den Abschnitt unten erweitern, um ein Skript zu erhalten, das namedtuple-Klassen und Datenklassen in Bezug auf ihre Leistung beim Attributzugriff vergleicht.
Was die Leistung betrifft, so sind die Ergebnisse wie folgt:
$ python namedtuple_dataclass_time.py namedtuple: 83.24 ns data class: 61.15 ns Der Leistungsunterschied ist minimal, so dass beide Datenstrukturen bei Attributzugriffsoperationen gleich gut abschneiden.
Anzeigen entfernen namedtuple vs typing.NamedTuple Python wird mit einem Modul namens typing ausgeliefert, das Type Hints unterstützt. Dieses Modul bietet NamedTuple, das eine typisierte Version von namedtuple ist. Mit NamedTuple kann man namedtuple-Klassen mit Typ-Hinweisen erstellen.
Um mit dem Beispiel Person fortzufahren, können Sie ein äquivalentes typisiertes benanntes Tupel erstellen, wie im folgenden Code gezeigt:
>>> from typing import NamedTuple
>>> class Person(NamedTuple): ... name: str ... age: int ... height: float ... weight: float ... country: str = "Canada" ...
>>> issubclass(Person, tuple) True >>> jane = Person("Jane", 25, 1.75, 67) >>> jane.name 'Jane' >>> jane.name = "Jane Doe" Traceback (most recent call last):
...
AttributeError: can't set attribute Mit NamedTuple können Sie Tupel-Unterklassen erstellen, die Typ-Hinweise und Attribut-Zugriff durch Punkt-Notation unterstützen. Da die resultierende Klasse eine Tupel-Unterklasse ist, ist sie auch unveränderlich.
Ein subtiles Detail im obigen Beispiel ist, dass NamedTuple-Unterklassen den Datenklassen noch ähnlicher sehen als benannte Tupel.
Was den Speicherverbrauch angeht, so verbrauchen die Instanzen von NamedTuple und NamedTuple die gleiche Menge an Speicher. Sie können das Feld unten erweitern, um ein Skript anzuzeigen, das den Speicherverbrauch der beiden Instanzen vergleicht.
Dieses Mal erzeugt das Skript, das die Speichernutzung vergleicht, die folgende Ausgabe:
$ python typed_namedtuple_memory.py namedtuple: 160 bytes typing.NamedTuple: 160 bytes In diesem Fall verbrauchen beide Instanzen die gleiche Menge an Speicher, so dass es diesmal keinen Gewinner gibt.
Da die NamedTuple-Klassen und die NamedTuple-Unterklassen beide Unterklassen von Tuple sind, haben sie eine Menge gemeinsam. In diesem Fall können Sie die Leistung von Zugehörigkeitstests für Felder und Werte sowie den Zugriff auf Attribute unter Verwendung der Punktnotation vergleichen. Erweitern Sie das Feld unten, um ein Skript zu sehen, das namedtuple und NamedTuple vergleicht.
Hier sind die Ergebnisse:
$ python typed_namedtuple_time.py namedtuple: 144.90 ns typing.NamedTuple: 145.67 ns In diesem Fall kann man sagen, dass sich beide Datenstrukturen in Bezug auf die Leistung fast gleich verhalten. Abgesehen davon kann die Verwendung von NamedTuple zur Erstellung von benannten Tupeln Ihren Code noch eindeutiger machen, da Sie den Feldern Typinformationen hinzufügen können. Sie können auch Standardwerte angeben, neue Funktionen hinzufügen und Docstrings für Ihre typisierten benannten Tupel schreiben.
namedtuple vs tuple Bisher haben Sie die Klassen namededtuple mit anderen Datenstrukturen anhand verschiedener Merkmale verglichen. In diesem Abschnitt werden Sie einen allgemeinen Blick darauf werfen, wie reguläre Tupel und benannte Tupel in Bezug auf die Zeit, die für die Erstellung einer Instanz jeder Klasse benötigt wird, miteinander verglichen werden.
Angenommen, Sie haben eine Anwendung, die eine Menge Tupel dynamisch erstellt. Sie beschließen, Ihren Code mit benannten Tupeln pythonischer und wartungsfreundlicher zu gestalten. Nachdem Sie Ihre Codebase aktualisiert haben, um benannte Tupel zu verwenden, führen Sie die Anwendung aus und bemerken einige Leistungsprobleme. Nach einigen Tests kommen Sie zu dem Schluss, dass die Probleme mit der dynamischen Erstellung von benannten Tupeln zusammenhängen könnten.
Hier ist ein Skript, das die durchschnittliche Zeit misst, die benötigt wird, um mehrere Tupel und benannte Tupel dynamisch zu erstellen:
tuple_namedtuple_time.py from collections import namedtuple from time import perf_counter
def average_time(test_func):
time_measurements = [] for _ in range(1_000): start = perf_counter() test_func() end = perf_counter() time_measurements.append(end - start) return sum(time_measurements) / len(time_measurements) * int(1e9)
def time_tuple():
tuple([1] * 1000)
fields = [f"a{n}" for n in range(1000)] TestNamedTuple = namedtuple("TestNamedTuple", fields)
def time_namedtuple():
TestNamedTuple(*([1] * 1000))
namedtuple_time = average_time(time_namedtuple) tuple_time = average_time(time_tuple) gain = namedtuple_time / tuple_time
print(f"tuple: {tuple_time:.2f} ns ({gain:.2f}x faster)") print(f"namedtuple: {namedtuple_time:.2f} ns") In diesem Skript berechnen Sie die durchschnittliche Zeit, die benötigt wird, um mehrere Tupel und ihre gleichwertigen benannten Tupel zu erstellen. Wenn Sie das Skript über die Befehlszeile ausführen, erhalten Sie eine Ausgabe ähnlich der folgenden:
$ python tuple_namedtuple_time.py tuple: 1707.38 ns (5.07x faster) namedtuple: 8662.46 ns Anhand dieser Ausgabe können Sie feststellen, dass die dynamische Erstellung von Tupel-Objekten viel schneller ist als die Erstellung entsprechender benannter Tupel.
In manchen Situationen, z. B. bei der Arbeit mit großen Datenbanken, kann die zusätzliche Zeit, die für die Erstellung eines benannten Tupels benötigt wird, die Leistung Ihrer Anwendung ernsthaft beeinträchtigen.
Sie haben eine Menge über namededtuple und andere ähnliche Datenstrukturen und Klassen gelernt. Hier ist eine Tabelle, die zusammenfasst, wie die in den vorherigen Abschnitten behandelten Datenstrukturen mit namedtuple zu vergleichen sind:
dict @dataclass NamedTuple
Lesbarkeit Ähnlich Gleichberechtigt Gleich Unveränderlichkeit Nein Standardmäßig nein, ja, wenn Sie @dataclass(frozen=True) einstellen Ja Speicherverbrauch Höher Höher Gleich Leistung Langsamer Ähnlich Ähnlich Iterabilität Ja Standardmäßig nein, ja, wenn Sie eine .__iter__() Methode anbieten Ja Mit dieser Zusammenfassung sind Sie besser vorbereitet, um die Datenstruktur zu wählen, die Ihren Anforderungen am besten entspricht. Außerdem sollten Sie bedenken, dass Datenklassen und NamedTuple es Ihnen ermöglichen, Typhinweise hinzuzufügen, die die Typsicherheit Ihres Codes verbessern können.
Anzeigen entfernen Unterklassifizierung von namedtuple-Klassen Da namedtuple-Klassen reguläre Python-Klassen sind, können Sie sie unterklassifizieren, wenn Sie zusätzliche Funktionalität, einen docstring, eine benutzerfreundliche String-Darstellung oder andere zusätzliche Methoden benötigen.
Zum Beispiel ist es im Allgemeinen nicht sinnvoll, das Alter einer Person direkt in einem Objekt zu speichern. Stattdessen ist es besser, das Geburtsdatum zu speichern und das Alter bei Bedarf dynamisch zu berechnen:
>>> from collections import namedtuple >>> from datetime import date
>>> BasePerson = namedtuple( ... "BasePerson", ... "name birthdate country", ... defaults=["Canada"] ... )
>>> class Person(BasePerson): ... """A namedtuple subclass to hold a person's data.""" ... __slots__ = () ... ... def __str__(self): ... return f"Name: {self.name}, age: {self.age} years old." ... ... @property ... def age(self): ... return (date.today() - self.birthdate).days // 365 ...
>>> Person.__doc__ "A namedtuple subclass to hold a person's data."
>>> jane = Person("Jane", date(1996, 3, 5)) >>> jane.age 25 >>> jane Person(name='Jane', birthdate=datetime.date(1996, 3, 5), country='Canada') >>> print(jane) Name: Jane, age: 29 years old. Person erbt von BasePerson, die eine namededtuple Klasse ist. In der Unterklassendefinition fügen Sie einen docstring hinzu, der beschreibt, was die Klasse tut. Dann setzen Sie das .__slots__ Klassenattribut auf ein leeres Tupel. Dies verhindert die automatische Erstellung eines .__dict__ pro Instanz und hält Ihre BasePerson Unterklasse speichereffizient.
Sie fügen auch eine benutzerdefinierte .__str__() hinzu, um eine schöne String-Darstellung für die Klasse bereitzustellen. Schließlich wird eine .age Eigenschaft hinzugefügt, um das Alter der Person mit Hilfe des datetime-Moduls und einiger seiner Funktionen zu berechnen.
Schlussfolgerung Sie haben ein umfassendes Verständnis von Pythons namedtuple erlangt, einem Werkzeug im Sammlungen-Modul, das Tupel-Unterklassen mit benannten Feldern erzeugt. Sie haben gesehen, wie es die Lesbarkeit und Wartbarkeit von Code verbessern kann, indem es den Zugriff auf Tupel-Elemente über beschreibende Feldnamen anstelle von numerischen Indizes ermöglicht.
Sie haben auch zusätzliche Funktionen von namedtuple-Klassen kennengelernt, darunter Unveränderlichkeit, Speichereffizienz und Kompatibilität mit Wörterbüchern und anderen Datenstrukturen.
Zu wissen, wie man namedtuple verwendet, ist eine wertvolle Fähigkeit, denn sie erlaubt es, mehr Python-Code zu schreiben, der sauber, eindeutig und wartbar ist.
In diesem Tutorium hast du gelernt, wie man:
Erstellen und Verwenden von namedtuple Klassen und Instanzen Die Vorteile einiger cooler Funktionen von namedtuple nutzen Erkennen von Möglichkeiten, mehr pythonischen Code mit namedtuple zu schreiben Wähle namedtuple gegenüber anderen Datenstrukturen, wenn es angebracht ist Subclass ein namededtuple um neue Funktionen hinzuzufügen Mit diesem Wissen können Sie die Qualität Ihres bestehenden und zukünftigen Codes erheblich verbessern. Wenn Sie häufig Tupel verwenden, sollten Sie diese in benannte Tupel umwandeln, wann immer dies sinnvoll ist. Auf diese Weise wird Ihr Code viel lesbarer und pythonischer.
Holen Sie sich Ihren Code: Klicken Sie hier, um den kostenlosen Beispielcode herunterzuladen, der Ihnen zeigt, wie Sie namedtuple verwenden können, um Pythonic und sauberen Code zu schreiben.