Skip to content

Perché la mutazione di una lista in una tupla solleva un'eccezione ma la muta comunque?

Dopo aver consultato esperti in materia, programmatori di diverse aree e professori, abbiamo trovato la risposta al dilemma e la condividiamo in questo post.

Soluzione:

La mia sensazione è che la riga x[0] += [3, 4] modifichi prima la lista stessa, così [1, 2] diventa [1, 2, 3, 4], poi tenta di aggiustare il contenuto della tupla, lanciando un errore TypeErrorma la tupla punta sempre alla stessa lista, quindi il suo contenuto (in termini di puntatori) non viene modificato, mentre l'oggetto puntato a è modificato.

Possiamo verificarlo in questo modo:

a_list = [1, 2, 3]
a_tuple = (a_list,)
print(a_tuple)
>>> ([1, 2, 3],)

a_list.append(4)
print(a_tuple)
>>> ([1, 2, 3, 4], )

Questo non lancia un errore e lo modifica sul posto, nonostante sia memorizzato in una tupla "immutabile".

Ci sono alcune cose che accadono qui.

+= non è sempre + e poi =.

+= e + possono avere implementazioni diverse, se necessario.

Date un'occhiata a questo esempio.

In [13]: class Foo: 
    ...:     def __init__(self, x=0): 
    ...:         self.x = x 
    ...:     def __add__(self, other): 
    ...:         print('+ operator used') 
    ...:         return Foo(self.x + other.x) 
    ...:     def __iadd__(self, other): 
    ...:         print('+= operator used') 
    ...:         self.x += other.x 
    ...:         return self 
    ...:     def __repr__(self): 
    ...:         return f'Foo(x={self.x})' 
    ...:                                                                        

In [14]: f1 = Foo(10)                                                           

In [15]: f2 = Foo(20)                                                           

In [16]: f3 = f1 + f2                                                           
+ operator used

In [17]: f3                                                                     
Out[17]: Foo(x=30)

In [18]: f1                                                                     
Out[18]: Foo(x=10)

In [19]: f2                                                                     
Out[19]: Foo(x=20)

In [20]: f1 += f2                                                               
+= operator used

In [21]: f1                                                                     
Out[21]: Foo(x=30)

Allo stesso modo, la classe elenco ha implementazioni separate per + e +=.

Utilizzando += fa effettivamente un extend in background.

In [24]: l = [1, 2, 3, 4]                                                       

In [25]: l                                                                      
Out[25]: [1, 2, 3, 4]

In [26]: id(l)                                                                  
Out[26]: 140009508733504

In [27]: l += [5, 6, 7]                                                         

In [28]: l                                                                      
Out[28]: [1, 2, 3, 4, 5, 6, 7]

In [29]: id(l)                                                                  
Out[29]: 140009508733504

Utilizzo di + crea un nuovo elenco.

In [31]: l                                                                      
Out[31]: [1, 2, 3]

In [32]: id(l)                                                                  
Out[32]: 140009508718080

In [33]: l = l + [4, 5, 6]                                                      

In [34]: l                                                                      
Out[34]: [1, 2, 3, 4, 5, 6]

In [35]: id(l)                                                                  
Out[35]: 140009506500096

Veniamo ora alla sua domanda.

In [36]: t = ([1, 2], [3, 4])                                                   

In [37]: t[0] += [10, 20]                                                       
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in 
----> 1 t[0] += [10, 20]

TypeError: 'tuple' object does not support item assignment

In [38]: t                                                                      
Out[38]: ([1, 2, 10, 20], [3, 4])

Il + viene eseguito per primo, il che significa che l'elenco viene aggiornato (esteso). Questo è consentito, poiché il riferimento all'elenco (valore memorizzato nella tupla) non cambia, quindi va bene.

Il = tenta quindi di aggiornare il riferimento all'interno della classe tuple il che non è consentito, poiché le tuple sono immutabili.

Ma l'elenco attuale è stato mutato dal metodo +.

Python non riesce ad aggiornare il riferimento all'elenco all'interno della tupla, ma poiché sarebbe stato aggiornato allo stesso riferimento, noi utenti non vediamo il cambiamento.

Quindi, il parametro + viene eseguito e il comando = non viene eseguito. + muta il già referenziato list all'interno del file tuple in modo da vedere la mutazione nell'elenco.

Le risposte esistenti sono corrette, ma credo che la documentazione possa fare un po' di luce in più su questo punto:

Dalla documentazione sugli operatori in-place:

l'istruzione x += y è equivalente a x = operator.iadd(x, y)

quindi quando scriviamo

x[0] += [3, 4]

è equivalente a

x[0] = operator.iadd(x[0], [3, 4])

iadd è implementato usando extendnel caso dell'elenco, quindi vediamo che questa operazione fa in realtà due cose:

  • estende l'elenco
  • riassegnare all'indice 0

Come indicato più avanti nella documentazione:

si noti che quando viene richiamato un metodo in-place, il calcolo e l'assegnazione vengono eseguiti in due fasi separate.

La prima operazione non è un problema

La seconda operazione è impossibile, poiché x è una tupla.

Ma perché riassegnare?

Questo può sembrare sconcertante in questo caso, e ci si può chiedere perché l'elemento +=è equivalente a x = operator.iadd(x, y)piuttosto che semplicemente operator.iadd(x, y).

Questo non funzionerebbe per i tipi immutabili, come int e str. Quindi, mentre iadd è implementato come return x.extend(y) per le liste, è implementato come return x + y per gli int.

Ancora dalla documentazione:

Per obiettivi immutabili come stringhe, numeri e tu

Se puoi, hai il potere di lasciare un post su cosa pensi di questa sezione.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.