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?



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow