operators math - Quando é "i+=x" diferente de "i=i+x" em Python?




symbol import (5)

Foi-me dito que += pode ter efeitos diferentes da notação padrão de i = i + . Existe um caso em que i += 1 seria diferente de i = i + 1 ?


Answers

Aqui está um exemplo que compara diretamente i += x com 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]

Se você está lidando apenas com literais, então i += 1 tem o mesmo comportamento que i = i + 1 .


Isso depende inteiramente do objeto i .

+= chama o método __iadd__ (se existir - voltando a __add__ se ele não existir) enquanto que + chama o método __add__ 1 ou o método __radd__ em alguns casos 2 .

De uma perspectiva API, __iadd__ deve ser usado para modificar objetos mutáveis no lugar (retornando o objeto que foi mutado) enquanto 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__ colocará a nova instância no namespace atual com o mesmo nome da instância antiga. Isso é por que

i = 1
i += 1

parece incrementar i . Na realidade, você obtém um novo inteiro e o atribui "em cima de" i - perdendo uma referência ao 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, desde b e a referência ao mesmo objeto, quando eu uso += on b , ele realmente muda b (e também vê essa mudança - Afinal, ele está referenciando a mesma lista). No segundo caso, no entanto, quando eu faço b = b + [1, 2, 3] , isso leva a lista que b está referenciando e concatena com uma nova lista [1, 2, 3] . Em seguida, ele armazena a lista concatenada no namespace atual como b - Sem considerar o que b era a linha anterior.

1 Na expressão x + y , se x.__add__ não é implementado ou se x.__add__(y) retorna NotImplemented e x e y possuem tipos diferentes , então x + y tenta chamar y.__radd__(x) . Então, no caso de você ter

foo_instance += bar_instance

Se Foo não implementar __add__ ou __iadd__ então o resultado aqui é o mesmo que

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

2 Na expressão foo_instance + bar_instance , bar_instance.__radd__ será tentado antes de foo_instance.__add__ se o tipo de bar_instance for uma subclasse do tipo foo_instance (por exemplo, issubclass(Bar, Foo) ). O racional para isso é porque Bar é, em certo sentido, um objeto de "nível mais alto" do que Foo então Bar deveria ter a opção de Foo o comportamento de Foo .


Nos bastidores, i += 1 faz algo assim:

try:
    i = i.__iadd__(1)
except AttributeError:
    i = i.__add__(1)

Enquanto i = i + 1 faz algo assim:

i = i.__add__(1)

Essa é uma simplificação exagerada, mas você tem a idéia: o Python dá aos tipos uma maneira de manipular += especialmente, criando um método __iadd__ bem como um __add__ .

A intenção é que os tipos mutáveis, como a list , sofram mutação em __iadd__ (e depois retornem para 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]

Como l2 é o mesmo objeto de l1 e você sofreu mutação de l1 , você também sofreu uma mutação de l2 .

Mas:

>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]

Aqui, você não l1 mutação l1 ; em vez disso, você criou uma nova lista, l1 + [3] , e rebote o nome l1 para apontar para ela, deixando l2 apontando para a lista original.

(Na versão += , você também estava rebatendo l1 , é só que nesse caso você estava rebatendo para a mesma list que já estava ligada, então você pode geralmente ignorar essa parte.)


import os
cmd = 'ls -al'
os.system(cmd)

Se você quiser retornar os resultados do comando, você pode usar os.popen . No entanto, isso está obsoleto desde a versão 2.6 em favor do módulo de subprocesso , que outras respostas abordaram bem.





python operators