+=
파이썬 의 연산자가 목록에서 예기치 않게 작동하는 것 같습니다. 아무도 여기서 무슨 일이 일어나고 있는지 말해 줄 수 있습니까?
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
산출
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar
클래스의 모든 인스턴스에 영향을 미치는 것처럼 보이지만, foo = foo + bar
내가 기대하는 방식으로 작동 하는 것 같습니다.
이 +=
연산자를 “복합 할당 연산자”라고합니다.
답변
일반적인 대답은 특수 메서드 +=
를 호출 __iadd__
하려고 시도하고 사용할 수없는 경우 __add__
대신 사용하려고하는 것입니다. 따라서 문제는 이러한 특수 방법의 차이점입니다.
__iadd__
특별한 방법은 변이가에 작용하는 객체 즉, 인플레 이스 (in-place) 추가 할 수 있습니다. __add__
특별한 방법은 새로운 객체를 반환하고 또한 표준에 사용되는 +
연산자입니다.
따라서 +=
연산자가 __iadd__
정의 된 객체에 사용되면 객체가 제자리에서 수정됩니다. 그렇지 않으면 대신 일반을 사용하고 __add__
새 객체를 반환 하려고 합니다.
그렇기 때문에 목록과 같은 변경 가능한 유형 +=
의 경우 객체의 값이 변경되는 반면 튜플, 문자열 및 정수와 같은 변경 불가능한 유형의 경우 새 객체가 대신 반환됩니다 ( a += b
와 동일하게 됨 a = a + b
).
유형에 대한 지원을 모두 __iadd__
하고 __add__
당신 때문에 당신이 사용하는 하나 조심해야합니다. a += b
를 호출 __iadd__
하고 변경 a
하는 반면 a = a + b
새 개체를 만들고에 할당합니다 a
. 그들은 같은 작업이 아닙니다!
>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3] # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3] # a1 and a2 are still the same list
>>> b2
[1, 2] # whereas only b1 was changed
변경 불가능한 유형 (가없는 경우 __iadd__
) a += b
및 a = a + b
동등합니다. 이것은 +=
불변 유형에 사용할 수있게 해주는 것인데, 그렇지 않으면 +=
숫자와 같은 불변 유형에 사용할 수 없다고 생각할 때까지 이상한 디자인 결정 처럼 보일 수 있습니다!
답변
일반적인 경우에는 Scott Griffith의 답변을 참조하십시오 . 당신처럼 목록을 다룰 때 +=
연산자는 someListObject.extend(iterableObject)
. extend () 문서를 참조하십시오 .
이 extend
함수는 매개 변수의 모든 요소를 목록에 추가합니다.
할 때 foo += something
목록 foo
을 제자리에서 수정 하므로 이름이 foo
가리키는 참조를 변경하지 않고 목록 개체를 직접 변경합니다. 를 사용하면 foo = foo + something
실제로 새 목록을 만드는 것 입니다.
이 예제 코드는이를 설명합니다.
>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216
새 목록을에 다시 할당 할 때 참조가 어떻게 변경되는지 확인합니다 l
.
bar
인스턴스 변수 대신 클래스 변수와 마찬가지로 제자리에서 수정하면 해당 클래스의 모든 인스턴스에 영향을줍니다. 그러나 재정의 할 때 self.bar
인스턴스는 self.bar
다른 클래스 인스턴스에 영향을주지 않고 별도의 인스턴스 변수 를 갖게됩니다 .
답변
여기서 문제 bar
는 인스턴스 변수가 아닌 클래스 속성으로 정의된다는 것입니다.
에서는 메서드 foo
에서 클래스 속성이 수정되므로 init
모든 인스턴스가 영향을받습니다.
에서 foo2
인스턴스 변수는 (빈) 클래스 속성을 사용하여 정의되며 모든 인스턴스는 고유 한 bar
.
“올바른”구현은 다음과 같습니다.
class foo:
def __init__(self, x):
self.bar = [x]
물론 클래스 속성은 완전히 합법적입니다. 실제로 다음과 같이 클래스의 인스턴스를 만들지 않고도 액세스하고 수정할 수 있습니다.
class foo:
bar = []
foo.bar = [x]
답변
여기에는 두 가지가 포함됩니다.
1. class attributes and instance attributes
2. difference between the operators + and += for lists
+
연산자 __add__
는 목록 에서 메서드를 호출합니다 . 피연산자에서 모든 요소를 가져 와서 순서를 유지하는 요소를 포함하는 새 목록을 만듭니다.
+=
연산자 __iadd__
는 목록에서 메소드를 호출 합니다. iterable을 취하고 iterable의 모든 요소를 목록에 추가합니다. 새 목록 개체를 만들지 않습니다.
수업에서 foo
그 진술 self.bar += [x]
은 할당 진술이 아니지만 실제로는 다음과 같이 번역됩니다.
self.bar.__iadd__([x]) # modifies the class attribute
목록을 제자리에서 수정하고 목록 메서드처럼 작동합니다 extend
.
foo2
반대로 클래스 에서 init
메서드 의 할당 문
self.bar = self.bar + [x]
다음과 같이 분해 될 수 있습니다
. 인스턴스에는 속성이 없으므로 bar
(동일한 이름의 클래스 속성이 있음) 클래스 속성에 액세스하고 bar
추가 x
하여 새 목록을 만듭니다 . 이 진술은 다음과 같이 번역됩니다.
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
그런 다음 인스턴스 속성을 만들고 bar
새로 만든 목록을 할당합니다. 참고 bar
할당의 우항은 다른 bar
좌변에.
클래스의 인스턴스의 경우 foo
, bar
class 속성이 아닌 인스턴스 속성입니다. 따라서 클래스 속성에 대한 변경 사항 bar
은 모든 인스턴스에 반영됩니다.
반대로, 클래스의 각 인스턴스 에는 동일한 이름의 클래스 속성과 다른 foo2
고유 한 인스턴스 속성 bar
이 bar
있습니다.
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
이것이 문제를 해결하기를 바랍니다.
답변
많은 시간이 지났고 많은 정답이 들었지만 두 효과를 모두 묶는 답은 없습니다.
두 가지 효과가 있습니다.
- “특별한”, 아마도 눈에 띄지 않는 목록의 동작
+=
( Scott Griffiths에 의해 언급 됨 ) - 클래스 속성과 인스턴스 속성이 관련되어 있다는 사실 ( Can Berk Büder에 의해 언급 됨 )
class foo
에서 __init__
메서드는 클래스 속성을 수정합니다. 으로 self.bar += [x]
번역 되기 때문 self.bar = self.bar.__iadd__([x])
입니다. __iadd__()
내부 수정을위한 것이므로 목록을 수정하고 그에 대한 참조를 반환합니다.
인스턴스 dict가 수정되었지만 클래스 dict에 이미 동일한 할당이 포함되어 있으므로 일반적으로 필요하지 않습니다. 따라서이 세부 사항은 foo.bar = []
나중에 수행하는 경우를 제외하고는 거의 눈에 띄지 않습니다 . 여기에서 인스턴스 bar
는 말한 사실 덕분에 동일하게 유지됩니다.
클래스에서 foo2
, 그러나, 클래스의는 bar
사용하지만, 감동하지 않습니다. 대신 여기에 a [x]
가 추가되어 self.bar.__add__([x])
여기에서 호출 되는 것처럼 개체를 수정하지 않는 새 개체를 형성 합니다. 결과는 인스턴스 dict에 입력되어 인스턴스에 새 목록을 dict로 제공하는 반면 클래스의 속성은 수정 된 상태로 유지됩니다.
구별 ... = ... + ...
하고 ... += ...
이후뿐만 아니라 할당에 영향을
f = foo(1) # adds 1 to the class's bar and assigns f.bar to this as well.
g = foo(2) # adds 2 to the class's bar and assigns g.bar to this as well.
# Here, foo.bar, f.bar and g.bar refer to the same object.
print f.bar # [1, 2]
print g.bar # [1, 2]
f.bar += [3] # adds 3 to this object
print f.bar # As these still refer to the same object,
print g.bar # the output is the same.
f.bar = f.bar + [4] # Construct a new list with the values of the old ones, 4 appended.
print f.bar # Print the new one
print g.bar # Print the old one.
f = foo2(1) # Here a new list is created on every call.
g = foo2(2)
print f.bar # So these all obly have one element.
print g.bar
객체의 신원을 확인할 수 있습니다 print id(foo), id(f), id(g)
( ()
Python3을 사용하는 경우 추가 s를 잊지 마십시오).
BTW : +=
연산자는 “증강 할당”이라고하며 일반적으로 가능한 한 내부 수정을 수행하도록되어 있습니다.
답변
다른 답변은 Augmented Assignments PEP 203 을 인용하고 참조 할 가치가있는 것처럼 보이지만 거의 다룬 것처럼 보입니다 .
그들은 [증대 된 할당 연산자가] 작업이 완료되는 것을 제외하고, 정상 바이너리 형식과 같은 연산자를 구현`의 장소 ‘왼쪽은 일단 평가 왼쪽 측면 객체가 지원하는 그것을, 그 때.
…
파이썬에서 증강 할당의이면에있는 아이디어는 이진 연산의 결과를 왼쪽 피연산자에 저장하는 일반적인 관행을 작성하는 것이 더 쉬운 방법 일뿐만 아니라 문제의 왼쪽 피연산자가 자신의 수정 된 사본을 생성하는 것이 아니라 ‘자체적으로’작동해야한다는 것을 알고 있어야합니다.
답변
>>> elements=[[1],[2],[3]]
>>> subset=[]
>>> subset+=elements[0:1]
>>> subset
[[1]]
>>> elements
[[1], [2], [3]]
>>> subset[0][0]='change'
>>> elements
[['change'], [2], [3]]
>>> a=[1,2,3,4]
>>> b=a
>>> a+=[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
>>> a=[1,2,3,4]
>>> b=a
>>> a=a+[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4])
