Topic: Augmented assignment e oggetti mutabili (WTF)  (Letto 66 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline Trizio

  • python unicellularis
  • *
  • Post: 40
  • Punti reputazione: 1
    • Mostra profilo
Augmented assignment e oggetti mutabili (WTF)
« il: Novembre 15, 2018, 12:09 »
L'augmented assignment (eh, non so come si chiami in italiano) con il + su una lista non esegue una concatenation, ma il metodo extend:

>>> foo = bar = [1, 2, 3]
>>> foo += 4, 5
>>> foo
[1, 2, 3, 4, 5]
>>> bar
[1, 2, 3, 4, 5]


Già qui la cosa mi urta, ma tant'è. Dato che l'operazione è mappata su extend e non su una concatenation l'oggetto a destra può essere qualsiasi iterable (nell'esempio una tupla) e ovviamente l'operazione cambia sul posto l'oggetto, conseguenza da tenere presente in caso di shared reference (come nell'esempio sopra).

Il tutto mi sembra sbagliato. E su più livelli. Tanto per cominciare il + fa pensare ad una concatenation ed infatti sarebbe lecito aspettarsi una concatenation. Inoltre, a che diavolo serve un in-place change così insaspettato, quando si può chiamare esplicitamente extend e rendere tutto molto più chiaro?

La cosa si fa ancora più oscura quando prendiamo i set. Ora, i set hanno i vari metodi update che possono cambiarli sul posto. Constatato come funziona l'augmented assignment con le liste, ci si potrebbe aspettare che un'operazione come |= sia mappata sul metodo update. Non proprio, come si evince qui:

>>> foo = bar = {'a', 'b', 'c'}
>>> foo |= 'x', 'y', 'z'
TypeError: unsupported operand type(s) for |=: 'set' and 'tuple'

>>> foo |= {'x', 'y', 'z'}
>>> foo
{'b', 'c', 'a', 'y', 'z', 'x'}
>>> bar
{'b', 'c', 'a', 'y', 'z', 'x'}


L'operazione di sicuro non esegue una normale unione (ossia foo = foo | {'x', 'y', 'z'}) dato che cambia l'oggetto sul posto (i.e. non ne crea uno nuovo come invece fa l'unione).  Ma non è neppure mappata sul metodo update, dato che update accetta qualsiasi iterable, mentre il qui presente augmented assignment accetta solo set. Il risultato è insomma una versione azzoppata del metodo update.



Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.821
  • Punti reputazione: 9
    • Mostra profilo
Re:Augmented assignment e oggetti mutabili (WTF)
« Risposta #1 il: Novembre 15, 2018, 15:40 »
Uhm, sì ma non vedo la domanda qui. O meglio, non vedo il problema. Se partiamo dal fatto che siamo consapevoli che
1) gli oggetti mutabili sono diversi dagli oggetti immutabili
2) le stesse operazioni su oggetti diversi possono avere una semantica diversa,
allora il resto, purché sia ben documentato, è una questione di gusti.
Per esempio, le liste. Partiamo dal fatto che sappiamo che a = a + [1] è una concatenazione e produce un oggetto diverso. A te dà fastidio che invece a += [1] sia un extend e quindi cambia l'oggetto sul posto. Perché ti sembra che quel "+=" dovrebbe richiamare di più alla mente una concatenazione. Curiosamente per me invece ha più un odore di "operazione da fare sul posto"... boh, sarò strano io. Certo non mi aspetto che a = 0; a += 1 cambi l'oggetto sul posto. Ma appunto, le stesse operazioni hanno una semantica diversa su oggetti diversi; una volta capito questo, tutti i discorsi "sarebbe meglio che A.foo si comportasse così e cosà per _analogia_ a quello che fa B.foo" sono legittimi, ma sono anche un po' questione di gusti. Probabilmente a scavare negli archivi di python-devs si trova anche la discussione e le motivazioni che hanno portato a mappare quel "+=" su extend... ma oggi la cosa importante è che sia documentato.
Poi naturalmente c'è che la concatenazione si può fare solo su liste, mentre extend accetta qualsiasi iterabile, e di conseguenza "+" e "+=" si comportano in modo diverso anche sotto questo aspetto. Ma di nuovo, se accettiamo la premessa (che concatenazione ed extend abbiano regole diverse) allora anche il "+=" non è difficile da accettare.
Forse l'errore "ottico" sta nel pensare che "+" e "+=" abbiano un "+" in comune... oppure di rifarsi sempre ad "a+=1" come scorciatoia per "a=a+1" ed aspettarsi che i due operatori implementino sempre la stessa "operazione analoga"... ma non è necessariamente una buona idea con gli oggetti più complessi degli interi. O piuttosto (ed è la mia umile opinione): è *sempre* una cattiva idea cercare di implementare operatori come "+=" con gli oggetti più complessi, e bisognerebbe sempre esplicitamente usare extend. Ma d'altra parte, chi ama insaporire il suo codice con un po' di sano offuscamento in stile perl, trova irresistibile scrivere a += 1, (il diavolo sta nella virgola). Contento lui.

Per i set, le cose stanno più o meno così, ma con una lieve differenza, che peraltro è ben documentata. L'operazione "|=" è effettivamente mappata su update, ma con la differenza che
Citazione
Note, the non-operator versions of union(), intersection(), difference(), and symmetric_difference(), issubset(), and issuperset() methods will accept any iterable as an argument. In contrast, their operator based counterparts require their arguments to be sets. This precludes error-prone constructions like set('abc') & 'cbs' in favor of the more readable set('abc').intersection('cbs').
(e, più sotto)
Note, the non-operator versions of the update(), intersection_update(), difference_update(), and symmetric_difference_update() methods will accept any iterable as an argument.
Questo potrà sembrarti una giustificazione ragionevole, oppure no. Io francamente prima di cinque minuti fa non mi ero neppure mai posto il problema, perché non ho mai usato "|=" nel mio codice (e continuerò a non usarlo)...
« Ultima modifica: Novembre 15, 2018, 15:42 da RicPol »

Offline Trizio

  • python unicellularis
  • *
  • Post: 40
  • Punti reputazione: 1
    • Mostra profilo
Re:Augmented assignment e oggetti mutabili (WTF)
« Risposta #2 il: Novembre 15, 2018, 16:29 »
Uhm, sì ma non vedo la domanda qui. O meglio, non vedo il problema. Se partiamo dal fatto che siamo consapevoli che
1) gli oggetti mutabili sono diversi dagli oggetti immutabili
2) le stesse operazioni su oggetti diversi possono avere una semantica diversa,
allora il resto, purché sia ben documentato, è una questione di gusti.

Non sono d'accordo. Quell'augmented assignment è di fatto una concatenazione sulle stringhe e sulle tuple; uno studioso poco attento potrebbe equivocare e pensare che faccia lo stesso con tutte le sequenze. A mio avviso è una palese incoerenza.

Inoltre, potrebbe indurre qualcuno a utilizzare questo espediente piuttosto che a utilizzare il metodo extend, ossia l'approccio più leggibile.

Per esempio, le liste. Partiamo dal fatto che sappiamo che a = a + [1] è una concatenazione e produce un oggetto diverso. A te dà fastidio che invece a += [1] sia un extend e quindi cambia l'oggetto sul posto. Perché ti sembra che quel "+=" dovrebbe richiamare di più alla mente una concatenazione. Curiosamente per me invece ha più un odore di "operazione da fare sul posto"... boh, sarò strano io. Certo non mi aspetto che a = 0; a += 1 cambi l'oggetto sul posto. Ma appunto, le stesse operazioni hanno una semantica diversa su oggetti diversi; una volta capito questo, tutti i discorsi "sarebbe meglio che A.foo si comportasse così e cosà per _analogia_ a quello che fa B.foo" sono legittimi, ma sono anche un po' questione di gusti. Probabilmente a scavare negli archivi di python-devs si trova anche la discussione e le motivazioni che hanno portato a mappare quel "+=" su extend... ma oggi la cosa importante è che sia documentato.
Poi naturalmente c'è che la concatenazione si può fare solo su liste, mentre extend accetta qualsiasi iterabile, e di conseguenza "+" e "+=" si comportano in modo diverso anche sotto questo aspetto. Ma di nuovo, se accettiamo la premessa (che concatenazione ed extend abbiano regole diverse) allora anche il "+=" non è difficile da accettare.
Forse l'errore "ottico" sta nel pensare che "+" e "+=" abbiano un "+" in comune... oppure di rifarsi sempre ad "a+=1" come scorciatoia per "a=a+1" ed aspettarsi che i due operatori implementino sempre la stessa "operazione analoga"... ma non è necessariamente una buona idea con gli oggetti più complessi degli interi. O piuttosto (ed è la mia umile opinione): è *sempre* una cattiva idea cercare di implementare operatori come "+=" con gli oggetti più complessi, e bisognerebbe sempre esplicitamente usare extend.

Sì, sono arrivato alla stessa conclusione. Ha un senso usarlo quando si lavora coi numeri, per esempio coi contatori.

Ma d'altra parte, chi ama insaporire il suo codice con un po' di sano offuscamento in stile perl, trova irresistibile scrivere a += 1, (il diavolo sta nella virgola). Contento lui.

A proposito, ieri ho scoperto un sistema per fare una shallow copy di una lista che è offuscamento allo stato dell'arte (modestamente):

>>> foo = [1, 2, 3]
>>> *bar, = foo     # oppure [*bar] = foo
>>> foo == bar
True
>>> foo is bar
False


Però qui utilizzo un extended sequence assignment per fare qualcosa di diverso dal suo scopo originario. Nel caso sopra è diverso.

Per i set, le cose stanno più o meno così, ma con una lieve differenza, che peraltro è ben documentata. L'operazione "|=" è effettivamente mappata su update, ma con la differenza che
Citazione
Note, the non-operator versions of union(), intersection(), difference(), and symmetric_difference(), issubset(), and issuperset() methods will accept any iterable as an argument. In contrast, their operator based counterparts require their arguments to be sets. This precludes error-prone constructions like set('abc') & 'cbs' in favor of the more readable set('abc').intersection('cbs').
(e, più sotto)
Note, the non-operator versions of the update(), intersection_update(), difference_update(), and symmetric_difference_update() methods will accept any iterable as an argument.
Questo potrà sembrarti una giustificazione ragionevole, oppure no. Io francamente prima di cinque minuti fa non mi ero neppure mai posto il problema, perché non ho mai usato "|=" nel mio codice (e continuerò a non usarlo)...

Anche qui... boh. Io sono dell'avviso che foo |= bar dovrebbe essere equivalente a foo = foo | bar. L'idea di avere un augmented assignment che si comporta in modo coerente dappertutto mi ispira di più.
« Ultima modifica: Novembre 15, 2018, 16:38 da Trizio »

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.103
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Augmented assignment e oggetti mutabili (WTF)
« Risposta #3 il: Novembre 24, 2018, 23:21 »
Scusa ma in quale linguaggio += crea una copia e fa append?

Al momento non me ne vengono in mente...