O +=
operador em python parece estar operando inesperadamente nas listas. Alguém pode me dizer o que está acontecendo aqui?
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
RESULTADO
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
parece afetar todas as instâncias da classe, enquanto foo = foo + bar
parece se comportar da maneira que eu esperaria que as coisas se comportassem.
O +=
operador é denominado "operador de atribuição composto".
python
augmented-assignment
eucalculia
fonte
fonte
+
operador em matrizes. Acho que faz todo o sentido, neste caso+=
, anexar.Respostas:
A resposta geral é que
+=
tenta chamar o__iadd__
método especial e, se não estiver disponível, ele tenta usar__add__
. Portanto, a questão é a diferença entre esses métodos especiais.O
__iadd__
método especial é para uma adição no local, ou seja, altera o objeto sobre o qual atua. O__add__
método especial retorna um novo objeto e também é usado para o+
operador padrão .Portanto, quando o
+=
operador é usado em um objeto que tem um__iadd__
definido, o objeto é modificado no local. Caso contrário, ele tentará usar o plano__add__
e retornar um novo objeto.É por isso que para tipos mutáveis, como listas,
+=
o valor do objeto muda, enquanto para tipos imutáveis como tuplas, strings e inteiros, um novo objeto é retornado (a += b
torna-se equivalente aa = a + b
).Para tipos que suporte tanto
__iadd__
e__add__
, portanto, você tem que ter cuidado qual você usa.a += b
irá chamar__iadd__
e sofrer mutaçãoa
, enquantoa = a + b
irá criar um novo objeto e atribuí-lo aa
. Eles não são a mesma operação!Para tipos imutáveis (onde você não tem
__iadd__
)a += b
ea = a + b
são equivalentes. Isso é o que permite que você use+=
em tipos imutáveis, o que pode parecer uma decisão de design estranha até que você considere que, de outra forma, você não poderia usar+=
em tipos imutáveis como números!fonte
__radd__
método que pode ser chamado às vezes (é relevante para expressões que envolvem principalmente subclasses).+=
realmente estende uma lista, isso explica por quex = []; x = x + {}
dá umTypeError
tempox = []; x += {}
apenas retorna[]
.Para o caso geral, consulte a resposta de Scott Griffith . Ao lidar com listas como você, entretanto, o
+=
operador é uma abreviação desomeListObject.extend(iterableObject)
. Veja a documentação de extend () .A
extend
função acrescentará todos os elementos do parâmetro à lista.Ao fazer isso,
foo += something
você está modificando a listafoo
no local, portanto, você não altera a referência para a qual o nomefoo
aponta, mas está alterando o objeto da lista diretamente. Comfoo = foo + something
, você está realmente criando uma nova lista.Este código de exemplo irá explicar isso:
Observe como a referência muda quando você reatribui a nova lista a
l
.Como
bar
é uma variável de classe em vez de uma variável de instância, a modificação no local afetará todas as instâncias dessa classe. Mas ao redefinirself.bar
, a instância terá uma variável de instância separadaself.bar
sem afetar as outras instâncias da classe.fonte
a += b
é diferente dea = a + b
duas listasa
eb
. Mas faz sentido;extend
seria mais frequentemente o que se pretendia fazer com as listas, em vez de criar uma nova cópia de toda a lista, que terá maior complexidade de tempo. Se os desenvolvedores precisarem ter cuidado para não modificar as listas originais no local, então as tuplas são uma opção melhor sendo objetos imutáveis.+=
com tuplas não pode modificar a tupla original.O problema aqui é que
bar
é definido como um atributo de classe, não uma variável de instância.Em
foo
, o atributo de classe é modificado noinit
método, por isso todas as instâncias são afetadas.Em
foo2
, uma variável de instância é definida usando o atributo de classe (vazio) e cada instância tem o seu própriobar
.A implementação "correta" seria:
Claro, os atributos de classe são completamente legais. Na verdade, você pode acessá-los e modificá-los sem criar uma instância da classe como esta:
fonte
Existem duas coisas envolvidas aqui:
+
operador chama o__add__
método em uma lista. Ele pega todos os elementos de seus operandos e faz uma nova lista contendo esses elementos mantendo sua ordem.+=
o operador chama o__iadd__
método da lista. Ele pega um iterável e anexa todos os elementos do iterável à lista no local. Ele não cria um novo objeto de lista.Na aula,
foo
a declaraçãoself.bar += [x]
não é uma declaração de atribuição, mas na verdade se traduz emque modifica a lista no local e atua como o método de lista
extend
.Na aula
foo2
, ao contrário, a instrução de atribuição noinit
métodopode ser desconstruída como:
A instância não tem nenhum atributo
bar
(embora haja um atributo de classe com o mesmo nome), portanto, ela acessa o atributo de classebar
e cria uma nova lista anexandox
a ela. A declaração se traduz em:Em seguida, ele cria um atributo de instância
bar
e atribui a lista recém-criada a ele. Observe quebar
no rhs da atribuição é diferente dobar
lhs.Para instâncias de classe
foo
,bar
é um atributo de classe e não um atributo de instância. Portanto, qualquer alteração no atributo de classebar
será refletida em todas as instâncias.Pelo contrário, cada instância da classe
foo2
tem seu próprio atributo de instância,bar
que é diferente do atributo de classe do mesmo nomebar
.Espero que isso esclareça as coisas.
fonte
Embora muito tempo tenha passado e muitas coisas corretas tenham sido ditas, não há uma resposta que agrupe ambos os efeitos.
Você tem 2 efeitos:
+=
(conforme declarado por Scott Griffiths )Em classe
foo
, o__init__
método modifica o atributo de classe. É porque seself.bar += [x]
traduz paraself.bar = self.bar.__iadd__([x])
.__iadd__()
é para modificação local, portanto, ele modifica a lista e retorna uma referência a ela.Observe que a instância dict é modificada, embora isso normalmente não seja necessário, pois a classe dict já contém a mesma atribuição. Portanto, esse detalhe passa quase despercebido - exceto se você fizer um
foo.bar = []
depois. Aqui as instânciasbar
permanecem as mesmas graças ao fato mencionado.Na aula
foo2
, entretanto, o da classebar
é usado, mas não é tocado. Em vez disso, um[x]
é adicionado a ele, formando um novo objeto, comoself.bar.__add__([x])
é chamado aqui, que não modifica o objeto. O resultado é colocado na instância dict então, dando à instância a nova lista como um dict, enquanto o atributo da classe permanece modificado.A distinção entre
... = ... + ...
e... += ...
afeta também as atribuições posteriores:Você pode verificar a identidade dos objetos com
print id(foo), id(f), id(g)
(não se esqueça dos()
s adicionais se estiver no Python3).BTW: O
+=
operador é chamado de "atribuição aumentada" e geralmente se destina a fazer modificações no local, tanto quanto possível.fonte
As outras respostas parecem ter coberto, embora pareça valer a pena citá-las e referir-se às Tarefas Aumentadas PEP 203 :
...
fonte
fonte
Vemos que, quando tentamos modificar um objeto imutável (inteiro neste caso), o Python simplesmente nos dá um objeto diferente. Por outro lado, podemos fazer alterações em um objeto mutável (uma lista) e mantê-lo sempre o mesmo objeto.
ref: https://medium.com/@tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Consulte também o url abaixo para entender a cópia rasa e profunda
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
fonte