Perché b + = (4,) funziona e b = b + (4,) non funziona quando b è un elenco?


77

Se prendiamo b = [1,2,3]e se proviamo a fare:b+=(4,)

Ritorna b = [1,2,3,4], ma se proviamo a farlo b = b + (4,)non funziona.

b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists

Mi aspettavo b+=(4,)di fallire perché non puoi aggiungere un elenco e una tupla, ma ha funzionato. Quindi ho provato ad b = b + (4,)aspettarmi di ottenere lo stesso risultato, ma non ha funzionato.


4
Credo che una risposta possa essere trovata qui .
jochen


All'inizio ho letto male questo e ho cercato di chiuderlo come troppo ampio, quindi l'ho ritratto. Poi ho pensato che dovesse essere un duplicato, ma non solo non potevo ripetere il voto, ma ho tirato fuori i capelli cercando di trovare altre risposte come quelle. : /
Karl Knechtel,

Risposte:


70

Il problema con le domande "perché" è che di solito possono significare più cose diverse. Proverò a rispondere a ciascuno di quelli che penso tu possa avere in mente.

"Perché è possibile che funzioni diversamente?" a cui risponde ad esempio questo . Fondamentalmente, +=tenta di utilizzare diversi metodi dell'oggetto: __iadd__(che viene controllato solo sul lato sinistro), vs __add__e __radd__("reverse reverse", controllato sul lato destro se il lato sinistro non ha __add__) per +.

"Cosa fa esattamente ogni versione?" In breve, il list.__iadd__metodo fa la stessa cosa list.extend(ma a causa del design del linguaggio, c'è ancora un compito indietro).

Questo significa anche che

>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]

+, ovviamente, crea un nuovo oggetto, ma richiede esplicitamente un altro elenco invece di cercare di estrarre elementi da una sequenza diversa.

"Perché è utile che + = lo faccia? È più efficiente; il extendmetodo non deve creare un nuovo oggetto. Naturalmente, questo ha alcuni effetti sorprendenti a volte (come sopra), e in generale Python non riguarda davvero l'efficienza , ma queste decisioni sono state prese molto tempo fa.

"Qual è il motivo per non consentire l'aggiunta di elenchi e tuple con +?" Vedi qui (grazie, @ splash58); un'idea è che (tupla + lista) dovrebbe produrre lo stesso tipo di (lista + tupla), e non è chiaro quale tipo dovrebbe essere il risultato. +=non ha questo problema, perché a += bovviamente non dovrebbe cambiare il tipo di a.


2
Oof, giusto. E le liste non usano |, quindi un po 'rovina il mio esempio. Se penso a un esempio più chiaro dopo, lo scambierò.
Karl Knechtel,

1
Btw |per i set è un operatore pendolarismo ma +per gli elenchi no. Per questa ragione non credo che l'argomento sull'ambiguità dei tipi sia particolarmente forte. Dal momento che l'operatore non commuta perché richiedere lo stesso per i tipi? Si potrebbe semplicemente concordare sul fatto che il risultato ha il tipo di lhs. D'altra parte, limitando list + iterator, lo sviluppatore è incoraggiato a essere più esplicito sul proprio intento. Se si desidera creare una nuova lista che contiene il materiale dal aprorogato di roba da bc'è già un modo per farlo: new = a.copy(); new += b. È un'altra riga ma cristallina.
a_guest

Il motivo per cui a += bsi comporta in modo diverso rispetto a = a + bnon è l'efficienza. In realtà, Guido ha ritenuto il comportamento scelto meno confuso. Immagina una funzione che riceve un elenco acome argomento e poi lo fa a += [1, 2, 3]. Questa sintassi sembra certamente modificare l'elenco in atto, piuttosto che creare un nuovo elenco, quindi è stata presa la decisione che dovrebbe comportarsi in base all'intuizione della maggior parte delle persone sul risultato atteso. Tuttavia, il meccanismo doveva anche funzionare per tipi immutabili come ints, che ha portato al design attuale.
Sven Marnach,

Personalmente penso che il design sia in realtà più confuso rispetto alla semplice a += bstenografia a = a + b, come ha fatto Ruby, ma posso capire come ci siamo arrivati.
Sven Marnach,

21

Non sono equivalenti:

b += (4,)

è una scorciatoia per:

b.extend((4,))

mentre +concatena gli elenchi, quindi:

b = b + (4,)

stai cercando di concatenare una tupla in un elenco


14

Quando lo fai:

b += (4,)

viene convertito in questo:

b.__iadd__((4,)) 

Sotto il cofano chiama b.extend((4,)), extendaccetta un iteratore e questo perché funziona anche:

b = [1,2,3]
b += range(2)  # prints [1, 2, 3, 0, 1]

ma quando lo fai:

b = b + (4,)

viene convertito in questo:

b = b.__add__((4,)) 

accetta solo oggetti elenco.


4

Dai documenti ufficiali, per i tipi di sequenza mutabili entrambi:

s += t
s.extend(t)

sono definiti come:

si estende scon il contenuto dit

Che è diverso dall'essere definito come:

s = s + t    # not equivalent in Python!

Questo significa anche che funzionerà qualsiasi tipo di sequenzat , inclusa una tupla come nel tuo esempio.

Funziona anche con gamme e generatori! Ad esempio, puoi anche fare:

s += range(3)

3

Gli operatori di assegnazione "aumentati" come +=sono stati introdotti in Python 2.0, che è stato rilasciato nell'ottobre 2000. Il design e la logica sono descritti in PEP 203 . Uno degli obiettivi dichiarati di questi operatori era il supporto delle operazioni sul posto. scrittura

a = [1, 2, 3]
a += [4, 5, 6]

dovrebbe aggiornare l'elenco a in atto . Questo importa se ci sono altri riferimenti all'elenco a, ad esempio quando è astato ricevuto come argomento di funzione.

Tuttavia, l'operazione non può sempre avvenire sul posto, poiché molti tipi di Python, inclusi numeri interi e stringhe, sono immutabili , quindi ad es. i += 1Per un numero intero inon è possibile operare sul posto.

In sintesi, gli operatori di assegnazione aumentata dovevano lavorare sul posto quando possibile e creare un nuovo oggetto in caso contrario. Per facilitare questi obiettivi di progettazione, l'espressione è x += ystata specificata per comportarsi come segue:

  • Se x.__iadd__definito, x.__iadd__(y)viene valutato.
  • Altrimenti, se x.__add__viene implementato x.__add__(y)viene valutato.
  • Altrimenti, se y.__radd__viene implementato y.__radd__(x)viene valutato.
  • Altrimenti solleva un errore.

Il primo risultato ottenuto da questo processo verrà assegnato nuovamente x(a meno che quel risultato non sia il NotImplementedsingleton, nel qual caso la ricerca continua con il passaggio successivo).

Questo processo consente l'implementazione di tipi che supportano la modifica sul posto __iadd__(). I tipi che non supportano la modifica sul posto non hanno bisogno di aggiungere nuovi metodi magici, poiché Python tornerà automaticamente a essenzialmente x = x + y.

Quindi, veniamo finalmente alla tua vera domanda: perché puoi aggiungere una tupla a un elenco con un operatore di assegnazione aumentato. Dalla memoria, la storia di questo è stata più o meno così: il list.__iadd__()metodo è stato implementato per chiamare semplicemente il list.extend()metodo già esistente in Python 2.0. Quando gli iteratori sono stati introdotti in Python 2.1, il list.extend()metodo è stato aggiornato per accettare iteratori arbitrari. Il risultato finale di queste modifiche è stato che ha my_list += my_tuplefunzionato a partire da Python 2.1. Il list.__add__()metodo, tuttavia, non avrebbe mai dovuto supportare iteratori arbitrari come argomento di destra - questo era considerato inappropriato per un linguaggio fortemente tipizzato.

Personalmente penso che l'implementazione di operatori aumentati sia stata un po 'troppo complessa in Python. Ha molti effetti collaterali sorprendenti, ad esempio questo codice:

t = ([42], [43])
t[0] += [44]

La seconda riga viene sollevata TypeError: 'tuple' object does not support item assignment, ma l'operazione viene comunque eseguita correttamente : tsarà ([42, 44], [43])dopo aver eseguito la riga che genera l'errore.


Bravo! Avere un riferimento al PEP è particolarmente utile. Ho aggiunto un collegamento all'altra estremità, a una precedente domanda SO sul comportamento dell'elenco in tupla. Quando ripenso a com'era Python prima della 2.3 o giù di lì, sembra praticamente inutilizzabile rispetto a oggi ... (e ho ancora un vago ricordo di aver provato e non riesco a ottenere 1.5 per fare qualcosa di utile su un Mac molto vecchio)
Karl Knechtel,

2

La maggior parte delle persone si aspetterebbe che X + = Y equivalga a X = X + Y. In effetti, il Python Pocket Reference (4 ° ed.) Di Mark Lutz dice a pagina 57 "I seguenti due formati sono approssimativamente equivalenti: X = X + Y, X + = Y ". Tuttavia, le persone che hanno specificato Python non le hanno rese equivalenti. Forse è stato un errore che comporterà ore di tempo di debug da parte di programmatori frustrati finché Python rimarrà in uso, ma ora è proprio così Python. Se X è un tipo di sequenza mutabile, X + = Y è equivalente a X.extend (Y) e non a X = X + Y.


> Forse è stato un errore che comporterebbe ore di tempo di debug da parte di programmatori frustrati per tutto il tempo in cui Python rimane in uso <- hai davvero sofferto per questo? Sembra che tu stia parlando per esperienza. Mi piacerebbe molto sentire la tua storia.
Veky,

1

Come spiegato qui , se arraynon implementa il __iadd__metodo, b+=(4,)sarebbe solo una scorciatoia b = b + (4,)ma ovviamente non lo è, così arrayimplementa il __iadd__metodo. Apparentemente l'implementazione del __iadd__metodo è qualcosa del genere:

def __iadd__(self, x):
    self.extend(x)

Tuttavia sappiamo che il codice sopra riportato non è l'implementazione effettiva del __iadd__metodo, ma possiamo presumere e accettare che esiste qualcosa di simile al extendmetodo, che accetta tuppleinput.

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.