source

+=가 목록에서 예기치 않게 작동하는 이유는 무엇입니까?

manycodes 2023. 7. 10. 22:32
반응형

+=가 목록에서 예기치 않게 작동하는 이유는 무엇입니까?

+=python의 연산자가 목록에서 예기치 않게 작동하는 것 같습니다.여기서 무슨 일이 일어나고 있는지 누가 말해줄 수 있나요?

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__특별한 방법은 임플레이스 추가를 위한 것입니다. 즉, 그것은 그것이 작용하는 객체를 변형시킵니다.__add__특수 메소드는 새 개체를 반환하며 표준에도 사용됩니다.+교환입니다.

래서그 ▁the 때.+=는 는다가객사용다됩니체에진연음산을이 있는 됩니다.__iadd__개체가 제자리에서 수정되었다고 정의했습니다.그렇지 않으면 플레인을 대신 사용하려고 합니다.__add__새 개체를 반환합니다.

그렇기 때문에 목록과 같은 가변 유형에 대해+= 튜플,, 의 경우 새 됩니다(" 의체 값변반면을경는객하다문자열니대됩반경튜유객새환같불형정신변우의의체로가은수운와플▁changes▁is,객▁object다▁like▁instead의값체반대됩▁returned니환정'신▁the▁a▁whereas▁(▁fora += b와 동등해집니다.a = a + b).

▁both를 모두 지원하는 __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

가 불사유형경없(우가는자변용사경)(우▁you▁ 경우).__iadd__)a += b그리고.a = a + b동등합니다.이것이 바로 당신이 사용할 수 있는 것입니다.+=유형에 결정으로 수 . 그렇지 수 을 고려하기 까지는.+=숫자와 같은 불변의 유형에서!

일반적인 경우 스콧 그리피스의 답변을 참조하십시오.하지만 당신과 같은 목록을 다룰 때,+=는 연자는의약다니입자산다의 입니다.someListObject.extend(iterableObject)확장()의 설명서를 참조하십시오.

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

+은 " 교이호다니합출환원▁the다▁operator▁calls니"라고 부릅니다.__add__.피연산자에서 모든 요소를 가져와서 순서를 유지하는 요소를 포함하는 새 목록을 만듭니다.

+= 호출 환화전교__iadd__리스트에 있는 방법.반복 가능한 항목을 사용하고 반복 가능한 항목의 모든 요소를 제자리에 목록에 추가합니다.새 목록 개체를 만들지 않습니다.

에.fooself.bar += [x]할당문은 아니지만 실제로는 로 번역됩니다.

self.bar.__iadd__([x])  # modifies the class attribute  

에서 수정하고 합니다.extend.

에.foo2 대로배, 는술진서정에 .init방법

self.bar = self.bar + [x]  

다음과 같이 분해할 수 있습니다.
" 인턴스특없습다니"이 없습니다.bar이름의 클래스 속성에 .bar 추하여새만목다듭니록을가ending를 추가하여 새 .x 문장은 다음과같이 됩니다.

self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute 

속성 " 그다인스속만듭다니성을스턴런bar새로 만든 목록을 할당합니다.:bar가 과의는 RH다와다니릅과 .bar왼쪽에

클래스 클스인의경우스의 .foo,bar인스턴스 속성이 아닌 클래스 속성입니다. 속성 ▁to▁▁change에 대한 모든 변경 사항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 []  

이것으로 모든 것이 해결되기를 바랍니다.

많은 시간이 지나고 많은 옳은 말들이 나왔지만, 두 가지 효과를 묶은 답은 없습니다.

두 가지 효과가 있습니다.

  1. "특별한", 어쩌면 눈에 띄지 않는 목록의 행동.+=(Scott Griffiths가 언급한 바와 같이)
  2. 인스턴스 속성뿐만 아니라 클래스 속성이 관련되어 있다는 사실(캔 Berk Büder에 의해 언급됨)

에.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 

를 를 여 개 체 ID 있 수 니 다 습 확 할 인 사 용 하 의 ▁with ▁objects ▁of ▁the ▁identity ▁you ▁verify 다 ▁the 를 니 있 ▁can 습 를 수용 사print id(foo), id(f), id(g) (추가사항잊마십오시지을마오추시십▁()를 잊지 마세요.()(Python3)에 있는 경우).

BTW:+=연산자를 "임의 할당"이라고 하며 일반적으로 가능한 한 장소에서 수정을 수행하기 위한 것입니다.

추가 과제 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])
>>> a = 89
>>> id(a)
4434330504
>>> a = 89 + 1
>>> print(a)
90
>>> id(a)
4430689552  # this is different from before!

>>> test = [1, 2, 3]
>>> id(test)
48638344L
>>> test2 = test
>>> id(test)
48638344L
>>> test2 += [4]
>>> id(test)
48638344L
>>> print(test, test2)  # [1, 2, 3, 4] [1, 2, 3, 4]```
([1, 2, 3, 4], [1, 2, 3, 4])
>>> id(test2)
48638344L # ID is different here

우리는 불변 객체(이 경우 정수)를 수정하려고 하면 Python이 대신 다른 객체를 제공한다는 것을 알 수 있습니다.반면에 우리는 변경 가능한 개체(목록)를 변경할 수 있으며 전체적으로 동일한 개체로 유지할 수 있습니다.

ref : https://medium.com/ @tyastropheus/cisco-module-i-memory-management for muttable-module-module-21507d1e5b95

또한 아래 URL을 참조하여 얕은 복사와 딥 복사를 이해하십시오.

https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/

listname.extend()에 잘 합니다 :) :)

언급URL : https://stackoverflow.com/questions/2347265/why-does-behave-unexpectedly-on-lists

반응형