Pourquoi b + = (4,) fonctionne-t-il et b = b + (4,) ne fonctionne-t-il pas lorsque b est une liste?

77

Si nous prenons b = [1,2,3]et si nous essayons de faire:b+=(4,)

Il revient b = [1,2,3,4], mais si nous essayons de le faire, b = b + (4,)cela ne fonctionne pas.

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

Je m'attendais b+=(4,)à l'échec car vous ne pouvez pas ajouter une liste et un tuple, mais cela a fonctionné. J'ai donc essayé de b = b + (4,)m'attendre à obtenir le même résultat, mais cela n'a pas fonctionné.

Supun Dasantha Kuruppu
la source
4
Je pense qu'une réponse peut être trouvée ici .
jochen
Au début, j'ai mal lu cela et j'ai essayé de le fermer comme trop large, puis je l'ai rétracté. Ensuite, j'ai pensé que ce devait être un doublon, mais non seulement je n'ai pas pu refaire un vote, mais j'ai arraché mes cheveux en essayant de trouver d'autres réponses comme celles-ci. : /
Karl Knechtel
Question très similaire: stackoverflow.com/questions/58048664/…
sanyash

Réponses:

70

Le problème des questions «pourquoi» est qu’elles peuvent généralement signifier plusieurs choses différentes. Je vais essayer de répondre à chacune, je pense que vous pourriez avoir à l'esprit.

"Pourquoi est-il possible qu'il fonctionne différemment?" ce qui est répondu par exemple par ceci . Essentiellement, +=essaie d'utiliser différentes méthodes de l'objet: __iadd__(qui n'est cochée que sur le côté gauche), vs __add__et __radd__("reverse add", cochée sur le côté droit si le côté gauche n'a pas __add__) pour +.

"Que fait exactement chaque version?" En bref, la list.__iadd__méthode fait la même chose que list.extend(mais à cause de la conception du langage, il y a toujours une affectation en arrière).

Cela signifie également par exemple que

>>> 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]

+, bien sûr, crée un nouvel objet, mais requiert explicitement une autre liste au lieu d'essayer d'extraire des éléments d'une séquence différente.

"Pourquoi est-il utile pour + = de faire cela? C'est plus efficace; la extendméthode n'a pas à créer un nouvel objet. Bien sûr, cela a parfois des effets surprenants (comme ci-dessus), et généralement Python n'est pas vraiment une question d'efficacité , mais ces décisions ont été prises il y a longtemps.

"Quelle est la raison de ne pas autoriser l'ajout de listes et de tuples avec +?" Voir ici (merci, @ splash58); une idée est que (tuple + liste) devrait produire le même type que (liste + tuple), et il n'est pas clair de quel type le résultat devrait être. +=n'a pas ce problème, car a += bil ne faut évidemment pas changer le type de a.

Karl Knechtel
la source
2
Oof, tout à fait raison. Et les listes n'utilisent pas |, ce qui gâche un peu mon exemple. Si je pense à un exemple plus clair plus tard, je l'échangerai.
Karl Knechtel
1
Btw |pour les ensembles est un opérateur de navettage, mais pas +pour les listes. Pour cette raison, je ne pense pas que l'argument concernant l'ambiguïté de type soit particulièrement fort. Puisque l'opérateur ne fait pas la navette pourquoi exiger la même chose pour les types? On pourrait simplement convenir que le résultat a le type de lhs. En revanche, en restreignant list + iterator, le développeur est encouragé à être plus explicite sur son intention. Si vous voulez créer une nouvelle liste qui contient la substance à partir aprolongée par les choses de bil y a déjà une façon de le faire: new = a.copy(); new += b. C'est une ligne de plus mais limpide.
a_guest
La raison pour laquelle a += bse comporte différemment a = a + bn'est pas l'efficacité. C'est en fait que Guido a jugé le comportement choisi moins déroutant. Imaginez une fonction recevant une liste acomme argument puis le faisant a += [1, 2, 3]. Cette syntaxe semble certainement modifier la liste en place, plutôt que de créer une nouvelle liste, donc la décision a été prise qu'elle devrait se comporter selon l'intuition de la plupart des gens sur le résultat attendu. Cependant, le mécanisme devait également fonctionner pour des types immuables comme ints, ce qui a conduit à la conception actuelle.
Sven Marnach
Personnellement, je pense que le design est en fait plus déroutant que de simplement faire a += bun raccourci a = a + b, comme l'a fait Ruby, mais je peux comprendre comment nous y sommes arrivés.
Sven Marnach
21

Ils ne sont pas équivalents:

b += (4,)

est un raccourci pour:

b.extend((4,))

tout en +concatène les listes, donc en:

b = b + (4,)

vous essayez de concaténer un tuple dans une liste

alfasin
la source
14

Lorsque vous faites cela:

b += (4,)

est converti en ceci:

b.__iadd__((4,)) 

Sous le capot, il appelle b.extend((4,)), extendaccepte un itérateur et c'est pourquoi cela fonctionne également:

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

mais quand vous faites cela:

b = b + (4,)

est converti en ceci:

b = b.__add__((4,)) 

accepter uniquement l'objet de liste.

Charif DZ
la source
4

À partir des documents officiels, pour les types de séquence mutables :

s += t
s.extend(t)

sont définis comme:

s'étend savec le contenu det

Ce qui est différent de celui défini comme:

s = s + t    # not equivalent in Python!

Cela signifie également que tout type de séquence fonctionnerat , y compris un tuple comme dans votre exemple.

Mais cela fonctionne aussi pour les gammes et les générateurs! Par exemple, vous pouvez également faire:

s += range(3)
Gland
la source
3

Les opérateurs d'affectation "augmentés" comme +=ont été introduits dans Python 2.0, qui a été publié en octobre 2000. La conception et la justification sont décrites dans PEP 203 . L'un des objectifs déclarés de ces opérateurs était de soutenir les opérations sur place. L'écriture

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

est censé mettre à jour la liste a en place . Cela est important s'il existe d'autres références à la liste a, par exemple quand a aété reçu comme argument de fonction.

Cependant, l'opération ne peut pas toujours se produire sur place, car de nombreux types Python, y compris les entiers et les chaînes, sont immuables , par exemple i += 1pour un entier ine peut pas fonctionner sur place.

En résumé, les opérateurs d'affectation augmentée étaient censés fonctionner sur place lorsque cela était possible et créer un nouvel objet dans le cas contraire. Pour faciliter ces objectifs de conception, l'expression a x += yété spécifiée pour se comporter comme suit:

  • Si x.__iadd__est défini, x.__iadd__(y)est évalué.
  • Sinon, if x.__add__est implémenté x.__add__(y)est évalué.
  • Sinon, if y.__radd__est implémenté y.__radd__(x)est évalué.
  • Sinon, déclenchez une erreur.

Le premier résultat obtenu par ce processus sera affecté à nouveau x(sauf si ce résultat est le NotImplementedsingleton, auquel cas la recherche se poursuit à l'étape suivante).

Ce processus permet aux types qui prennent en charge la modification sur place à implémenter __iadd__(). Les types qui ne prennent pas en charge la modification sur place n'ont pas besoin d'ajouter de nouvelles méthodes magiques, car Python reviendra automatiquement à essentiellement x = x + y.

Venons-en enfin à votre question réelle - pourquoi vous pouvez ajouter un tuple à une liste avec un opérateur d'affectation augmenté. De mémoire, l'historique de ceci était à peu près comme ceci: La list.__iadd__()méthode a été implémentée pour appeler simplement la list.extend()méthode déjà existante dans Python 2.0. Lorsque les itérateurs ont été introduits dans Python 2.1, la list.extend()méthode a été mise à jour pour accepter les itérateurs arbitraires. Le résultat final de ces changements a my_list += my_tuplefonctionné à partir de Python 2.1. La list.__add__()méthode, cependant, n'a jamais été censé soutenir itérateurs arbitraires comme argument de droite - cela a été jugé inapproprié pour un langage fortement typé.

Personnellement, je pense que l'implémentation des opérateurs augmentés a fini par être un peu trop complexe en Python. Il a de nombreux effets secondaires surprenants, par exemple ce code:

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

La deuxième ligne soulève TypeError: 'tuple' object does not support item assignment, mais l'opération est réalisée avec succès de toute façon - tsera ([42, 44], [43])après l' exécution de la ligne qui soulève l'erreur.

Sven Marnach
la source
Bravo! La référence au PEP est particulièrement utile. J'ai ajouté un lien à l'autre extrémité, à une question SO précédente sur le comportement de liste dans les tuples. Quand je repense à ce qu'était Python avant 2.3 ou plus, cela semble pratiquement inutilisable par rapport à aujourd'hui ... (et j'ai toujours un vague souvenir d'essayer et de ne pas obtenir 1.5 pour faire quoi que ce soit d'utile sur un très vieux Mac)
Karl Knechtel
2

La plupart des gens s'attendraient à ce que X + = Y soit équivalent à X = X + Y. En effet, la référence de poche Python (4e éd.) De Mark Lutz dit à la page 57 "Les deux formats suivants sont à peu près équivalents: X = X + Y, X + = Y ". Cependant, les personnes qui ont spécifié Python ne les ont pas rendues équivalentes. Peut-être que c'était une erreur qui entraînera des heures de temps de débogage par des programmeurs frustrés aussi longtemps que Python restera utilisé, mais c'est maintenant exactement comme Python. Si X est un type de séquence mutable, X + = Y est équivalent à X.extend (Y) et non à X = X + Y.

zizzler
la source
> Peut-être que c'était une erreur qui entraînera des heures de débogage par des programmeurs frustrés aussi longtemps que Python restera utilisé <- avez-vous réellement souffert à cause de cela? Vous semblez parler d'expérience. J'aimerais beaucoup entendre votre histoire.
Veky
1

Comme il est expliqué ici , si arrayn'implémente pas la __iadd__méthode, le b+=(4,)serait juste un raccourci b = b + (4,)mais ce n'est évidemment pas le cas, il en arrayva de même pour la __iadd__méthode. Apparemment, l'implémentation de la __iadd__méthode ressemble à ceci:

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

Cependant, nous savons que le code ci-dessus n'est pas l'implémentation réelle de la __iadd__méthode, mais nous pouvons supposer et accepter qu'il existe quelque chose comme la extendméthode, qui accepte les tuppleentrées.

Hamidreza
la source