Perché abbiamo bisogno di tuple in Python (o in qualsiasi tipo di dati immutabile)?


140

Ho letto diversi tutorial su Python (Dive Into Python, per esempio) e il riferimento linguistico su Python.org - Non vedo perché la lingua abbia bisogno di tuple.

Le tuple non hanno metodi rispetto a un elenco o set, e se devo convertire una tupla in un set o elenco per poterli ordinare, a che serve usare una tupla?

Immutabilità?

Perché a qualcuno importa se una variabile vive in un posto diverso nella memoria rispetto a quando è stata originariamente allocata? Tutta questa faccenda dell'immutabilità in Python sembra essere troppo enfatizzata.

In C / C ++ se allocare un puntatore e punto un po 'di memoria valida, non mi interessa dove si trova l'indirizzo fintanto che non è nullo prima di usarlo.

Ogni volta che faccio riferimento a quella variabile, non ho bisogno di sapere se il puntatore sta ancora puntando all'indirizzo originale o meno. Controllo solo null e lo uso (o no).

In Python, quando allocare una stringa (o tupla) la assegno a x, quindi modifico la stringa, perché mi interessa se è l'oggetto originale? Finché la variabile punta ai miei dati, è tutto ciò che conta.

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

x fa ancora riferimento ai dati che desidero, perché qualcuno deve preoccuparsi se il suo ID è uguale o diverso?


12
stai prestando attenzione all'aspetto sbagliato della mutabilità: "se l'id è uguale o diverso" è solo un effetto collaterale; "se i dati indicati da altri riferimenti che in precedenza puntavano allo stesso oggetto ora riflettono gli aggiornamenti" è fondamentale.
Charles Duffy,

Risposte:


124
  1. oggetti immutabili possono consentire una sostanziale ottimizzazione; questo è presumibilmente il motivo per cui le stringhe sono anche immutabili in Java, sviluppate abbastanza separatamente ma all'incirca nello stesso momento di Python, e praticamente tutto è immutabile in linguaggi veramente funzionali.

  2. in Python in particolare, solo gli immutabili possono essere hash (e, quindi, membri di insiemi o chiavi nei dizionari). Ancora una volta, questa ottimizzazione economica, ma molto più di un semplice "sostanziale" (la progettazione di tabelle hash decenti che memorizzano oggetti completamente mutabili è un incubo - o prendi copie di tutto non appena lo hai, o l'incubo di verificare se l'hash dell'oggetto è cambiato dall'ultima volta che hai preso un riferimento ad esso alza la sua brutta testa).

Esempio di problema di ottimizzazione:

$ 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

11
@musicfreak, guarda la modifica che ho appena fatto in cui la creazione di una tupla è 7,6 volte più veloce rispetto alla creazione dell'elenco equivalente - ora non puoi più dire di non aver "mai visto una differenza evidente", a meno che la tua definizione di "notabile" "è davvero singolare ...
Alex Martelli,

11
@musicfreak Penso che tu stia abusando "l'ottimizzazione prematura è la radice di tutti i mali". C'è un'enorme differenza tra l'ottimizzazione prematura in un'applicazione (ad esempio, dicendo "le tuple sono più veloci degli elenchi, quindi utilizzeremo solo le tuple in tutte le app!") E fare benchmark. Il benchmark di Alex è approfondito e sapere che costruire una tupla è più veloce della creazione di un elenco potrebbe aiutarci nelle future operazioni di ottimizzazione (quando è veramente necessario).
Virgil Dupras,

5
@Alex, "costruire" una tupla è molto più veloce di "costruire un elenco" o stiamo vedendo il risultato del runtime di Python che memorizza nella cache la tupla? Mi sembra quest'ultimo.
Trittico

6
@ACoolie, è totalmente dominato dalle randomchiamate (prova a fare proprio questo, vedrai!), Quindi non molto significativo. Prova python -mtimeit -s "x=23" "[x,x]"e vedrai uno speedup più significativo di 2-3 volte per la costruzione della tupla rispetto alla costruzione dell'elenco.
Alex Martelli,

9
per chiunque si chiedesse: siamo riusciti a raderci più di un'ora di elaborazione dei dati passando da liste a tuple.
Mark Ribau,

42

Nessuna delle risposte sopra sottolinea il vero problema delle tuple contro le liste, che molte novità di Python sembrano non comprendere appieno.

Tuple e liste hanno scopi diversi. Gli elenchi memorizzano dati omogenei. Puoi e dovresti avere un elenco come questo:

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

Il motivo che è un uso corretto degli elenchi è perché si tratta di tutti i tipi di dati omogenei, in particolare i nomi delle persone. Ma prendi un elenco come questo:

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

Tale elenco è il nome completo di una persona e la sua età. Questo non è un tipo di dati. Il modo corretto di archiviare tali informazioni è in una tupla o in un oggetto. Diciamo che ne abbiamo alcuni:

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

L'immutabilità e la mutabilità di Tuples and Lists non è la differenza principale. Un elenco è un elenco dello stesso tipo di elementi: file, nomi, oggetti. Le tuple sono un raggruppamento di diversi tipi di oggetti. Hanno usi diversi e molti programmatori di Python abusano di elenchi per cui sono intese le tuple.

Per favore, no.


Modificare:

Penso che questo post sul blog spieghi perché lo penso meglio di me: http://news.e-scribe.com/397


13
Penso che tu abbia una visione che non è almeno d'accordo con me, non conosco gli altri.
Stefano Borini,

13
Sono anche fortemente in disaccordo con questa risposta. L'omogeneità dei dati non ha assolutamente nulla a che fare con la necessità di utilizzare un elenco o una tupla. Niente in Python suggerisce questa distinzione.
Glenn Maynard,

14
Anche Guido ha sottolineato questo punto qualche anno fa. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy

11
Anche se Guido (il designer di Python) intendeva che le liste fossero usate per dati omogenei e le tuple per eterogenee, il fatto è che il linguaggio non lo applica. Pertanto, penso che questa interpretazione sia più un problema di stile che altro. Accade così che nei casi d'uso tipici di molte persone, gli elenchi tendono ad essere simili a array e le tuple tendono ad essere record. Ma ciò non dovrebbe impedire alle persone di utilizzare elenchi per dati eterogenei se si adatta meglio al loro problema. Come dice lo Zen di Python: la praticità batte la purezza.
John Y,

9
@Glenn, sostanzialmente ti sbagli. Uno degli usi principali delle tuple è come un tipo di dati composito per l'archiviazione di più parti di dati correlate. Il fatto di poter iterare su una tupla ed eseguire molte delle stesse operazioni non cambia questo. (Come riferimento consideriamo che le tuple in molte altre lingue non hanno le stesse caratteristiche iterabili delle loro controparti di elenco)
HS.

22

se devo convertire una tupla in un set o in un elenco per poterli ordinare, a che serve usare una tupla?

In questo caso particolare, probabilmente non ha senso. Questo non è un problema, perché non è uno dei casi in cui potresti prendere in considerazione l'uso di una tupla.

Come fai notare, le tuple sono immutabili. Le ragioni per avere tipi immutabili si applicano alle tuple:

  • efficienza di copia: anziché copiare un oggetto immutabile, puoi alias (associare una variabile a un riferimento)
  • efficienza di confronto: quando si utilizza la copia per riferimento, è possibile confrontare due variabili confrontando la posizione anziché il contenuto
  • interning: è necessario archiviare al massimo una copia di qualsiasi valore immutabile
  • non è necessario sincronizzare l'accesso agli oggetti immutabili nel codice simultaneo
  • const correttezza: alcuni valori non dovrebbero essere autorizzati a cambiare. Questo (per me) è il motivo principale per i tipi immutabili.

Si noti che una particolare implementazione di Python potrebbe non utilizzare tutte le funzionalità di cui sopra.

Le chiavi del dizionario devono essere immutabili, altrimenti la modifica delle proprietà di un oggetto chiave può invalidare gli invarianti della struttura di dati sottostante. Le tuple possono quindi essere potenzialmente utilizzate come chiavi. Questa è una conseguenza della costante correttezza.

Vedi anche " Presentazione delle tuple ", da Dive Into Python .


2
id ((1,2,3)) == id ((1,2,3)) è falso. Non è possibile confrontare le tuple semplicemente confrontando la posizione, perché non esiste alcuna garanzia che siano state copiate per riferimento.
Glenn Maynard,

@Glenn: nota l'osservazione qualificante "quando stai usando la copia per riferimento". Mentre il programmatore può creare la propria implementazione, la copia per riferimento per le tuple è in gran parte una questione di interprete / compilatore. Mi riferivo principalmente a come ==viene implementato a livello di piattaforma.
uscita

1
@Glenn: nota anche che la copia per riferimento non si applica alle tuple in (1,2,3) == (1,2,3). È più una questione di internamento.
uscita

Come ho detto piuttosto chiaramente, non vi è alcuna garanzia che siano stati copiati per riferimento . Le tuple non sono internate in Python; questo è un concetto di stringa.
Glenn Maynard,

Come ho detto molto chiaramente: non sto parlando del programmatore che confronta le tuple confrontando la posizione. Sto parlando della possibilità che la piattaforma può, che può garantire la copia per riferimento. Inoltre, l'internato può essere applicato a qualsiasi tipo immutabile, non solo alle stringhe. L'implementazione principale di Python potrebbe non internare tipi immutabili, ma il fatto che Python abbia tipi immutabili rende l'internato un'opzione.
uscita

15

A volte ci piace usare gli oggetti come chiavi del dizionario

Per quello che vale, le tuple di recente (2.6+) sono cresciute index()e i count()metodi


5
+1: un elenco modificabile (o un set modificabile o un dizionario modificabile) come chiave del dizionario non può funzionare. Quindi abbiamo bisogno di liste immutabili ("tuple"), set congelati e ... beh ... un dizionario congelato, suppongo.
S.Lott

9

Ho sempre trovato che avere due tipi completamente separati per la stessa struttura di dati di base (array) fosse una progettazione scomoda, ma non un vero problema nella pratica. (Ogni lingua ha le sue verruche, incluso Python, ma questa non è importante.)

Perché a qualcuno importa se una variabile vive in un posto diverso nella memoria rispetto a quando è stata originariamente allocata? Tutta questa faccenda dell'immutabilità in Python sembra essere troppo enfatizzata.

Queste sono cose diverse. La mutabilità non è correlata al luogo in cui è memorizzata; significa che le cose a cui punta non possono cambiare.

Gli oggetti Python non possono cambiare posizione dopo essere stati creati, modificabili o meno. (Più precisamente, il valore di id () non può cambiare - stessa cosa, in pratica.) La memoria interna degli oggetti mutabili può cambiare, ma si tratta di un dettaglio di implementazione nascosto.

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

Questo non sta modificando ("mutando") la variabile; sta creando una nuova variabile con lo stesso nome e scartando quella vecchia. Confronta con un'operazione di mutazione:

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

Come altri hanno sottolineato, ciò consente di utilizzare le matrici come chiavi dei dizionari e altre strutture di dati che richiedono immutabilità.

Nota che le chiavi per i dizionari non devono essere completamente immutabili. Solo la parte utilizzata come chiave deve essere immutabile; per alcuni usi, questa è una distinzione importante. Ad esempio, potresti avere una classe che rappresenta un utente, che confronta l'uguaglianza e un hash con il nome utente univoco. È quindi possibile appendere altri dati mutabili sulla classe: "utente connesso", ecc. Poiché ciò non influisce sull'uguaglianza o sull'hash, è possibile e perfettamente valido utilizzarlo come chiave in un dizionario. Questo non è troppo comunemente necessario in Python; Lo sottolineo solo perché diverse persone hanno affermato che le chiavi devono essere "immutabili", il che è solo parzialmente corretto. Tuttavia, l'ho usato molte volte con mappe e set C ++.


>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Tu ' ho appena modificato un tipo di dati mutabile, quindi non ha senso in relazione alla domanda originale. x = 'hello "id (x) 12345 x =" addio "id (x) 65432 A chi importa se si tratta di un nuovo oggetto o meno. Fintanto che x punta ai dati che ho assegnato, è tutto ciò che conta.
pyNewGuy

4
Sei confuso ben oltre la mia capacità di aiutarti.
Glenn Maynard,

+1 per indicare la confusione nelle sotto-domande, che sembrano essere la principale fonte di difficoltà nel percepire il valore delle tuple.
uscita

1
Se potessi, un altro +1 per sottolineare che la vera rubrica per le chiavi è se l'oggetto è o meno hash ( docs.python.org/glossary.html#term-hashable ).
uscita

7

Come Gnibbler ha offerto in un commento, Guido ha espresso un'opinione non pienamente accettata / apprezzata: "gli elenchi sono per dati omogenei, le tuple sono per dati eterogenei". Naturalmente, molti degli oppositori hanno interpretato ciò nel senso che tutti gli elementi di un elenco dovrebbero essere dello stesso tipo.

Mi piace vederlo diversamente, non diversamente dagli altri anche in passato:

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

Nota che considero gli alist come omogenei, anche se type (alist [1])! = Type (alist [2]).

Se posso cambiare l'ordine degli elementi e non avrò problemi nel mio codice (a parte le ipotesi, ad esempio "dovrebbe essere ordinato"), allora dovrebbe essere usato un elenco. In caso contrario (come nella tupla bluesopra), allora dovrei usare una tupla.


Se potessi, voterei questa risposta 15 volte. Questo è esattamente quello che provo per le tuple.
Concedi a Paul il

6

Sono importanti poiché garantiscono al chiamante che l'oggetto che passano non sarà mutato. Se lo fai:

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

Il chiamante non ha alcuna garanzia del valore di a dopo la chiamata. Però,

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

Ora tu come chiamante o come lettore di questo codice sai che a è lo stesso. Potresti sempre per questo scenario creare una copia dell'elenco e passarlo, ma ora stai sprecando cicli invece di usare un costrutto linguistico che ha più senso semantico.


1
Questa è una proprietà molto secondaria delle tuple. Ci sono troppi casi in cui hai un oggetto mutabile che vuoi passare a una funzione e non averlo modificato, che si tratti di un elenco preesistente o di qualche altra classe. In Python non esiste semplicemente il concetto di "parametri const per riferimento" (es. Const foo e in C ++). Le tuple ti danno questo se ti risulta conveniente usare una tupla, ma se hai ricevuto un elenco dal tuo chiamante, lo convertirai davvero in una tupla prima di passarlo altrove?
Glenn Maynard,

Sono d'accordo con te su questo. Una tupla non è la stessa cosa dello schiaffo di una parola chiave const. Il mio punto è che l'immutabilità di una tupla ha un significato aggiunto per il lettore del codice. Data una situazione in cui entrambi funzionerebbero e la tua aspettativa è che non dovrebbe cambiare usando la tupla si aggiungerà quel significato in più per il lettore (garantendo anche questo)
Matthew Manela

a = [1,1,1] doWork (a) se dowork () è definito come def dowork (arg): arg = [0,0,0] la chiamata di dowork () in un elenco o tupla ha lo stesso risultato
pyNewGuy


1

La tua domanda (e i commenti di follow-up) si concentrano sul fatto che l'id () cambi durante un compito. Concentrarsi su questo effetto di follow-on della differenza tra la sostituzione di oggetti immutabili e la modifica di oggetti mutabili piuttosto che la differenza stessa non è forse l'approccio migliore.

Prima di continuare, assicurati che il comportamento dimostrato di seguito sia quello che ti aspetti da Python.

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

In questo caso, il contenuto di a2 è stato modificato, anche se solo a1 è stato assegnato un nuovo valore. Contrariamente a quanto segue:

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

In quest'ultimo caso, abbiamo sostituito l'intero elenco, anziché aggiornarne i contenuti. Con tipi immutabili come le tuple, questo è l'unico comportamento consentito.

Perché è importante? Diciamo che hai un dict:

>>> 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'}

Usando una tupla, il dizionario è sicuro dal fatto che le sue chiavi cambino "da sotto" ad elementi che hanno un valore diverso. Questo è fondamentale per consentire un'implementazione efficiente.


Come altri hanno sottolineato, l'immutabilità! = Hashability. Non tutte le tuple possono essere usate come chiavi del dizionario: {([1], [2]): 'valore'} non riesce perché gli elenchi mutabili nella tupla possono essere modificati, ma {((1), (2)): ' il valore '} è OK.
Ned Deily,

Ned, è vero, ma non sono sicuro che la distinzione sia fondamentale per la domanda che viene posta.
Charles Duffy,

@ K. Nicholas, la modifica che hai approvato qui ha cambiato il codice in modo da assegnare un numero intero, non una tupla, facendo fallire le successive operazioni sugli indici, quindi non avrebbero potuto testare che il nuovo la trascrizione era effettivamente possibile. Problema identificato correttamente, certo; soluzione non valida.
Charles Duffy,

@MichaelPuckettII, allo stesso modo, vedi sopra.
Charles Duffy,
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.