PS/Python

[Python] 다중 할당 과 passed by assignment

램램 2023. 1. 26. 05:13

Python의 다중 할당 개념이 헷갈려서 정리하는 글

Python은 아래의 구문과 같이 동시에 여러 변수에 값을 할당하는 다중 할당을 지원한다.

a, b = 1, 2

이렇게 동시에 a와 b에 값을 넣을 수 있다.

그런데 만약 동시에 같은 객체에 다중할당을 사용한다면 어떻게 될까?
다음과 같은 linked list 의 node class에서 다중할당을 한다고 해보자.

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

# list1 : 1 -> 3 -> 5
# list2 : 2 -> 4 -> 6

list1, list1.next = list2, list1

주석과 같이 list1 과 list2에 값이 들어가 있다고 할때,

list1 에는 list2를 할당했으니 2 → 4- > 6 이 되고, list1.next 은 1 → 3- > 5가 들어가서 최종적으로 list1의 값은 2→ 1 → 3→ 5 가 된다.

다중할당은 이런 연산을 동시에 처리시킨다.

조금 더 헷갈리는 예제를 보자.

list1 = 1, list2 = 2 → 3 일 때

# 1.
list1, list1.next , list2 = list2, list1, list2.next

# 2.
list1, list1.next = list2, list1
list2 =  list2.next

1번과 2번 방법은 각각 다른 실행 결과를 낸다.

1번은 앞에서 보았듯 list1에 2→ 3 , list1.next에 1, list2에 3이 들어가는 연산을 동시에 처리하므로 최종결과는

list1 = 2 → 1 , list2 = 3 이 된다.

그렇다면 2번은?

2번은 언뜻 보면 같은 코드처럼 보이지만 2줄로 나눠졌기 때문에 동시에 처리되지 않는다.

첫번째 줄의 결과는동일하게 list1에 2→ 1 이 할당된다.

그런데 list2는 뭔가 예상과 다르게 3이 아닌 1이 들어가게 된다.

첫번째 줄의 list1 = list2 라는 파트 때문에 두 리스트가 동일한 참조가 되기 때문이다.

이러한 결과는 파이썬이 pass-by-assignment 라는 방식을 택하고 있어서다.

이는

  1. 파이썬에서 parameter를 패스하는 것은 실제로는 그 객체에 대한 참조를 패스한다.
  2. 어떤 데이터 타입은 mutable 하고 (ex. list 등) 나머지는 그렇지 않다 (ex. int, string )

를 의미한다고 한다.

이 스택오버플로 의 답변의 예시를 사용했다.

How do I pass a variable by reference?

list 타입 - append

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Output :

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

이 함수에서는 list를 인자로 받긴 하지만 내부에서 append를 할 뿐 리턴하지는 않는다.

그러나 함수가 실행된 후 list를 출력해보면 함수 내부에서 추가한 ‘four’가 함수 바깥에서도 존재하는 것을 볼 수 있다. 이는 list 는 mutable 하기 때문에 함수의 인자로 패스되는 outer_list 가 카피가 아닌 동일한 객체에 대한 참조이기 때문이다. 따라서 함수 내부의 the_list 는 전달받은 outer_list 의 참조이고, 함수 밖에서도 그 참조에 대한 연산의 결과가 그대로 반영된다.

list 타입 - set

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Output :

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

이번엔 함수 내에서 인자로 받은 list 에 아예 새로운 list를 할당해버렸다. 그런데 함수의 매개변수인 the_list 와 함수 내에서 값을 할당받는 the_list 는 같은 값이 아니기 때문에 새로운 리스트를 가리키게 되어도 함수 밖의 outer_list 의 값은 바뀌지 않는다.

더 명확한 이해를 위해 immutable한 string 타입의 예시와 비교해보자.

String 타입

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Output :

before, outer_string = It was many and many a year ago
got It was many and many a year ago # 함수 내부에서 get한 인자
set to In a kingdom by the sea # 함수 내부에서 set
after, outer_string = It was many and many a year ago

이 함수에서는 string의 값을 바꾸지만 해당 연산이 함수 밖에서는 적용되지 않는다.

string은 immutable한 타입이기 때문에 함수 내에서 the_string 의 값을 바꿀 수 없다.

 

그림 요약

 

+ ) 추가 자료

http://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

 

Programming FAQ

Contents: Programming FAQ- General Questions- Is there a source code level debugger with breakpoints, single-stepping, etc.?, Are there tools to help find bugs or perform static analysis?, How can ...

docs.python.org