Python Language
Mutevole vs Immutabile (e Lavabile) in Python
Ricerca…
Mutevole vs Immutabile
Ci sono due tipi di tipi in Python. Tipi immutabili e tipi mutevoli.
immutabili
Un oggetto di un tipo immutabile non può essere cambiato. Qualsiasi tentativo di modificare l'oggetto comporterà la creazione di una copia.
Questa categoria include: interi, float, complessi, stringhe, byte, tuple, intervalli e frozenset.
Per evidenziare questa proprietà, giochiamo con l' id
integrato. Questa funzione restituisce l'identificativo univoco dell'oggetto passato come parametro. Se l'id è lo stesso, questo è lo stesso oggetto. Se cambia, allora questo è un altro oggetto. (Alcuni dicono che questo è in realtà l'indirizzo di memoria dell'oggetto, ma attenzione, sono dal lato oscuro della forza ...)
>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328
Ok, 1 non è 3 ... Ultime notizie ... Forse no. Tuttavia, questo comportamento viene spesso dimenticato quando si tratta di tipi più complessi, in particolare stringhe.
>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'
Aha! Vedere? Possiamo modificarlo!
>>> id(stack)
140128123911472
No. Mentre sembra che possiamo cambiare la stringa nominata dallo stack
variabile, ciò che effettivamente facciamo è creare un nuovo oggetto per contenere il risultato della concatenazione. Siamo ingannati perché nel processo, il vecchio oggetto non va da nessuna parte, quindi viene distrutto. In un'altra situazione, sarebbe stato più ovvio:
>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480
In questo caso è chiaro che se vogliamo mantenere la prima stringa, abbiamo bisogno di una copia. Ma è così ovvio per altri tipi?
Esercizio
Ora, sapendo come funzionano i tipi immutabili, cosa diresti con il codice sottostante? È saggio?
s = ""
for i in range(1, 1000):
s += str(i)
s += ","
Mutables
Un oggetto di un tipo mutevole può essere cambiato, ed è cambiato in-situ . Non vengono eseguite copie implicite.
Questa categoria include: elenchi, dizionari, filtri e set.
Continuiamo a giocare con la nostra piccola funzione di id
.
>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288
(Come nota a margine, uso i byte che contengono i dati ascii per chiarire il mio punto, ma ricorda che i byte non sono progettati per contenere dati testuali.
Cosa abbiamo? Creiamo un bytearray, lo modifichiamo e usando l' id
, possiamo assicurarci che questo sia lo stesso oggetto, modificato. Non una copia di esso.
Ovviamente, se un oggetto viene modificato spesso, un tipo mutevole svolge un lavoro molto migliore di un tipo immutabile. Sfortunatamente, la realtà di questa proprietà è spesso dimenticata quando fa più male.
>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')
Va bene...
>>> b
bytearray(b'StackOverflow rocks!')
Waiiit un secondo ...
>>> id(c) == id(b)
True
Infatti. c
non è una copia di b
. c
è b
.
Esercizio
Ora è meglio capire quale effetto collaterale è implicito da un tipo mutevole, puoi spiegare cosa sta andando storto in questo esempio?
>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...
Mutevole e immutabile come argomenti
Uno dei principali casi d'uso in cui uno sviluppatore deve prendere in considerazione la mutabilità è quando si passano gli argomenti a una funzione. Questo è molto importante, perché questo determinerà la possibilità per la funzione di modificare oggetti che non appartengono al suo scopo, o in altre parole se la funzione ha effetti collaterali. Questo è anche importante per capire dove deve essere reso disponibile il risultato di una funzione.
>>> def list_add3(lin):
lin += [3]
return lin
>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]
Qui, l'errore è pensare che lin
, come parametro per la funzione, possa essere modificato localmente. Invece, lin
e a
riferimento lo stesso oggetto. Poiché questo oggetto è mutabile, la modifica viene eseguita sul posto, il che significa che l'oggetto a cui fa riferimento sia lin
che a
viene modificato. lin
non ha davvero bisogno di essere restituito, perché abbiamo già un riferimento a questo oggetto sotto forma di a
. a
e b
terminano con riferimento allo stesso oggetto.
Questo non è lo stesso per le tuple.
>>> def tuple_add3(tin):
tin += (3,)
return tin
>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)
All'inizio della funzione, tin
e a
riferimento allo stesso oggetto. Ma questo è un oggetto immutabile. Così, quando la funzione tenta di modificarlo, tin
ricevere un nuovo oggetto con la modifica, mentre a
mantiene un riferimento all'oggetto originale. In questo caso, restituire tin
è obbligatorio, altrimenti il nuovo oggetto andrebbe perso.
Esercizio
>>> def yoda(prologue, sentence):
sentence.reverse()
prologue += " ".join(sentence)
return prologue
>>> focused = ["You must", "stay focused"]
>>> saying = "Yoda said: "
>>> yoda_sentence = yoda(saying, focused)
Nota: il reverse
funziona sul posto.
Cosa ne pensi di questa funzione? Ha effetti collaterali? Il reso è necessario? Dopo la chiamata, qual è il valore di saying
? Di focused
? Cosa succede se la funzione viene richiamata con gli stessi parametri?