A banner is created from here
"전문가를 위한 파이썬" 이라는 책을 읽던 중
정리하고 싶은 내용이 생겨 이 글을 작성합니다
파이썬의 복합 할당 연산자를
스펙에서는 Augmented assignment statements (확장 된 할당문) 이라 합니다
파이썬의 복합 할당 연산자는 다른 언어와는 다르게
단순히 축약 문법으로 동작하지 않습니다
문서에 따른 정확한 표현은 아래와 같습니다
An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated once. Also, when possible, the actual operation is performed in-place, meaning that rather than creating a new object and assigning that to the target, the old object is modified instead.
(죽이고 싶은 영어와의 10선)
요약하자면, 비슷하지만 정확한 동작은 다르다! 라는 점 입니다
자바스크립트의 단축 연산자가 단순한 문법적인 단축을 의미하는 것과 달리
파이썬은 문법적인 단축만을 의미하는 것은 아니라는 뜻이죠
"확장 된 할당문" 은 보통의 할당문과는 두 가지가 다릅니다
예를 들어 a += 1
의 경우에는
a 의 값을 한 번만 평가하여, 할당합니다
내부적으로 __iadd__()
메소드를 사용하고,
a = a + 1
은 __add__()
메소드를 사용합니다.
하지만 보통의 경우에는 add와 iadd 의 차이를 알기 힘듭니다
보통의 사칙연산과 비트연산을 지원합니다
a = 0
a += 1
print(a) // 1
일반적으로 아는 사칙연산과 비트연산의 축약 문법은 모두 지원합니다
augmented_assignment_stmt ::= augtarget augop (expression_list | yield_expression)
augtarget ::= identifier | attributeref | subscription | slicing
augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
| ">>=" | "<<=" | "&=" | "^=" | "|="
정확한 내용은 이 곳을, 연산자에 대한 자세한 내용은 이 곳을 참고하세요!
이 글을 쓰기 위해 문서를 읽다가 특이한 연산자 Symbol 을 알게 되었습니다
바로 @ ("at" operator) 인데요
numpy 배열의 행렬곱을 하기 위해 사용합니다
import numpy
x = numpy.ones(3) # array([1., 1., 1.])
y = numpy.ones(3) # array([1., 1., 1.])
z = x @ y
print(z) // 3
바로 위와 같이 행렬곱을 할 수 있습니다
관련 내용의 Proposal 은 PEP-465 를 참고하세요
보통의 확장된 연산자는 축약 문법과 비슷하게 동작 하지만
"가변값"에 대한 연산을 수행하게 되면 그 동작은 조금 달라집니다
파이썬의 mutable object 는 dict, list, bytesarray, array.array
등이 있습니다만
(사칙 연산) operator 를 지원하는 것은 list, bytesarray
이므로
이에 관한 설명입니다
a = tuple([1,2,3])
a[0] += 3
---------------------------------------------------------------------------
TypeError: Traceback (most recent call last)
<ipython-input-73-c8548dbc0a7e> in <module>
----> 1 a[0] += 3
TypeError: 'tuple' object does not support item assignment
tuple
은 기본적으로 불변 객체입니다
이렇게 기존 값을 변경하는 것이 불가능 합니다
그런데, 만약 tuple
안에 가변 시퀀스(list, bytesarray
)가 위치하면 어떻게 될까요?
In [74]: a = tuple([[], 1, 2])
In [75]: a
Out[75]: ([], 1, 2)
In [76]: a[0] += [1]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-76-2d8bcd3a4d75> in <module>
----> 1 a[0] += [1]
TypeError: 'tuple' object does not support item assignment
In [77]: a
Out[77]: ([1], 1, 2)
Out[77] 을 보시면, 빈 리스트에 1이 추가된 것을 볼 수 있습니다
분명히 TypeError
가 발생했는데, 어떻게 값이 추가 되었을까요?
위의 과정을 순서대로 나누어 보면 아래와 같습니다
tuple_obj = ([[], 2, 3])
temp = tuple_obj[0] # temp 는 재 할당을 위한 임시변수 입니다
temp += [1] # 이 부분 까지는 문제 없습니다
tuple_obj[0] = temp # TypeError!
3번째 라인의 코드까지는 리스트를 수정하는 것이기 때문에 문제가 없습니다
마지막 4번째 라인의 코드가, 튜플의 값을 수정하려 하므로 TypeError
가 발생합니다
그런데, 파이썬에서 list
값은 참조로 전달되고
이미 temp
라는 가변 변수는 값이 변경되었기 때문에
tuple_obj
의 0번째 값인 리스트에 값이 추가되어 버린 것입니다
위의 풀어쓴 예제는 아래와도 같습니다
temp = tuple_obj[0]
temp = temp.__iadd__([1])
tuple_obj[0] = temp # TypeError
+=
연산자의 경우, __iadd__()
라는 내장 메소드를 호출하고,
in-place (자신의 값을 변경) 형태로 동작합니다
list.extend
메소드와도 동일하게 동작합니다
__iadd__()
아래와 같은 함수가 있습니다
def expand_inplace(a, b):
a += b
return a
def expand(a, b):
a = a + b
return a
얼핏 보기에는 expand_inplace
함수와 expand
함수가
동일하게 동작할 것이라 예상됩니다
실제로 불변값의 경우는 같습니다
In [86]: c = expand_inplace(1, 2)
In [87]: c
Out[87]: 3
In [88]: c = expand(1, 2)
In [89]: c
Out[89]: 3
In [90]: c = expand_inplace(tuple([1,2,3]), tuple([4,5,6]))
In [91]: c
Out[91]: (1, 2, 3, 4, 5, 6)
하지만, 가변 시퀀스의 경우에는 다르게 동작합니다
In [93]: a = [1,2,3]; b = [4,5,6]
In [94]: c = expand_inplace(a, b)
In [95]: c
Out[95]: [1, 2, 3, 4, 5, 6]
In [96]: a
Out[96]: [1, 2, 3, 4, 5, 6]
In [97]: a = [1,2,3]; b = [4,5,6]
In [98]: c = expand(a, b)
In [99]: c
Out[99]: [1, 2, 3, 4, 5, 6]
In [100]: a
Out[100]: [1, 2, 3]
c의 값은 동일하지만, 인자로 넘긴 a
변수의 값이
expand_inplace
함수의 경우에는 변경되었습니다
__iadd__()
의 경우에는 좌항의 변수에 대한 평가를 한 번만 합니다
expand_inplace
함수 내의 a
라는 변수를 평가할 때
a
는 local scope 에 존재하지 않기 때문에
함수의 인자인 a 에다가 값을 재 할당한 것입니다
함수의 실행에 대해 좀 더 자세히 알아보면
In [101]: dis.dis(expand_inplace)
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 INPLACE_ADD
6 STORE_FAST 0 (a)
3 8 LOAD_FAST 0 (a)
10 RETURN_VALUE
In [102]: dis.dis(expand)
6 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 STORE_FAST 0 (a)
7 8 LOAD_FAST 0 (a)
10 RETURN_VALUE
각각의 4번째 실행부에서, INPLACE_ADD 와 BINARY_ADD 로
바이트코드에서의 실행부가 다른것을 확인할 수 있습니다
따라서, a += 1
를 완전한 a = a + 1
이라고 할 수는 없는 것입니다
튜플 안의 리스트 값의 변경에 대해서는 꾸준히 논의된 주제로
그 내용은 Python FAQ 에도 언급 되어 있으니
한 번 읽어보시길 추천합니다!
파이썬이 어떻게 변수를 탐색하고 코드를 실행하는지
연산자와 Augmented assignment statements 는 무엇인지에 대해
자세히 공부하게 된 좋은 챕터 였습니다!
의식의 흐름대로 작성한 긴 글을
혹시 여기까지 읽어주신 분이 계시다면 감사합니다!
문제가 있다면 댓글 부탁 드립니다