Warum gibt es in Python kein Tupelverständnis?

337

Wie wir alle wissen, gibt es Listenverständnis wie

[i for i in [1, 2, 3, 4]]

und es gibt Wörterbuchverständnis, wie

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

aber

(i for i in (1, 2, 3))

wird in einem Generator enden, nicht in einem tupleVerständnis. Warum ist das so?

Ich vermute, dass a tupleunveränderlich ist, aber dies scheint nicht die Antwort zu sein.

Shady Xu
quelle
15
Es gibt auch ein festgelegtes Verständnis - das einem
diktierten
3
Es gibt einen Syntaxfehler in Ihrem Code: {i:j for i,j in {1:'a', 2:'b'}}sollte sein{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Danke für den Hinweis -.-
Shady Xu
Nur der Nachwelt zuliebe gibt es eine Diskussion darüber im Python Chat
Inbar Rose
Anscheinend gibt es. stackoverflow.com/a/51811147/9627166
Super S

Antworten:

466

Sie können einen Generatorausdruck verwenden:

tuple(i for i in (1, 2, 3))

Für… Generatorausdrücke wurden jedoch bereits Klammern gesetzt.

Martijn Pieters
quelle
15
Mit diesem Argument könnte man sagen, dass ein Listenverständnis auch nicht notwendig ist : list(i for i in (1,2,3)). Ich denke wirklich, es liegt einfach daran, dass es keine saubere Syntax dafür gibt (oder zumindest niemand daran gedacht hat)
mgilson
79
Eine Liste, ein Satz oder ein Diktatverständnis ist nur syntaktischer Zucker, um einen Generatorausdruck zu verwenden, der einen bestimmten Typ ausgibt. list(i for i in (1, 2, 3))ist ein Generatorausdruck, der eine Liste und set(i for i in (1, 2, 3))eine Menge ausgibt. Bedeutet das, dass die Verständnissyntax nicht benötigt wird? Vielleicht nicht, aber es ist schrecklich praktisch. In den seltenen Fällen, in denen Sie stattdessen ein Tupel benötigen, reicht der Generatorausdruck aus, ist klar und erfordert nicht die Erfindung einer anderen Klammer oder Klammer.
Martijn Pieters
16
Die Antwort ist offensichtlich, weil Tupelsyntax und Klammern nicht eindeutig sind
Charles Salvia
19
Der Unterschied zwischen der Verwendung eines Verständnisses und der Verwendung eines Konstruktors + Generators ist mehr als subtil, wenn Sie Wert auf Leistung legen. Verständnis führt zu einer schnelleren Konstruktion im Vergleich zur Verwendung eines Generators, der an einen Konstruktor übergeben wird. Im letzteren Fall erstellen und führen Sie Funktionen aus, und Funktionen sind in Python teuer. [thing for thing in things]erstellt eine Liste viel schneller als list(thing for thing in things). Ein Tupelverständnis wäre nicht nutzlos; tuple(thing for thing in things)hat Latenzprobleme und tuple([thing for thing in things])könnte Speicherprobleme haben.
Justin Turner Arthur
9
@MartijnPieters, können Sie möglicherweise umformulieren A list or set or dict comprehension is just syntactic sugar to use a generator expression? Es verursacht Verwirrung, wenn Menschen diese als gleichwertige Mittel zum Zweck betrachten. Es ist technisch gesehen kein syntaktischer Zucker, da die Prozesse tatsächlich unterschiedlich sind, selbst wenn das Endprodukt das gleiche ist.
Jpp
77

Raymond Hettinger (einer der Python-Kernentwickler) hatte kürzlich in einem Tweet Folgendes über Tupel zu sagen :

# Python-Tipp: Im Allgemeinen dienen Listen zum Schleifen. Tupel für Strukturen. Listen sind homogen; Tupel heterogen. Listen für variable Länge.

Dies (für mich) unterstützt die Idee, dass es eine Liste sein sollte, wenn die Elemente in einer Sequenz so verwandt sind, dass sie von einem Generator generiert werden können. Obwohl ein Tupel iterierbar ist und einfach wie eine unveränderliche Liste erscheint, ist es wirklich das Python-Äquivalent einer C-Struktur:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

wird in Python

x = (3, 'g', 5.9)
chepner
quelle
26
Die Unveränderlichkeitseigenschaft kann jedoch wichtig sein und ist oft ein guter Grund, ein Tupel zu verwenden, wenn Sie normalerweise eine Liste verwenden würden. Wenn Sie beispielsweise eine Liste mit 5 Zahlen haben, die Sie als Schlüssel für ein Diktat verwenden möchten, ist Tupel der richtige Weg.
Pavon
Das ist ein schöner Tipp von Raymond Hettinger. Ich würde immer noch sagen, dass es einen Anwendungsfall für die Verwendung des Tupelkonstruktors mit einem Generator gibt, z. B. das Entpacken einer anderen Struktur, möglicherweise einer größeren, in eine kleinere, indem Sie die Attribute durchlaufen, die Sie in einen Tupeldatensatz konvertieren möchten.
Dave
2
@dave Sie können wahrscheinlich nur operator.itemgetterin diesem Fall verwenden.
Chepper
@chepner, ich verstehe. Das kommt dem, was ich meine, ziemlich nahe. Es gibt einen Callable zurück. Wenn ich es also nur einmal tun muss, sehe ich keinen großen Gewinn, wenn ich es nur direkt benutze tuple(obj[item] for item in items). In meinem Fall habe ich dies in ein Listenverständnis eingebettet, um eine Liste von Tupeldatensätzen zu erstellen. Wenn ich dies im gesamten Code wiederholt tun muss, sieht itemgetter großartig aus. Vielleicht wäre Itemgetter so oder so idiomatischer?
Dave
Ich sehe die Beziehung zwischen Frozenset und Set analog zu der von Tupel und Liste. Es geht weniger um Heterogenität als vielmehr um Unveränderlichkeit - Frozensets und Tupel können aufgrund ihrer Veränderlichkeit Schlüssel zu Wörterbüchern, Listen und Sets sein.
Polyglot
56

Seit Python 3.5 können Sie auch die *Syntax zum Entpacken von Splat verwenden , um einen Generatorausdruck zu entpacken:

*(x for x in range(10)),
czheo
quelle
2
Das ist großartig (und es funktioniert), aber ich kann nirgendwo finden, wo es dokumentiert ist! Hast du einen Link?
Felixphew
8
Hinweis: Als Implementierungsdetail ist dies im Grunde dasselbe wie das Ausführen tuple(list(x for x in range(10)))( die Codepfade sind identisch , wobei beide ein listerstellen, mit dem einzigen Unterschied, dass der letzte Schritt darin besteht, ein tupleaus dem zu erstellen listund das listWann tuplewegzulassen, wenn eine Ausgabe erfolgt ist erforderlich). Bedeutet, dass Sie ein Paar von Provisorien nicht wirklich vermeiden.
ShadowRanger
4
Um den Kommentar von @ShadowRanger zu erweitern, hier eine Frage, in der gezeigt wird, dass die Literal-Syntax von splat + tuple tatsächlich etwas langsamer ist als das Übergeben eines Generatorausdrucks an den Tupelkonstruktor.
Lucubrator
Ich versuche dies in Python 3.7.3 und *(x for x in range(10))funktioniert nicht. Ich verstehe SyntaxError: can't use starred expression here. Funktioniert jedoch tuple(x for x in range(10)).
Ryan H.
4
@ RyanH. Sie müssen am Ende ein Komma setzen.
Czheo
27

Wie in einem anderen Poster macmerwähnt, ist der schnellste Weg, ein Tupel aus einem Generator zu erstellen, der tuple([generator]).


Leistungsvergleich

  • Listenverständnis:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tupel aus Listenverständnis:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tupel vom Generator:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Tupel vom Auspacken:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Meine Version von Python :

$ python3 --version
Python 3.6.3

Sie sollten daher immer ein Tupel aus einem Listenverständnis erstellen, es sei denn, die Leistung spielt keine Rolle.

Tom
quelle
10
Hinweis: Für tuplelistcomp ist eine maximale Speichernutzung erforderlich, die auf der kombinierten Größe von final tupleund basiert list. tupleWenn ein Genexpr langsamer ist, bedeutet dies, dass Sie nur für das Finale bezahlen tuple, nicht für das Temporäre list(der Genexpr selbst belegt ungefähr einen festen Speicher). Normalerweise nicht sinnvoll, aber es kann wichtig sein, wenn die beteiligten Größen sehr groß sind.
ShadowRanger
25

Zum Verständnis werden Elemente wiederholt oder iteriert und einem Container zugewiesen. Ein Tupel kann keine Zuweisungen empfangen.

Sobald ein Tupel erstellt wurde, kann es nicht mehr angehängt, erweitert oder zugewiesen werden. Die einzige Möglichkeit, ein Tupel zu ändern, besteht darin, dass eines seiner Objekte selbst zugewiesen werden kann (ein Nicht-Tupel-Container). Weil das Tupel nur einen Verweis auf diese Art von Objekt enthält.

Außerdem hat ein Tupel einen eigenen Konstruktor, tuple()den Sie jedem Iterator geben können. Um ein Tupel zu erstellen, können Sie Folgendes tun:

tuple(i for i in (1,2,3))
Inbar Rose
quelle
9
In gewisser Hinsicht stimme ich zu (dass es nicht notwendig ist, weil eine Liste ausreicht), aber in anderer Hinsicht bin ich anderer Meinung (weil die Argumentation unveränderlich ist). In mancher Hinsicht ist es sinnvoller, unveränderliche Objekte zu verstehen. wer tut das lst = [x for x in ...]; x.append()?
mgilson
@mgilson Ich bin nicht sicher, wie das mit dem zusammenhängt, was ich gesagt habe?
Inbar Rose
2
@mgilson Wenn ein Tupel unveränderlich ist, bedeutet dies, dass die zugrunde liegende Implementierung kein Tupel "generieren" kann ("Generierung" bedeutet, dass jeweils ein Teil erstellt wird). Unveränderlich bedeutet, dass Sie das mit 4 Teilen nicht bauen können, indem Sie das mit 3 Teilen ändern. Stattdessen implementieren Sie die "Generierung" von Tupeln, indem Sie eine Liste erstellen, die für die Generierung vorgesehen ist. Erstellen Sie dann das Tupel als letzten Schritt und verwerfen Sie die Liste. Die Sprache spiegelt diese Realität wider. Stellen Sie sich Tupel als C-Strukturen vor.
Scott
2
obwohl es vernünftig wäre, wenn der syntaktische Zucker von Verständnis für Tupel funktioniert, da Sie das Tupel erst verwenden können, wenn das Verständnis zurückgegeben wird. Tatsächlich verhält es sich nicht wie veränderlich, sondern ein Tupelverständnis könnte sich ähnlich wie das Anhängen von Zeichenfolgen verhalten.
Uchuugaka
12

Ich gehe davon aus, dass ihnen die Klammern ausgegangen sind und sie nicht der Meinung waren, dass dies nützlich genug wäre, um das Hinzufügen einer "hässlichen" Syntax zu rechtfertigen ...

mgilson
quelle
1
Winkelklammern unbenutzt.
Uchuugaka
@uchuugaka - Nicht vollständig. Sie werden für Vergleichsoperatoren verwendet. Es könnte wahrscheinlich immer noch ohne Mehrdeutigkeit gemacht werden, aber vielleicht nicht die Mühe wert ...
mgilson
3
@uchuugaka Bemerkenswert, dass {*()}, obwohl hässlich, als leeres Set-Literal funktioniert!
MI Wright
1
Pfui. Aus ästhetischer Sicht denke ich, dass ich parteiisch bin set():)
mgilson
1
@QuantumMechanic: Ja, das ist der Punkt; Die Entpackungsverallgemeinerungen ermöglichten das leere "Set-Literal". Beachten Sie, dass dies {*[]}den anderen Optionen strikt unterlegen ist. Die leere Zeichenfolge und die leere Zeichenfolge tuplesind unveränderlich und Singletons. Daher wird keine temporäre Zeichenfolge benötigt, um die leere Zeichenfolge zu erstellen set. Im Gegensatz dazu ist das Leerzeichen listkein Singleton. Sie müssen es also tatsächlich konstruieren, zum Erstellen verwenden setund dann zerstören, wobei Sie den trivialen Leistungsvorteil verlieren, den der einäugige Affenoperator bietet.
ShadowRanger
8

Tupel können nicht effizient wie eine Liste angehängt werden.

Ein Tupelverständnis müsste also eine Liste intern verwenden und dann in ein Tupel konvertieren.

Das wäre das gleiche wie das, was Sie jetzt tun: Tupel ([Verständnis])

macm
quelle
3

Klammern erzeugen kein Tupel. aka one = (two) ist kein Tupel. Der einzige Weg ist entweder eins = (zwei,) oder eins = Tupel (zwei). Eine Lösung ist also:

tuple(i for i in myothertupleorlistordict) 
ilias iliadis
quelle
nett. es ist fast das gleiche.
Uchuugaka
-1

Ich glaube, es ist nur der Klarheit halber, wir wollen die Sprache nicht mit zu vielen verschiedenen Symbolen überladen. Auch ein tupleVerständnis ist niemals notwendig , eine Liste kann stattdessen nur mit vernachlässigbaren Geschwindigkeitsunterschieden verwendet werden, im Gegensatz zu einem Diktatverständnis im Gegensatz zu einem Listenverständnis.

Jamylak
quelle
-2

Wir können Tupel aus einem Listenverständnis generieren. Der folgende fügt zwei Zahlen nacheinander zu einem Tupel hinzu und gibt eine Liste mit den Zahlen 0-9 an.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Rohit Malgaonkar
quelle