Warum brauchen wir Tupel in Python (oder einem unveränderlichen Datentyp)?

140

Ich habe mehrere Python-Tutorials (zum Beispiel Dive Into Python) und die Sprachreferenz auf Python.org gelesen - ich verstehe nicht, warum die Sprache Tupel benötigt.

Tupel haben keine Methoden im Vergleich zu einer Liste oder einem Satz. Wenn ich ein Tupel in einen Satz oder eine Liste konvertieren muss, um sie sortieren zu können, was bringt es dann überhaupt, ein Tupel zu verwenden?

Unveränderlichkeit?

Warum kümmert es jemanden, wenn eine Variable an einem anderen Ort im Speicher lebt als zu dem Zeitpunkt, als sie ursprünglich zugewiesen wurde? Dieses ganze Geschäft der Unveränderlichkeit in Python scheint überbetont zu sein.

Wenn ich in C / C ++ einen Zeiger zuweise und auf einen gültigen Speicher zeige, ist es mir egal, wo sich die Adresse befindet, solange sie nicht null ist, bevor ich sie verwende.

Wann immer ich auf diese Variable verweise, muss ich nicht wissen, ob der Zeiger noch auf die ursprüngliche Adresse zeigt oder nicht. Ich überprüfe nur auf null und benutze es (oder nicht).

Wenn ich in Python eine Zeichenfolge (oder ein Tupel) x zuordne und dann die Zeichenfolge ändere, warum kümmert es mich dann, wenn es sich um das ursprüngliche Objekt handelt? Solange die Variable auf meine Daten verweist, ist das alles, was zählt.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x verweist immer noch auf die Daten, die ich möchte. Warum muss sich jemand darum kümmern, ob die ID gleich oder verschieden ist?

pyNewGuy
quelle
12
Sie achten auf den falschen Aspekt der Veränderlichkeit: "Ob die ID gleich oder verschieden ist" ist nur ein Nebeneffekt. "Ob die Daten, auf die andere Referenzen verweisen, die zuvor auf dasselbe Objekt verwiesen haben, jetzt Aktualisierungen widerspiegeln", ist kritisch.
Charles Duffy

Antworten:

124
  1. unveränderliche Objekte können eine wesentliche Optimierung ermöglichen; Dies ist vermutlich der Grund, warum Strings auch in Java unveränderlich sind, ganz separat entwickelt wurden, aber ungefähr zur gleichen Zeit wie Python, und fast alles ist in wirklich funktionierenden Sprachen unveränderlich.

  2. Insbesondere in Python können nur unveränderliche Elemente hashbar sein (und daher Mitglieder von Mengen oder Schlüssel in Wörterbüchern). Auch dies ist eine Optimierung, aber weit mehr als nur "substanziell" (das Entwerfen anständiger Hash-Tabellen, in denen vollständig veränderbare Objekte gespeichert sind, ist ein Albtraum - entweder Sie erstellen Kopien von allem, sobald Sie es hashen, oder der Albtraum, zu überprüfen, ob der Hash des Objekts vorliegt hat sich geändert, seit Sie das letzte Mal darauf hingewiesen haben, dass es seinen hässlichen Kopf zeigt.

Beispiel für ein Optimierungsproblem:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop
Alex Martelli
quelle
11
@musicfreak, siehe die Bearbeitung, die ich gerade durchgeführt habe, bei der das Erstellen eines Tupels mehr als 7,6-mal schneller ist als das Erstellen der entsprechenden Liste. Jetzt können Sie nicht mehr sagen, dass Sie "nie einen spürbaren Unterschied gesehen haben", es sei denn, Ihre Definition von "spürbar" "ist wirklich eigenartig ...
Alex Martelli
11
@musicfreak Ich denke, Sie missbrauchen "vorzeitige Optimierung ist die Wurzel allen Übels". Es gibt einen großen Unterschied zwischen vorzeitiger Optimierung in einer Anwendung (z. B. "Tupel sind schneller als Listen, daher werden in der gesamten App nur Tupel verwendet!") Und Benchmarks. Alex 'Benchmark ist aufschlussreich und das Wissen, dass das Erstellen eines Tupels schneller ist als das Erstellen einer Liste, kann uns bei zukünftigen Optimierungsvorgängen helfen (wenn es wirklich benötigt wird).
Virgil Dupras
5
@Alex, ist das "Erstellen" eines Tupels wirklich schneller als das "Erstellen einer Liste", oder sehen wir das Ergebnis der Python-Laufzeit, die das Tupel zwischenspeichert? Scheint mir letzteres.
Triptychon
6
@ACoolie, das wird total von den randomAnrufen dominiert (versuchen Sie genau das, Sie werden sehen!), Also nicht sehr wichtig. Versuchen Sie es python -mtimeit -s "x=23" "[x,x]"und Sie werden eine aussagekräftigere Beschleunigung von 2-3 Mal für das Erstellen des Tupels im Vergleich zum Erstellen der Liste sehen.
Alex Martelli
9
Für alle, die sich fragen: Wir konnten über eine Stunde Datenverarbeitung sparen, indem wir von Listen zu Tupeln wechselten.
Mark Ribau
42

Keine der obigen Antworten weist auf das eigentliche Problem von Tupeln gegen Listen hin, das viele Python-Neulinge nicht vollständig zu verstehen scheinen.

Tupel und Listen dienen unterschiedlichen Zwecken. Listen speichern homogene Daten. Sie können und sollten eine Liste wie diese haben:

["Bob", "Joe", "John", "Sam"]

Der Grund für die korrekte Verwendung von Listen liegt darin, dass dies alles homogene Datentypen sind, insbesondere die Namen von Personen. Aber nehmen Sie eine Liste wie diese:

["Billy", "Bob", "Joe", 42]

Diese Liste enthält den vollständigen Namen einer Person und ihr Alter. Das ist keine Art von Daten. Die richtige Art, diese Informationen zu speichern, ist entweder in einem Tupel oder in einem Objekt. Nehmen wir an, wir haben einige:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

Die Unveränderlichkeit und Veränderlichkeit von Tupeln und Listen ist nicht der Hauptunterschied. Eine Liste ist eine Liste derselben Art von Elementen: Dateien, Namen, Objekte. Tupel sind eine Gruppierung verschiedener Objekttypen. Sie haben unterschiedliche Verwendungszwecke und viele Python-Codierer missbrauchen Listen für das, wofür Tupel gedacht sind.

Bitte nicht.


Bearbeiten:

Ich denke, dieser Blog-Beitrag erklärt, warum ich das besser finde als ich: http://news.e-scribe.com/397

Grant Paul
quelle
13
Ich denke, Sie haben eine Vision, der zumindest von mir nicht zugestimmt wird, kennen die anderen nicht.
Stefano Borini
13
Ich bin auch mit dieser Antwort nicht einverstanden. Die Homogenität der Daten hat absolut nichts damit zu tun, ob Sie eine Liste oder ein Tupel verwenden sollten. Nichts in Python deutet auf diese Unterscheidung hin.
Glenn Maynard
14
Guido hat dies auch vor einigen Jahren betont. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy
11
Obwohl Guido (der Designer von Python) beabsichtigte, Listen für homogene Daten und Tupel für heterogene Daten zu verwenden, ist die Tatsache, dass die Sprache dies nicht erzwingt. Daher denke ich, dass diese Interpretation mehr ein Stilproblem ist als alles andere. Es kommt daher vor, dass in den typischen Anwendungsfällen vieler Menschen Listen eher Array-artig und Tupel eher Datensatz-artig sind. Dies sollte die Benutzer jedoch nicht davon abhalten, Listen für heterogene Daten zu verwenden, wenn dies besser zu ihrem Problem passt. Wie das Zen von Python sagt: Praktikabilität schlägt Reinheit.
John Y
9
@Glenn, du liegst im Grunde falsch. Eine der Hauptanwendungen von Tupeln ist der zusammengesetzte Datentyp zum Speichern mehrerer miteinander verbundener Daten. Die Tatsache, dass Sie über ein Tupel iterieren und viele der gleichen Vorgänge ausführen können, ändert daran nichts. (Beachten Sie als Referenz, dass Tupel in vielen anderen Sprachen nicht dieselben iterierbaren Funktionen wie ihre Listengegenstücke haben.)
HS.
22

Wenn ich ein Tupel in eine Menge oder Liste konvertieren muss, um sie sortieren zu können, wozu sollte ich dann überhaupt ein Tupel verwenden?

In diesem speziellen Fall gibt es wahrscheinlich keinen Punkt. Dies ist kein Problem, da dies nicht der Fall ist, in dem Sie die Verwendung eines Tupels in Betracht ziehen würden.

Wie Sie hervorheben, sind Tupel unveränderlich. Die Gründe für unveränderliche Typen gelten für Tupel:

  • Kopiereffizienz: Anstatt ein unveränderliches Objekt zu kopieren, können Sie es als Alias ​​verwenden (eine Variable an eine Referenz binden).
  • Vergleichseffizienz: Wenn Sie Copy-by-Reference verwenden, können Sie zwei Variablen vergleichen, indem Sie den Standort und nicht den Inhalt vergleichen
  • Internierung: Sie müssen höchstens eine Kopie eines unveränderlichen Wertes speichern
  • Der Zugriff auf unveränderliche Objekte im gleichzeitigen Code muss nicht synchronisiert werden
  • Konstante Korrektheit: Einige Werte sollten sich nicht ändern dürfen. Dies ist (für mich) der Hauptgrund für unveränderliche Typen.

Beachten Sie, dass eine bestimmte Python-Implementierung möglicherweise nicht alle oben genannten Funktionen nutzt.

Wörterbuchschlüssel müssen unveränderlich sein, andernfalls können durch Ändern der Eigenschaften eines Schlüsselobjekts Invarianten der zugrunde liegenden Datenstruktur ungültig werden. Tupel können daher möglicherweise als Schlüssel verwendet werden. Dies ist eine Folge der konstanten Korrektheit.

Siehe auch " Einführung in Tupel " von Dive Into Python .

outis
quelle
2
id ((1,2,3)) == id ((1,2,3)) ist falsch. Sie können Tupel nicht einfach durch Vergleichen des Speicherorts vergleichen, da nicht garantiert werden kann, dass sie als Referenz kopiert wurden.
Glenn Maynard
@Glenn: Beachten Sie die qualifizierende Bemerkung "Wenn Sie Copy-by-Reference verwenden". Während der Codierer seine eigene Implementierung erstellen kann, ist das Kopieren als Referenz für Tupel weitgehend Sache des Interpreters / Compilers. Ich bezog mich hauptsächlich auf die ==Implementierung auf Plattformebene.
Outis
1
@Glenn: Beachten Sie auch, dass Copy-by-Reference nicht für die Tupel in gilt (1,2,3) == (1,2,3). Das ist eher eine Frage des Praktikums.
Outis
Wie ich ziemlich deutlich sagte, gibt es keine Garantie dafür, dass sie als Referenz kopiert wurden . Tupel sind nicht in Python interniert. Das ist ein String-Konzept.
Glenn Maynard
Wie ich ganz klar sagte: Ich spreche nicht davon, dass der Programmierer Tupel durch Vergleichen der Position vergleicht. Ich spreche von der Möglichkeit, dass die Plattform kann, was eine Kopie durch Referenz garantieren kann. Internierung kann auch auf jeden unveränderlichen Typ angewendet werden, nicht nur auf Zeichenfolgen. Die Hauptimplementierung von Python enthält möglicherweise keine internen unveränderlichen Typen, aber die Tatsache, dass Python unveränderliche Typen hat, macht das Internieren zu einer Option.
Outis
15

Manchmal verwenden wir gerne Objekte als Wörterbuchschlüssel

Für das, was es wert ist, sind in letzter Zeit Tupel (2.6+) index()und count()Methoden gewachsen

John La Rooy
quelle
5
+1: Eine veränderbare Liste (oder veränderbare Menge oder veränderbares Wörterbuch) als Wörterbuchschlüssel kann nicht funktionieren. Wir brauchen also unveränderliche Listen ("Tupel"), eingefrorene Sets und ... nun ... ein eingefrorenes Wörterbuch, nehme ich an.
S.Lott
9

Ich habe immer festgestellt, dass zwei völlig getrennte Typen für dieselbe grundlegende Datenstruktur (Arrays) ein umständliches Design sind, in der Praxis jedoch kein wirkliches Problem. (Jede Sprache hat ihre Warzen, einschließlich Python, aber dies ist keine wichtige.)

Warum kümmert es jemanden, wenn eine Variable an einem anderen Ort im Speicher lebt als zu dem Zeitpunkt, als sie ursprünglich zugewiesen wurde? Dieses ganze Geschäft der Unveränderlichkeit in Python scheint überbetont zu sein.

Das sind verschiedene Dinge. Die Veränderlichkeit hängt nicht mit dem Ort zusammen, an dem sie gespeichert ist. es bedeutet, dass sich das Zeug, auf das es zeigt, nicht ändern kann.

Python-Objekte können den Speicherort nicht ändern, nachdem sie erstellt wurden, veränderbar oder nicht. (Genauer gesagt kann sich der Wert von id () nicht ändern - in der Praxis auch.) Der interne Speicher von veränderlichen Objekten kann sich ändern, aber das ist ein verstecktes Implementierungsdetail.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

Dadurch wird die Variable nicht geändert ("mutiert"). Es wird eine neue Variable mit demselben Namen erstellt und die alte verworfen. Vergleichen Sie mit einer Mutationsoperation:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

Wie andere bereits betont haben, können auf diese Weise Arrays als Schlüssel für Wörterbücher und andere Datenstrukturen verwendet werden, die unveränderlich sein müssen.

Beachten Sie, dass Schlüssel für Wörterbücher nicht vollständig unveränderlich sein müssen. Nur der Teil, der als Schlüssel verwendet wird, muss unveränderlich sein. Für einige Anwendungen ist dies eine wichtige Unterscheidung. Sie könnten beispielsweise eine Klasse haben, die einen Benutzer darstellt, der Gleichheit und einen Hash mit dem eindeutigen Benutzernamen vergleicht. Sie können dann andere veränderbare Daten an die Klasse hängen - "Benutzer ist angemeldet" usw. Da dies weder die Gleichheit noch den Hash beeinflusst, ist es möglich und absolut gültig, diese als Schlüssel in einem Wörterbuch zu verwenden. Dies wird in Python nicht allzu häufig benötigt. Ich weise nur darauf hin, da mehrere Leute behauptet haben, dass Schlüssel "unveränderlich" sein müssen, was nur teilweise richtig ist. Ich habe dies jedoch oft mit C ++ - Karten und -Sätzen verwendet.

Glenn Maynard
quelle
>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Sie ' Wir haben gerade einen veränderlichen Datentyp geändert, sodass er in Bezug auf die ursprüngliche Frage keinen Sinn ergibt. x = 'Hallo "ID (x) 12345 x =" Auf Wiedersehen "ID (x) 65432 Wen interessiert es, ob es sich um ein neues Objekt handelt oder nicht. Solange x auf die von mir zugewiesenen Daten zeigt, ist das alles, was zählt.
pyNewGuy
4
Sie sind weit über meine Fähigkeit hinaus verwirrt, Ihnen zu helfen.
Glenn Maynard
+1 für den Hinweis auf Verwirrung in den Unterfragen, die die Hauptursache für Schwierigkeiten bei der Wahrnehmung des Werts von Tupeln zu sein scheinen.
Outis
1
Wenn ich könnte, ein weiteres +1 für den Hinweis, dass die wahre Rubrik für Schlüssel ist, ob das Objekt hashbar ist oder nicht ( docs.python.org/glossary.html#term-hashable ).
Outis
7

Wie Gnibbler in einem Kommentar anbot, hatte Guido eine Meinung , die nicht vollständig akzeptiert / geschätzt wird: „Listen sind für homogene Daten, Tupel sind für heterogene Daten“. Natürlich interpretierten viele der Gegner dies so, dass alle Elemente einer Liste vom gleichen Typ sein sollten.

Ich sehe es gerne anders, ähnlich wie andere es auch in der Vergangenheit getan haben:

blue= 0, 0, 255
alist= ["red", "green", blue]

Beachten Sie, dass ich alist als homogen betrachte, auch wenn type (alist [1])! = Type (alist [2]).

Wenn ich die Reihenfolge der Elemente ändern kann und keine Probleme in meinem Code haben (abgesehen von Annahmen, z. B. "es sollte sortiert werden"), sollte eine Liste verwendet werden. Wenn nicht (wie im blueobigen Tupel ), sollte ich ein Tupel verwenden.

tzot
quelle
Wenn ich könnte, würde ich diese Antwort 15 Mal abstimmen. Genau so fühle ich mich mit Tupeln.
Grant Paul
6

Sie sind wichtig, da sie dem Anrufer garantieren, dass das Objekt, das sie übergeben, nicht mutiert wird. Wenn du das tust:

a = [1,1,1]
doWork(a)

Der Anrufer hat keine Garantie für den Wert von a nach dem Anruf. Jedoch,

a = (1,1,1)
doWorK(a)

Jetzt wissen Sie als Anrufer oder als Leser dieses Codes, dass a dasselbe ist. Sie könnten für dieses Szenario immer eine Kopie der Liste erstellen und diese übergeben, aber jetzt verschwenden Sie Zyklen, anstatt ein Sprachkonstrukt zu verwenden, das semantischer Sinn macht.

Matthew Manela
quelle
1
Dies ist eine sehr sekundäre Eigenschaft von Tupeln. Es gibt zu viele Fälle, in denen Sie ein veränderbares Objekt haben, das Sie an eine Funktion übergeben möchten, und es nicht ändern lassen, unabhängig davon, ob es sich um eine bereits vorhandene Liste oder eine andere Klasse handelt. In Python gibt es einfach kein Konzept für "const-Parameter nach Referenz" (z. B. const foo & in C ++). Tupel geben Ihnen dies, wenn es bequem ist, überhaupt ein Tupel zu verwenden. Wenn Sie jedoch eine Liste von Ihrem Anrufer erhalten haben, werden Sie sie dann wirklich in ein Tupel konvertieren, bevor Sie sie an einen anderen Ort weitergeben?
Glenn Maynard
Da stimme ich Ihnen zu. Ein Tupel ist nicht dasselbe wie ein const-Schlüsselwort. Mein Punkt ist, dass die Unveränderlichkeit eines Tupels dem Leser des Codes eine zusätzliche Bedeutung verleiht. In einer Situation, in der beides funktionieren würde und Sie erwarten, dass es sich mit dem Tupel nicht ändern sollte, wird dies dem Leser diese zusätzliche Bedeutung hinzufügen (und dies auch sicherstellen)
Matthew Manela
a = [1,1,1] doWork (a) wenn dowork () als def dowork (arg) definiert ist: arg = [0,0,0] der Aufruf von dowork () in einer Liste oder einem Tupel hat das gleiche Ergebnis
pyNewGuy
1

Sie können hier für eine Diskussion darüber sehen

Ghostdog74
quelle
1

Ihre Frage (und Ihre nachfolgenden Kommentare) konzentrieren sich darauf, ob sich die id () während einer Aufgabe ändert. Es ist vielleicht nicht der beste Ansatz, sich auf diesen Folgeeffekt des Unterschieds zwischen dem Ersetzen unveränderlicher Objekte und der Modifikation veränderlicher Objekte anstatt auf den Unterschied selbst zu konzentrieren.

Bevor wir fortfahren, stellen Sie sicher, dass das unten gezeigte Verhalten Ihren Erwartungen an Python entspricht.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

In diesem Fall wurde der Inhalt von a2 geändert, obwohl nur a1 ein neuer Wert zugewiesen wurde. Im Gegensatz zu folgenden:

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

In diesem letzteren Fall haben wir die gesamte Liste ersetzt, anstatt ihren Inhalt zu aktualisieren. Bei unveränderlichen Typen wie Tupeln ist dies das einzige zulässige Verhalten.

Warum ist das wichtig? Angenommen, Sie haben ein Diktat:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

Mit einem Tupel kann das Wörterbuch sicher sein, dass seine Schlüssel nicht "darunter" in Elemente geändert werden, die einen anderen Wert haben. Dies ist wichtig, um eine effiziente Implementierung zu ermöglichen.

Charles Duffy
quelle
Wie andere betont haben, ist Unveränderlichkeit! = Hashability. Nicht alle Tupel können als Wörterbuchschlüssel verwendet werden: {([1], [2]): 'Wert'} schlägt fehl, weil die veränderlichen Listen im Tupel geändert werden können, aber {((1), (2)): ' Wert '} ist OK.
Ned Deily
Ned, das stimmt, aber ich bin mir nicht sicher, ob die Unterscheidung für die gestellte Frage von Bedeutung ist.
Charles Duffy
@ K.Nicholas, die Bearbeitung, die Sie hier genehmigt haben, hat den Code so geändert, dass eine Ganzzahl und kein Tupel zugewiesen wird. Dadurch schlagen die späteren Indexoperationen fehl, sodass sie möglicherweise nicht getestet haben, dass die neue Transkript war tatsächlich möglich. Richtig identifiziertes Problem, klar; ungültige Lösung.
Charles Duffy
@ MichaelPuckettII ebenfalls siehe oben.
Charles Duffy