Was heißen Tupel?
Ein benanntes Tupel ist ein Tupel.
Es macht alles, was ein Tupel kann.
Aber es ist mehr als nur ein Tupel.
Es ist eine bestimmte Unterklasse eines Tupels, die programmgesteuert nach Ihrer Spezifikation erstellt wird, mit benannten Feldern und einer festen Länge.
Dies erzeugt beispielsweise eine Unterklasse von Tupeln und kann nicht nur eine feste Länge haben (in diesem Fall drei), sondern überall dort verwendet werden, wo ein Tupel verwendet wird, ohne zu brechen. Dies ist als Liskov-Substituierbarkeit bekannt.
Neu in Python 3.6 können wir eine Klassendefinition verwendentyping.NamedTuple
, um ein benanntes Tupel zu erstellen:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
Das Obige ist das gleiche wie das Untere, außer dass das Obige zusätzlich Typanmerkungen und eine Dokumentzeichenfolge enthält. Folgendes ist in Python 2+ verfügbar:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Dies instanziiert es:
>>> ant = ANamedTuple(1, 'bar', [])
Wir können es inspizieren und seine Attribute verwenden:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Tiefere Erklärung
Um benannte Tupel zu verstehen, müssen Sie zuerst wissen, was ein Tupel ist. Ein Tupel ist im Wesentlichen eine unveränderliche Liste (kann nicht direkt im Speicher geändert werden).
So können Sie ein normales Tupel verwenden:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Sie können ein Tupel durch iterierbares Entpacken erweitern:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Benannte Tupel sind Tupel, mit denen auf ihre Elemente nach Namen anstatt nur nach Index zugegriffen werden kann!
Sie erstellen ein benanntes Tupel wie folgt:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
Sie können auch eine einzelne Zeichenfolge verwenden, deren Namen durch Leerzeichen getrennt sind. Dies ist eine etwas besser lesbare Verwendung der API:
>>> Student = namedtuple('Student', 'first last grade')
Wie benutzt man sie?
Sie können alles tun, was Tupel können (siehe oben) sowie Folgendes tun:
>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
'Lisa'
>>> named_student_tuple.last
'Simpson'
>>> named_student_tuple.grade
'A'
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')
Ein Kommentator fragte:
Wo definiert man in einem großen Skript oder Programm normalerweise ein benanntes Tupel?
Die Typen, mit namedtuple
denen Sie erstellen, sind im Grunde Klassen, die Sie mit einfacher Kurzform erstellen können. Behandle sie wie Klassen. Definieren Sie sie auf Modulebene, damit pickle und andere Benutzer sie finden können.
Das Arbeitsbeispiel auf globaler Modulebene:
>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')
Und dies zeigt, dass die Definition nicht nachgeschlagen werden kann:
>>> def foo():
... LocalNT = namedtuple('LocalNT', 'foo bar')
... return LocalNT('foo', 'bar')
...
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed
Warum / wann sollte ich benannte Tupel anstelle von normalen Tupeln verwenden?
Verwenden Sie sie, wenn es Ihren Code verbessert, damit die Semantik von Tupelelementen in Ihrem Code ausgedrückt wird.
Sie können sie anstelle eines Objekts verwenden, wenn Sie andernfalls ein Objekt mit unveränderlichen Datenattributen und ohne Funktionalität verwenden würden.
Sie können sie auch in Unterklassen unterteilen, um Funktionen hinzuzufügen, z. B .:
class Point(namedtuple('Point', 'x y')):
"""adding functionality to a named tuple"""
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
Warum / wann sollte ich normale Tupel anstelle von benannten Tupeln verwenden?
Es wäre wahrscheinlich eine Regression, von benannten Tupeln zu Tupeln zu wechseln. Bei der Entwurfsentscheidung im Voraus geht es darum, ob die Kosten für den zusätzlichen Code die verbesserte Lesbarkeit bei Verwendung des Tupels wert sind.
Es gibt keinen zusätzlichen Speicher, der von benannten Tupeln gegenüber Tupeln verwendet wird.
Gibt es irgendeine Art von "benannter Liste" (eine veränderbare Version des benannten Tupels)?
Sie suchen entweder nach einem Objekt mit Schlitz, das alle Funktionen einer Liste mit statischer Größe implementiert, oder nach einer Liste mit Unterklassen, die wie ein benanntes Tupel funktioniert (und die Größe der Liste irgendwie daran hindert, ihre Größe zu ändern.)
Ein jetzt erweitertes und vielleicht sogar durch Liskov ersetzbares Beispiel des ersten:
from collections import Sequence
class MutableTuple(Sequence):
"""Abstract Base Class for objects that work like mutable
namedtuples. Subclass and define your named fields with
__slots__ and away you go.
"""
__slots__ = ()
def __init__(self, *args):
for slot, arg in zip(self.__slots__, args):
setattr(self, slot, arg)
def __repr__(self):
return type(self).__name__ + repr(tuple(self))
# more direct __iter__ than Sequence's
def __iter__(self):
for name in self.__slots__:
yield getattr(self, name)
# Sequence requires __getitem__ & __len__:
def __getitem__(self, index):
return getattr(self, self.__slots__[index])
def __len__(self):
return len(self.__slots__)
Und um zu verwenden, einfach Unterklasse und definieren __slots__
:
class Student(MutableTuple):
__slots__ = 'first', 'last', 'grade' # customize
>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
>>> student[0]
'Lisa'
>>> student[2]
'A'
>>> len(student)
3
>>> 'Lisa' in student
True
>>> 'Bart' in student
False
>>> student.first = 'Bart'
>>> for i in student: print(i)
...
Bart
Simpson
A