L' +=
opérateur en python semble fonctionner de manière inattendue sur les listes. Quelqu'un peut-il me dire ce qui se passe ici?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
PRODUCTION
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
semble affecter chaque instance de la classe, alors que foo = foo + bar
semble se comporter de la manière dont je m'attendrais à ce que les choses se comportent.
L' +=
opérateur est appelé "opérateur d'affectation composé".
python
augmented-assignment
eucalculie
la source
la source
+
opérateur sur des tableaux. Je pense qu'il est tout à fait logique dans ce cas d'+=
ajouter.Réponses:
La réponse générale est que
+=
tente d'appeler la__iadd__
méthode spéciale, et si elle n'est pas disponible, elle essaie de l'utiliser à la__add__
place. Le problème réside donc dans la différence entre ces méthodes spéciales.La
__iadd__
méthode spéciale est pour un ajout sur place, c'est-à-dire qu'elle mute l'objet sur lequel elle agit. La__add__
méthode spéciale renvoie un nouvel objet et est également utilisée pour l'+
opérateur standard .Ainsi, lorsque l'
+=
opérateur est utilisé sur un objet qui a un__iadd__
défini l'objet est modifié en place. Sinon, il essaiera d'utiliser le plain__add__
et de renvoyer un nouvel objet.C'est pourquoi pour les types mutables comme les listes
+=
modifie la valeur de l'objet, alors que pour les types immuables comme les tuples, les chaînes et les entiers, un nouvel objet est renvoyé à la place (a += b
devient équivalent àa = a + b
).Pour les types qui prennent en charge les deux
__iadd__
,__add__
vous devez donc faire attention à celui que vous utilisez.a += b
va appeler__iadd__
et mutera
, alors quea = a + b
va créer un nouvel objet et l'assigner àa
. Ce ne sont pas la même opération!Pour les types immuables (où vous n'avez pas de
__iadd__
)a += b
eta = a + b
sont équivalents. C'est ce qui vous permet d'utiliser+=
sur des types immuables, ce qui peut sembler une décision de conception étrange jusqu'à ce que vous considériez que sinon vous ne pourriez pas utiliser+=
sur des types immuables comme les nombres!la source
__radd__
méthode qui peut être appelée parfois (elle est pertinente pour les expressions qui impliquent principalement des sous-classes).+=
réellement une liste, cela explique pourquoi donne un certain temps revient simplement .x = []; x = x + {}
TypeError
x = []; x += {}
[]
Pour le cas général, voir la réponse de Scott Griffith . Cependant, lorsque vous traitez des listes comme vous, l'
+=
opérateur est un raccourci poursomeListObject.extend(iterableObject)
. Voir la documentation de extend () .La
extend
fonction ajoutera tous les éléments du paramètre à la liste.Lorsque
foo += something
vous modifiez la listefoo
sur place, vous ne changez donc pas la référence vers laquellefoo
pointe le nom , mais vous modifiez directement l'objet de la liste. Avecfoo = foo + something
, vous créez en fait une nouvelle liste.Cet exemple de code l'expliquera:
Notez comment la référence change lorsque vous réaffectez la nouvelle liste à
l
.Comme
bar
c'est une variable de classe au lieu d'une variable d'instance, la modification sur place affectera toutes les instances de cette classe. Mais lors de la redéfinitionself.bar
, l'instance aura une variable d'instance distincteself.bar
sans affecter les autres instances de classe.la source
a += b
c'est différent dea = a + b
deux listesa
etb
. Mais cela a du sens;extend
serait plus souvent la chose prévue à faire avec des listes plutôt que de créer une nouvelle copie de la liste entière qui aura une plus grande complexité de temps. Si les développeurs doivent faire attention à ne pas modifier les listes d'origine en place, les tuples sont une meilleure option étant des objets immuables.+=
avec des tuples ne peut pas modifier le tuple d'origine.Le problème ici est qu'il
bar
est défini comme un attribut de classe et non comme une variable d'instance.Dans
foo
, l'attribut class est modifié dans lainit
méthode, c'est pourquoi toutes les instances sont affectées.Dans
foo2
, une variable d'instance est définie à l'aide de l'attribut de classe (vide), et chaque instance obtient la siennebar
.La mise en œuvre "correcte" serait:
Bien sûr, les attributs de classe sont tout à fait légaux. En fait, vous pouvez y accéder et les modifier sans créer une instance de la classe comme ceci:
la source
Il y a deux choses impliquées ici:
+
L'opérateur appelle la__add__
méthode sur une liste. Il prend tous les éléments de ses opérandes et crée une nouvelle liste contenant ces éléments en conservant leur ordre.+=
l'opérateur appelle la__iadd__
méthode de la liste. Il prend un itérable et ajoute tous les éléments de l'itérable à la liste en place. Il ne crée pas de nouvel objet de liste.En classe,
foo
l'instructionself.bar += [x]
n'est pas une instruction d'affectation mais se traduit en fait parqui modifie la liste en place et agit comme la méthode list
extend
.En classe
foo2
, au contraire, l'instruction d'affectation dans lainit
méthodepeut être déconstruite comme suit:
l'instance n'a pas d'attribut
bar
(il existe cependant un attribut de classe du même nom), donc elle accède à l'attribut de classebar
et crée une nouvelle liste en y ajoutantx
. La déclaration se traduit par:Ensuite, il crée un attribut d'instance
bar
et lui affecte la liste nouvellement créée. Notez quebar
sur le rhs de l'affectation est différent dubar
sur le lhs.Pour les instances de classe
foo
,bar
est un attribut de classe et non un attribut d'instance. Par conséquent, toute modification de l'attribut de classebar
sera reflétée pour toutes les instances.Au contraire, chaque instance de la classe
foo2
a son propre attribut d'instancebar
qui est différent de l'attribut de classe du même nombar
.J'espère que cela clarifie les choses.
la source
Bien que beaucoup de temps se soit écoulé et que beaucoup de choses correctes aient été dites, il n'y a pas de réponse regroupant les deux effets.
Vous avez 2 effets:
+=
(comme indiqué par Scott Griffiths )En classe
foo
, la__init__
méthode modifie l'attribut de classe. C'est parce que seself.bar += [x]
traduit parself.bar = self.bar.__iadd__([x])
.__iadd__()
est pour la modification sur place, donc il modifie la liste et renvoie une référence à celle-ci.Notez que l'instance dict est modifiée bien que cela ne soit normalement pas nécessaire car la classe dict contient déjà la même affectation. Donc, ce détail passe presque inaperçu - sauf si vous faites un
foo.bar = []
après. Ici, les instancesbar
restent les mêmes grâce à ce fait.En classe
foo2
, cependant, la classebar
est utilisée, mais pas touchée. Au lieu de cela, un y[x]
est ajouté, formant un nouvel objet, commeself.bar.__add__([x])
on l'appelle ici, qui ne modifie pas l'objet. Le résultat est ensuite placé dans l'instance dict, donnant à l'instance la nouvelle liste sous forme de dict, tandis que l'attribut de la classe reste modifié.La distinction entre
... = ... + ...
et... += ...
affecte également les affectations par la suite:Vous pouvez vérifier l'identité des objets avec
print id(foo), id(f), id(g)
(n'oubliez pas les()
s supplémentaires si vous êtes sur Python3).BTW: L'
+=
opérateur est appelé «affectation augmentée» et est généralement destiné à faire des modifications sur place dans la mesure du possible.la source
Les autres réponses semblent couvrir à peu près tout cela, bien qu'il semble utile de citer et de se référer aux affectations augmentées PEP 203 :
...
la source
la source
Nous voyons que lorsque nous essayons de modifier un objet immuable (entier dans ce cas), Python nous donne simplement un objet différent à la place. D'autre part, nous pouvons apporter des modifications à un objet mutable (une liste) et le faire rester le même objet partout.
réf: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Référez-vous également à l'url ci-dessous pour comprendre la copie peu profonde et la copie profonde
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
la source