
Dieser Artikel handelt von einer der besten Erfindungen von Python: namedtuple. Wir werden seine angenehmen Eigenschaften betrachten, von bekannt bis nicht offensichtlich. Der Grad des Eintauchens in das Thema wird allmählich zunehmen, daher hoffe ich, dass jeder etwas Interessantes für sich findet. Lass uns gehen!
Einführung
Sicherlich sind Sie mit einer Situation konfrontiert, in der Sie mehrere Eigenschaften des Objekts in einem Stück übertragen müssen. Zum Beispiel Informationen zu einem Haustier: Typ, Spitzname und Alter.
Oft ist es zu faul, eine separate Klasse für dieses Ding zu erstellen, und es werden Tupel verwendet:
("pigeon", "", 3) ("fox", "", 7) ("parrot", "", 1)
Zur besseren Übersichtlichkeit ist ein benanntes Tupel - collections.namedtuple
geeignet:
from collections import namedtuple Pet = namedtuple("Pet", "type name age") frank = Pet(type="pigeon", name="", age=3) >>> frank.age 3
Jeder weiß das ツ Und hier sind ein paar weniger bekannte Funktionen:
Schnellwechselfelder
Was ist, wenn eine der Eigenschaften geändert werden muss? Frank altert und die Wagenkolonne ist unveränderlich. Um es nicht vollständig neu zu erstellen, haben wir die Methode _replace()
entwickelt:
>>> frank._replace(age=4) Pet(type='pigeon', name='', age=4)
Und wenn Sie die gesamte Struktur veränderbar machen möchten - _asdict()
:
>>> frank._asdict() OrderedDict([('type', 'pigeon'), ('name', ''), ('age', 3)])
Automatische Titeländerung
Angenommen, Sie importieren Daten aus einer CSV und verwandeln jede Zeile in ein Tupel. Die Feldnamen wurden aus dem Header der CSV-Datei übernommen. Aber etwas geht schief:
Die Lösung ist das Argument rename=True
im Konstruktor:
"Nicht erfolgreiche" Namen wurden gemäß den Seriennummern umbenannt.
Standardwerte
Wenn ein Tupel mehrere optionale Felder enthält, müssen Sie diese bei jedem Erstellen eines Objekts auflisten:
Pet = namedtuple("Pet", "type name alt_name") >>> Pet("pigeon", "") TypeError: __new__() missing 1 required positional argument: 'alt_name' >>> Pet("pigeon", "", None) Pet(type='pigeon', name='', alt_name=None)
Um dies zu vermeiden, geben Sie im Konstruktor die defaults
an:
Pet = namedtuple("Pet", "type name alt_name", defaults=("",)) >>> Pet("pigeon", "") Pet(type='pigeon', name='', alt_name='')
Standardmäßig werden Standardwerte vom Ende zugewiesen. Funktioniert in Python 3.7+
Bei älteren Versionen können Sie mit dem Prototyp ungeschickter das gleiche Ergebnis erzielen:
Pet = namedtuple("Pet", "type name alt_name") default_pet = Pet(None, None, "") >>> default_pet._replace(type="pigeon", name="") Pet(type='pigeon', name='', alt_name='') >>> default_pet._replace(type="fox", name="") Pet(type='fox', name='', alt_name='')
Aber mit defaults
natürlich viel schöner.
Außergewöhnliche Leichtigkeit
Einer der Vorteile eines benannten Tupels ist die Leichtigkeit. Eine Armee von hunderttausend Tauben benötigt nur 10 Megabyte:
from collections import namedtuple import objsize
Wenn Sie Pet zum Vergleich zu einer normalen Klasse machen, belegt eine ähnliche Liste bereits 19 Megabyte.
Dies geschieht, weil gewöhnliche Objekte in Python einen schweren __dict__ Dander tragen, der die Namen und Werte aller Attribute des Objekts enthält:
class PetObj: def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_obj = PetObj(type="pigeon", name="", age=3) >>> frank_obj.__dict__ {'type': 'pigeon', 'name': '', 'age': 3}
Die Namedtuple-Objekte haben kein Wörterbuch und benötigen daher weniger Speicher:
frank = Pet(type="pigeon", name="", age=3) >>> frank.__dict__ AttributeError: 'Pet' object has no attribute '__dict__' >>> objsize.get_deep_size(frank_obj) 335 >>> objsize.get_deep_size(frank) 239
Aber wie wurde das genannte Tupel __dict__
los? Lesen Sie weiter ツ
Reiche innere Welt
Wenn Sie schon lange mit Python arbeiten, wissen Sie wahrscheinlich: Ein leichtes Objekt kann über den Dander __slots__ erstellt werden:
class PetSlots: __slots__ = ("type", "name", "age") def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_slots = PetSlots(type="pigeon", name="", age=3)
"Slot" -Objekte haben kein Wörterbuch mit Attributen, daher nehmen sie wenig Speicherplatz ein. "Frank in den Slots" ist so leicht wie "Frank in der Autokolonne", siehe:
>>> objsize.get_deep_size(frank) 239 >>> objsize.get_deep_size(frank_slots) 231
Wenn Sie entscheiden, dass namedtuple auch Slots verwendet, ist dies nicht weit von der Wahrheit entfernt. Wie Sie sich erinnern, werden bestimmte Tupelklassen dynamisch deklariert:
Pet = namedtuple("Pet", "type name age")
Der Namedtuple-Konstruktor verwendet verschiedene dunkle Magie und generiert so etwas wie diese Klasse (stark vereinfacht):
class Pet(tuple): __slots__ = () type = property(operator.itemgetter(0)) name = property(operator.itemgetter(1)) age = property(operator.itemgetter(2)) def __new__(cls, type, name, age): return tuple.__new__(cls, (type, name, age))
Das heißt, unser Haustier ist ein gewöhnliches tuple
, an das drei Eigenschaftsmethoden mit Nägeln geheftet wurden:
type
gibt das Nullelement des Tupels zurückname
- das erste Element des Tupelsage
- das zweite Element des Tupels
Und __slots__
nur benötigt, um die Objekte hell zu machen. Infolgedessen nimmt Pet wenig Platz ein und kann als normales Tupel verwendet werden:
>>> frank.index("") 1 >>> type, _, _ = frank >>> type 'pigeon'
Listig erfunden, oder?
Datenklassen nicht unterlegen
Da sprechen wir über Codegenerierung. In Python 3.7 ist ein Übergenerator-Code erschienen, der keine gleichen Datenklassen hat.
Wenn Sie zum ersten Mal eine Datenklasse sehen, möchten Sie nur aus diesem Grund zu einer neuen Version der Sprache wechseln:
from dataclasses import dataclass @dataclass class PetData: type: str name: str age: int
Wunder ist so gut! Aber es gibt eine Nuance - es ist fett:
frank_data = PetData(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_data) 335 >>> objsize.get_deep_size(frank) 239
Die Datenklasse generiert eine reguläre Python-Klasse, deren Objekte unter dem Gewicht von __dict__
erschöpft sind. Wenn Sie also Zeilen von der Basis lesen und in Objekte verwandeln, sind Datenklassen nicht die beste Wahl.
Aber warten Sie, Sie können eine Datenklasse wie ein Tupel einfrieren. Vielleicht wird es dann einfacher?
@dataclass(frozen=True) class PetFrozen: type: str name: str age: int frank_frozen = PetFrozen(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_frozen) 335
Leider. Selbst eingefroren blieb es ein gewöhnliches schweres Objekt mit einem Wörterbuch von Attributen. Wenn Sie also leichte unveränderliche Objekte benötigen (die auch als normale Tupel verwendet werden können), ist namedtuple immer noch die beste Wahl.
⌘ ⌘ ⌘
Ich mag das genannte Tupel wirklich:
- ehrlich iterable,
- dynamische Typdeklaration
- Benannter Attributzugriff
- leicht und unveränderlich.
Gleichzeitig wird es in 150 Codezeilen implementiert. Was braucht man sonst noch für das Glück?
Wenn Sie mehr über die Standard-Python-Bibliothek erfahren möchten, abonnieren Sie den @ ohmypy- Kanal