파이썬에서 "i + = x"는 "i = i + x"와 언제 다릅니 까?


212

나는 +=표준 표기법과는 다른 효과를 가질 수 있다고 들었습니다 i = i +. 하는 경우가 i += 1다른 것은 i = i + 1?


7
+=extend()목록의 경우 처럼 작동 합니다.
Ashwini Chaudhary

12
@AshwiniChaudhary 그건 미묘한 차이 i=[1,2,3];i=i+[4,5,6];i==[1,2,3,4,5,6]입니다 True. 많은 개발자들이 id(i)한 작업에 대한 변경 사항을 알지 못할 수도 있지만 다른 작업에는 변경 되지 않을 수도 있습니다 .
kojiro

1
@kojiro-미묘한 차이이지만 중요한 것 같습니다.
mgilson

@ mgilson 그것은 중요하므로 설명이 필요하다고 느꼈습니다. :)
kojiro 2014 년

1
: 두 개의 자바에서의 차이에 관한 관련 질문 stackoverflow.com/a/7456548/245966
jakub.g

답변:


317

이것은 전적으로 객체에 의존합니다 i.

+=__iadd__메소드를 호출합니다 (존재하는 경우- __add__존재하지 않는 경우 폴백) +. __add__메소드 1 또는 메소드를 호출하는 __radd__경우가 있습니다 2 .

API 관점에서 볼 때 __iadd__변경 가능한 객체를 수정 (변경된 객체 반환)하는 데 사용되는 반면 새로운 인스턴스__add__반환해야 합니다. 들어 불변의 객체, 두 가지 방법은 새로운 인스턴스를 반환하지만, 이전 인스턴스가 가진 것과 같은 이름으로 현재 네임 스페이스의 새로운 인스턴스를 둘 것이다. 이는 이유__iadd__

i = 1
i += 1

증가하는 것 같습니다 i. 실제로 새 정수를 가져 와서 "정수"에 할당 i하면 이전 정수에 대한 하나의 참조가 손실됩니다. 이 경우와 i += 1정확히 동일합니다 i = i + 1. 그러나 가장 가변적 인 객체의 경우 다른 이야기입니다.

구체적인 예로서 :

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]

다음과 비교 :

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

on을 사용할 때 첫 번째 예제 에서 동일한 객체를 참조 b하고 a참조하면 실제로 변경됩니다 (그리고 그 변경 사항도 볼 수 있습니다-결국 동일한 목록을 참조합니다). 그러나 두 번째 경우, 내가 할 때 이것은 참조 하는 목록을 가져 와서 새로운 목록과 연결합니다 . 그 다음으로 현재 이름 공간에 연결된 목록을 저장 - 무엇에 대한 어떤 관련하여 이전에 선이었다.+=bbab = b + [1, 2, 3]b[1, 2, 3]bb


1 발현은 x + y, 경우는 x.__add__구현되지 않은 경우 또는 x.__add__(y)반환 NotImplemented x와는 y다른 유형을 가지고 , 다음 x + y호출을 시도합니다 y.__radd__(x). 그래서, 당신이 가진 경우

foo_instance += bar_instance

경우 Foo구현하지 않는 __add__또는 __iadd__여기에 결과는 동일하다

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

식에서는 foo_instance + bar_instance, bar_instance.__radd__이전에 시도 될 foo_instance.__add__ 경우 의 타입 bar_instance의 유형의 서브 클래스 foo_instance(예 issubclass(Bar, Foo)). 이에 대한 합리적인 이유 Bar는 어떤 의미에서는 "상위 레벨"객체이기 Foo때문에 동작 Bar을 재정의하는 옵션을 가져와야하기 때문 Foo입니다.


18
글쎄, 존재하면+= 호출 __iadd__ 하고 , 그렇지 않으면 추가 및 리 바인딩으로 넘어갑니다. 그것이 i = 1; i += 1없는 경우에도 작동하는 이유 int.__iadd__입니다. 그러나 그 작은 니트 외에는 훌륭한 설명이 있습니다.
abarnert

4
@abarnert-항상 int.__iadd__방금 호출 했다고 가정했습니다 __add__. 오늘 새로운 것을 배웠습니다 :).
mgilson

@abarnert - 나는 할 어쩌면 생각 완료 , x + y전화를 y.__radd__(x)하는 경우 x.__add__(또는 반환 존재하지 않는 NotImplementedxy다른 유형의 수 있습니다)
mgilson

실제로 completist가되고 싶다면, "존재한다면"비트가 일반적인 getattr 메커니즘을 거치게된다는 점을 언급해야합니다. 단, 고전적인 클래스가있는 단점을 제외하고 C API에서 구현 된 유형은 대신 nb_inplace_add또는 sq_inplace_concatC API 함수는 Python dunder 메소드보다 엄격한 요구 사항을 가지고 있지만 ... 대답과 관련이 있다고 생각하지 않습니다. 주요 차이점은 +=과 같이 행동하기 전에 전체 추가 를 시도한다는 것 +입니다.
abarnert

네, 당신이 옳다고 생각합니다 ... C API가 python의 일부가 아니라는 입장으로 되돌아 갈 수는 있습니다. 의 그것의 부분 으로 CPython - P
mgilson

67

표지 아래에서 i += 1다음과 같이하십시오.

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

하지만이 i = i + 1같은 무언가를 :

i = i.__add__(1)

이것은 약간 지나치게 단순화되었지만 아이디어를 얻습니다. 파이썬은 유형 뿐만 아니라 메소드를 +=작성하여 특수하게 처리 할 수있는 __iadd__방법을 제공합니다 __add__.

의도는와 같은 가변 유형 list은 스스로 변이하고 __iadd__( self매우 까다로운 작업을 수행하지 않으면 return ),과 같은 불변 유형 int은 구현하지 않을 것입니다.

예를 들면 다음과 같습니다.

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

때문에 l2같은 객체이다 l1, 당신은 돌연변이 l1, 당신은 또한 돌연변이 l2.

그러나:

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

여기, 당신은 돌연변이하지 않았다 l1; 대신 새 목록 인을 만들고 원래 목록을 가리키면서 l1 + [3]이름 l1을 리바운드 l2합니다.

( +=버전 에서는 리 바인드 l1했습니다.이 경우 list이미 바인딩 된 것과 동일 하게 리 바인드 했기 때문에 일반적으로 해당 부분을 무시할 수 있습니다.)


않습니다 __iadd__실제로 전화 __add__의 경우에 AttributeError?
mgilson

음, i.__iadd__호출하지 않습니다 __add__; 그건 i += 1그 전화를 __add__.
abarnert

어 ... 그래, 그게 내 뜻이야 흥미 롭군 나는 그것이 자동으로 이루어 졌다는 것을 몰랐다.
mgilson

3
첫 번째 시도는 실제로 i = i.__iadd__(1)- 객체를 제자리에 수정할 iadd 있지만 반드시 그럴 필요는 없으며, 어느 경우이든 결과를 반환 할 것으로 예상됩니다.
lvc

이것은 on 을 operator.iadd호출 하지만 결과를 리 바인드 할 수 없다는 것을 의미합니다 . 따라서 2를 반환하고로 설정된 상태로 둡니다 . 약간 혼란 스럽습니다. __add__AttributeErrori=1; operator.iadd(i, 1)i1
abarnert

6

여기서 직접 비교 예이다 i += x으로는 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]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.