@AshwiniChaudhary Essa é uma distinção bem sutil, considerando que i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]é True. Muitos desenvolvedores podem não perceber que isso id(i)muda para uma operação, mas não para a outra.
Kojiro
1
@kojiro - Embora seja uma distinção sutil, acho que é importante.
mgilson
@ mgilson é importante, e por isso senti que precisava de uma explicação. :)
Do ponto de vista da API, __iadd__é suposto ser usado para modificar objetos mutáveis no local (retornando o objeto que foi mutado), ao passo que __add__deve retornar uma nova instância de algo. Para objetos imutáveis , os dois métodos retornam uma nova instância, mas __iadd__colocam a nova instância no espaço para nome atual com o mesmo nome que a instância antiga. Isso é por que
i =1
i +=1
parece aumentar i. Na realidade, você obtém um novo número inteiro e o atribui "em cima de" i- perdendo uma referência ao número inteiro antigo. Nesse caso, i += 1é exatamente o mesmo que i = i + 1. Mas, com a maioria dos objetos mutáveis, é uma história diferente:
Como um exemplo concreto:
a =[1,2,3]
b = a
b +=[1,2,3]print a #[1, 2, 3, 1, 2, 3]print b #[1, 2, 3, 1, 2, 3]
comparado com:
a =[1,2,3]
b = a
b = b +[1,2,3]print a #[1, 2, 3]print b #[1, 2, 3, 1, 2, 3]
Observe como no primeiro exemplo, uma vez be areferenciar o mesmo objeto, quando eu uso +=em b, ele realmente muda b(e avê que a mudança também - Afinal de contas, ele está referenciando a mesma lista). No segundo caso, no entanto, quando eu faço b = b + [1, 2, 3], isso pega a lista que bestá fazendo referência e a concatena com uma nova lista [1, 2, 3]. Em seguida, ele armazena a lista concatenada no espaço para nome atual como b- Sem levar em conta qual bera a linha anterior.
1 Na expressão x + y, se x.__add__não for implementada ou se x.__add__(y)retorna NotImplementede xe ytêm tipos diferentes , em seguida, x + ytenta chamar y.__radd__(x). Então, no caso em que você tem
foo_instance += bar_instance
se Foonão implementar __add__ou __iadd__então o resultado aqui é o mesmo que
2 Na expressão foo_instance + bar_instance, bar_instance.__radd__será tentado antes foo_instance.__add__se o tipo de bar_instancefor uma subclasse do tipo de foo_instance(por exemplo issubclass(Bar, Foo)). O racional para isso é porque Baré em certo sentido um objeto "de alto nível" que Fooassim Bardeve obter a opção de substituir Fooo comportamento.
Bem, +=chama , __iadd__se existir , e volta a adicionar e refazer o contrário. É por isso que i = 1; i += 1funciona mesmo que não haja int.__iadd__. Mas além desse pequeno detalhe, ótimas explicações.
abarnert
4
@abarnert - eu sempre assumi que int.__iadd__acabou de ligar __add__. Estou feliz por ter aprendido algo novo hoje :).
mgilson
@abarnert - Acho que talvez seja completa , x + ychamadas y.__radd__(x)se x.__add__não existe (ou retornos NotImplementede xe ysão de tipos diferentes)
mgilson
Se você realmente quer ser completista, deve mencionar que o bit "se existe" passa pelos mecanismos getattr habituais, exceto por algumas peculiaridades com classes clássicas e pelos tipos implementados na API C. nb_inplace_addou sq_inplace_concat, e essas funções da API C têm requisitos mais rígidos do que os métodos dunder do Python e ... Mas não acho que isso seja relevante para a resposta. A principal distinção é que +=tenta fazer uma adição no local antes de voltar a agir como +, o que acho que você já explicou.
22813 abarnert #
Sim, suponho que você esteja certo ... Embora eu possa apenas recorrer à posição de que a API C não faz parte do python . É parte do Cpython :-P
mgilson 13/03
67
Sob as cobertas, i += 1faz algo assim:
try:
i = i.__iadd__(1)exceptAttributeError:
i = i.__add__(1)
Enquanto i = i + 1faz algo parecido com isto:
i = i.__add__(1)
Essa é uma pequena simplificação excessiva, mas você entendeu: o Python fornece aos tipos uma maneira de lidar +=especialmente, criando um __iadd__método e um __add__.
A intenção é que tipos mutáveis, como list, se transformem em __iadd__(e retornem self, a menos que você esteja fazendo algo muito complicado), enquanto tipos imutáveis, como int, simplesmente não o implementarão.
Por exemplo:
>>> l1 =[]>>> l2 = l1
>>> l1 +=[3]>>> l2
[3]
Porque l2é o mesmo objeto que l1, e você mutou l1, também modificou l2.
Mas:
>>> l1 =[]>>> l2 = l1
>>> l1 = l1 +[3]>>> l2
[]
Aqui, você não sofreu mutação l1; em vez disso, você criou uma nova lista l1 + [3]e recuperou o nome l1para apontá-lo, deixando l2apontar para a lista original.
(Na +=versão, você também estava religando l1, é só nesse caso que você estava religando da mesma forma listque já estava vinculada, portanto, geralmente você pode ignorar essa parte.)
se __iadd__realmente chamar __add__em caso de um AttributeError?
mgilson
Bem, i.__iadd__não liga __add__; é isso i += 1que chama __add__.
abarnert
errr ... Sim, foi isso que eu quis dizer. Interessante. Não sabia que isso era feito automaticamente.
mgilson
3
A primeira tentativa é realmente i = i.__iadd__(1)- iaddpode modificar o objeto no lugar, mas não precisa, e, portanto, espera-se retornar o resultado em ambos os casos.
LVC
Note que isto significa que operator.iaddas chamadas __add__sobre AttributeError, mas não pode religar o resultado ... então i=1; operator.iadd(i, 1)retorna 2 e folhas idefinido para 1. O que é um pouco confuso.
abarnert
6
Aqui está um exemplo que se compara diretamente i += xcom i = i + x:
def foo(x):
x = x +[42]def bar(x):
x +=[42]
c =[27]
foo(c);# c is not changed
bar(c);# c is changed to [27, 42]
+=
age comoextend()
no caso de listas.i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]
éTrue
. Muitos desenvolvedores podem não perceber que issoid(i)
muda para uma operação, mas não para a outra.Respostas:
Isso depende inteiramente do objeto
i
.+=
chama o__iadd__
método (se existir - recorrendo novamente__add__
se não existir), enquanto+
chama o__add__
método 1 ou o__radd__
método em alguns casos 2 .Do ponto de vista da API,
__iadd__
é suposto ser usado para modificar objetos mutáveis no local (retornando o objeto que foi mutado), ao passo que__add__
deve retornar uma nova instância de algo. Para objetos imutáveis , os dois métodos retornam uma nova instância, mas__iadd__
colocam a nova instância no espaço para nome atual com o mesmo nome que a instância antiga. Isso é por queparece aumentar
i
. Na realidade, você obtém um novo número inteiro e o atribui "em cima de"i
- perdendo uma referência ao número inteiro antigo. Nesse caso,i += 1
é exatamente o mesmo quei = i + 1
. Mas, com a maioria dos objetos mutáveis, é uma história diferente:Como um exemplo concreto:
comparado com:
Observe como no primeiro exemplo, uma vez
b
ea
referenciar o mesmo objeto, quando eu uso+=
emb
, ele realmente mudab
(ea
vê que a mudança também - Afinal de contas, ele está referenciando a mesma lista). No segundo caso, no entanto, quando eu façob = b + [1, 2, 3]
, isso pega a lista queb
está fazendo referência e a concatena com uma nova lista[1, 2, 3]
. Em seguida, ele armazena a lista concatenada no espaço para nome atual comob
- Sem levar em conta qualb
era a linha anterior.1 Na expressão
x + y
, sex.__add__
não for implementada ou sex.__add__(y)
retornaNotImplemented
ex
ey
têm tipos diferentes , em seguida,x + y
tenta chamary.__radd__(x)
. Então, no caso em que você temfoo_instance += bar_instance
se
Foo
não implementar__add__
ou__iadd__
então o resultado aqui é o mesmo quefoo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2 Na expressão
foo_instance + bar_instance
,bar_instance.__radd__
será tentado antesfoo_instance.__add__
se o tipo debar_instance
for uma subclasse do tipo defoo_instance
(por exemploissubclass(Bar, Foo)
). O racional para isso é porqueBar
é em certo sentido um objeto "de alto nível" queFoo
assimBar
deve obter a opção de substituirFoo
o comportamento.fonte
+=
chama ,__iadd__
se existir , e volta a adicionar e refazer o contrário. É por isso quei = 1; i += 1
funciona mesmo que não hajaint.__iadd__
. Mas além desse pequeno detalhe, ótimas explicações.int.__iadd__
acabou de ligar__add__
. Estou feliz por ter aprendido algo novo hoje :).x + y
chamadasy.__radd__(x)
sex.__add__
não existe (ou retornosNotImplemented
ex
ey
são de tipos diferentes)nb_inplace_add
ousq_inplace_concat
, e essas funções da API C têm requisitos mais rígidos do que os métodos dunder do Python e ... Mas não acho que isso seja relevante para a resposta. A principal distinção é que+=
tenta fazer uma adição no local antes de voltar a agir como+
, o que acho que você já explicou.Sob as cobertas,
i += 1
faz algo assim:Enquanto
i = i + 1
faz algo parecido com isto:Essa é uma pequena simplificação excessiva, mas você entendeu: o Python fornece aos tipos uma maneira de lidar
+=
especialmente, criando um__iadd__
método e um__add__
.A intenção é que tipos mutáveis, como
list
, se transformem em__iadd__
(e retornemself
, a menos que você esteja fazendo algo muito complicado), enquanto tipos imutáveis, comoint
, simplesmente não o implementarão.Por exemplo:
Porque
l2
é o mesmo objeto quel1
, e você mutoul1
, também modificoul2
.Mas:
Aqui, você não sofreu mutação
l1
; em vez disso, você criou uma nova listal1 + [3]
e recuperou o nomel1
para apontá-lo, deixandol2
apontar para a lista original.(Na
+=
versão, você também estava religandol1
, é só nesse caso que você estava religando da mesma formalist
que já estava vinculada, portanto, geralmente você pode ignorar essa parte.)fonte
__iadd__
realmente chamar__add__
em caso de umAttributeError
?i.__iadd__
não liga__add__
; é issoi += 1
que chama__add__
.i = i.__iadd__(1)
-iadd
pode modificar o objeto no lugar, mas não precisa, e, portanto, espera-se retornar o resultado em ambos os casos.operator.iadd
as chamadas__add__
sobreAttributeError
, mas não pode religar o resultado ... entãoi=1; operator.iadd(i, 1)
retorna 2 e folhasi
definido para1
. O que é um pouco confuso.Aqui está um exemplo que se compara diretamente
i += x
comi = i + x
:fonte