Perché non c'è comprensione della tupla in Python?


340

Come tutti sappiamo, c'è una comprensione dell'elenco, come

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

e c'è comprensione del dizionario, come

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

ma

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

finirà in un generatore, non in una tuplecomprensione. Perché?

La mia ipotesi è che a tuplesia immutabile, ma questa non sembra essere la risposta.


16
C'è anche una comprensione prestabilita - che assomiglia molto a una comprensione
dettata

3
C'è un errore di sintassi nel tuo codice: {i:j for i,j in {1:'a', 2:'b'}}dovrebbe essere{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose

@InbarRose Grazie per averlo sottolineato -.-
Shady Xu

Solo per il bene dei posteri, c'è una discussione al riguardo nella Python Chat
Inbar Rose,

Risposte:


471

È possibile utilizzare un'espressione del generatore:

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

ma le parentesi erano già state prese per ... espressioni del generatore.


15
Con questo ragionamento, potremmo dire un elenco-di comprensione non è necessario troppo: list(i for i in (1,2,3)). Penso davvero che sia semplicemente perché non esiste una sintassi
chiara

79
Una comprensione di un elenco o di un set o di una dettatura è solo zucchero sintattico per usare un'espressione di generatore che genera un tipo specifico. list(i for i in (1, 2, 3))è un'espressione del generatore che genera un elenco, set(i for i in (1, 2, 3))genera un set. Ciò significa che la sintassi di comprensione non è necessaria? Forse no, ma è terribilmente utile. Per i rari casi invece è necessaria una tupla, l'espressione del generatore lo farà, è chiara e non richiede l'invenzione di un'altra parentesi graffa o parentesi.
Martijn Pieters

16
La risposta è ovviamente perché la sintassi e la parentesi delle tuple sono ambigue
Charles Salvia,

19
La differenza tra usare una comprensione e usare un costruttore + generatore è più che sottile se ti preoccupi delle prestazioni. Le comprensioni si traducono in una costruzione più rapida rispetto all'utilizzo di un generatore passato a un costruttore. In quest'ultimo caso si stanno creando ed eseguendo funzioni e funzioni costose in Python. [thing for thing in things]costruisce un elenco molto più velocemente di list(thing for thing in things). Una comprensione tupla non sarebbe inutile; tuple(thing for thing in things)ha problemi di latenza e tuple([thing for thing in things])potrebbe avere problemi di memoria.
Justin Turner Arthur,

9
@MartijnPieters, puoi potenzialmente riformulare A list or set or dict comprehension is just syntactic sugar to use a generator expression? Sta causando confusione da parte di persone che vedono questi come mezzi equivalenti per un fine. Non è zucchero tecnicamente sintattico in quanto i processi sono in realtà diversi, anche se il prodotto finale è lo stesso.
jpp,

77

Raymond Hettinger (uno degli sviluppatori core di Python) ha detto questo sulle tuple in un recente tweet :

#python tip: in genere, gli elenchi sono per il looping; tuple per strutture. Le liste sono omogenee; tuple eterogenee. Elenchi per lunghezza variabile.

Questo (per me) supporta l'idea che se gli elementi in una sequenza sono abbastanza correlati da essere generati da un generatore, allora dovrebbe essere un elenco. Sebbene una tupla sia iterabile e sembri semplicemente un elenco immutabile, in realtà è l'equivalente Python di una struttura C:

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

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

diventa in Python

x = (3, 'g', 5.9)

26
La proprietà dell'immutibilità può essere importante e spesso una buona ragione per usare una tupla quando normalmente usereste un elenco. Ad esempio, se si dispone di un elenco di 5 numeri che si desidera utilizzare come chiave per un dict, la tupla è la strada da percorrere.
padiglione

Questo è un bel consiglio di Raymond Hettinger. Direi ancora che esiste un caso d'uso per usare il costruttore di tuple con un generatore, come disimballare un'altra struttura, forse più grande, in una più piccola iterando sugli attr che ti interessano convertire in un record di tupla.
dave,

2
@dave Probabilmente puoi semplicemente usarlo operator.itemgetterin quel caso.
Chepner

@chepner, capisco. Questo è abbastanza vicino a ciò che intendo. Restituisce un callable, quindi se ho bisogno di farlo solo una volta non vedo molto di una vittoria rispetto al solo utilizzo tuple(obj[item] for item in items)diretto. Nel mio caso lo stavo incorporando in una comprensione di elenco per creare un elenco di record di tuple. Se devo farlo ripetutamente in tutto il codice, Itemgetter sembra fantastico. Forse Itemgetter sarebbe più idiomatico in entrambi i casi?
Dave,

Vedo il rapporto tra frozenset e impostato analogo a quello di tupla e lista. Riguarda meno l'eterogeneità e più l'immutabilità: frozenset e tuple possono essere le chiavi di dizionari, elenchi e set non possono a causa della loro mutabilità.
poliglotta

56

Da Python 3.5 , puoi anche usare la *sintassi di decompressione splat per decomprimere un'espressione del generatore:

*(x for x in range(10)),

2
Questo è fantastico (e funziona), ma non riesco a trovare da nessuna parte è documentato! Avete un link?
Felixphew,

8
Nota: come dettaglio di implementazione, questo è fondamentalmente lo stesso che fare tuple(list(x for x in range(10)))( i percorsi del codice sono identici , con entrambi listche creano un, con l'unica differenza che il passaggio finale è quello di creare un tupleda liste buttare via listquando un tupleoutput è obbligatorio). Significa che in realtà non eviti un paio di provvisori.
ShadowRanger

4
Per espandere il commento di @ShadowRanger, ecco una domanda in cui mostrano che la sintassi letterale splat + tuple è in realtà un po 'più lenta rispetto al passaggio di un'espressione del generatore al costruttore di tuple.
Lucubrator,

Sto provando questo in Python 3.7.3 e *(x for x in range(10))non funziona. Ho capito SyntaxError: can't use starred expression here. Comunque tuple(x for x in range(10))funziona.
Ryan H.

4
@RyanH. è necessario inserire una virgola alla fine.
czheo,

27

Come macmmenzionato da un altro poster , il modo più veloce per creare una tupla da un generatore è tuple([generator]).


Confronto delle prestazioni

  • Comprensione elenco:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tupla dalla comprensione dell'elenco:

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

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

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

La mia versione di Python :

$ python3 --version
Python 3.6.3

Quindi dovresti sempre creare una tupla dalla comprensione di un elenco a meno che le prestazioni non siano un problema.


10
Nota: tupledi listcomp richiede un picco di utilizzo della memoria in base alla dimensione combinata del finale tuplee list. tupledi un genexpr, sebbene più lento, significa che paghi solo per il finale tuple, non temporaneo list(il genexpr stesso occupa una memoria approssimativamente fissa). Di solito non è significativo, ma può essere importante quando le dimensioni sono enormi.
ShadowRanger

25

La comprensione funziona eseguendo il ciclo o ripetendo gli elementi e assegnandoli in un contenitore, una Tupla non è in grado di ricevere assegnazioni.

Una volta creata, una Tupla non può essere aggiunta, estesa o assegnata. L'unico modo per modificare una Tupla è se uno dei suoi oggetti può essere assegnato (è un contenitore non tupla). Perché la Tupla contiene solo un riferimento a quel tipo di oggetto.

Inoltre - una tupla ha un proprio costruttore tuple()che puoi dare a qualsiasi iteratore. Ciò significa che per creare una tupla, potresti fare:

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

9
In un certo senso sono d'accordo (sul fatto che non è necessario perché un elenco lo farà), ma in altri modi non sono d'accordo (sul fatto che il ragionamento è perché è immutabile). In un certo senso, ha più senso comprendere gli oggetti immutabili. chi lo fa lst = [x for x in ...]; x.append()?
mgilson,

@mgilson Non sono sicuro di come si riferisca a ciò che ho detto?
Inbar Rose,

2
@mgilson se una tupla è immutabile, ciò significa che l'implementazione sottostante non può "generare" una tupla ("generazione" che implica la costruzione di un pezzo alla volta). immutabile significa che non puoi costruire quello con 4 pezzi alterando quello con 3 pezzi. invece, si implementa la "generazione" della tupla costruendo un elenco, qualcosa progettato per la generazione, quindi si crea la tupla come ultimo passaggio e si elimina l'elenco. Il linguaggio riflette questa realtà. Pensa alle tuple come a strutture a C.
Scott,

2
sebbene sarebbe ragionevole che lo zucchero sintattico delle comprensioni funzioni per le tuple, poiché non puoi usare la tupla fino a quando non viene restituita la comprensione. In effetti non si comporta come mutabile, piuttosto una comprensione tupla potrebbe comportarsi in modo simile all'aggiunta di stringhe.
Uchuugaka,

12

La mia ipotesi migliore è che abbiano finito le parentesi e non pensassero che sarebbe stato utile warrent aggiungendo una sintassi "brutta" ...


1
Parentesi angolari non utilizzate.
Uchuugaka,

@uchuugaka - Non del tutto. Sono utilizzati per operatori di confronto. Probabilmente potrebbe ancora essere fatto senza ambiguità, ma forse non vale la pena ...
mgilson,

3
@uchuugaka Vale la pena notare che {*()}, sebbene brutto, funziona come un set vuoto letterale!
MI Wright,

1
Ugh. Da un punto di vista estetico, penso di essere parziale a set():)
mgilson

1
@QuantumMechanic: Sì, questo è il punto; le generalizzazioni spacchettate hanno reso possibile il "set letterale" vuoto. Si noti che {*[]}è strettamente inferiore alle altre opzioni; la stringa vuota e vuota tuple, essendo immutabili, sono singoli, quindi non è necessario alcun temporaneo per costruire il vuoto set. Al contrario, il vuoto listnon è un singleton, quindi in realtà devi costruirlo, usarlo per costruire il set, quindi distruggerlo, perdendo qualsiasi vantaggio di prestazioni banale fornito dall'operatore scimmia con un occhio solo.
ShadowRanger,

8

Le tuple non possono essere aggiunte in modo efficiente come un elenco.

Quindi una comprensione di tupla dovrebbe usare un elenco internamente e quindi convertirsi in tupla.

Sarebbe lo stesso di quello che fai ora: tuple ([comprensione])


3

Le parentesi non creano una tupla. aka one = (two) non è una tupla. L'unico modo per aggirare è uno = (due) o uno = tupla (due). Quindi una soluzione è:

tuple(i for i in myothertupleorlistordict) 

simpatico. è quasi lo stesso.
Uchuugaka,

-1

Credo che sia semplicemente per chiarezza, non vogliamo ingombrare la lingua con troppi simboli diversi. Anche una tuplecomprensione non è mai necessaria , un elenco può essere semplicemente utilizzato invece con differenze di velocità trascurabili, a differenza di una comprensione dettata rispetto a una comprensione di elenco.


-2

Possiamo generare tuple dalla comprensione di un elenco. Quello seguente aggiunge due numeri in sequenza in una tupla e fornisce un elenco dai numeri 0-9.

>>> 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)]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.