본문 바로가기
파이썬(Python)/문법 내용정리

함수 인자 전달

by HealingMusic 2021. 2. 7.

C언어나 C++을 공부하다 보면, 함수에 인자를 전달하는 방식으로 Call by value, 또는 Call by pointer이나 reference 등의 방식을 본 적이 있을 것이다. 파이썬에서는 이들과도 조금 다른 방식으로 인자를 전달하는데, 일반적으로 Call by object reference 라고 한다 (Call by Sharing이라고도 한다). 즉 객체의 형태로 인자를 전달하되, 레퍼런스, 즉 참조의 방식으로 전달한다. 또 변수가 수정 가능한지 아닌지에 따라 결과에 차이가 있다.

 

... 무슨 이야기인지 나도 처음에는 이해가 잘 가지 않았는데, 아래의 예시들을 통해 이해해 보자. int와 list, 두 가지의 자료형을 통해 설명해 보고자 한다.

 

목차

1. int (immutable 대표주자)

2. list (mutable 대표주자)

1. Int

모를 수도 있겠지만, int는 immutable한 자료형이다. 언뜻 보아서는 값을 변경할 수 있는 것처럼 보이지만, 실제로는 값의 변경이 아니라 다른 객체를 재할당하는 것이다. 이는 아래의 코드를 통해 알 수 있다.

>>> num = 10
>>> id(num)
2209951410768
>>> num = 11
>>> id(num)
2209951410800 # 아예 바뀌어 버렸다

만약 int가 mutable이었다면 값을 수정할 때 원래의 객체에서 값만을 바꾸었을 것이고, 메모리 주소가 바뀌지 않았을 것이다. 하지만 파이썬에서는 11을 저장하는 새로운 객체를 다른 메모리 주소에 저장한 후, 포인팅을 변경하는 방식으로 실행된다.

 

이를 토대로 아래의 코드를 분석해 보자.  아래는 정수를 0으로 바꾸는 함수와 실행문이다.

def change_to_zero(input : int) -> None:
    input = 0 # 입력받은 숫자를 0으로 바꾼다

num = 1
change_to_zero(num)
print(num) # result : 1

예상과 달리 num의 값이 바뀌지 않았는데, 왜 그런지 알아보자 (필자도 여기서 멘붕이 한 번 왔었다...ㅎ)

 

처음에 num이라는 변수명이 1이라는 객체를 가리키고 있었다. 이름표라고 생각하면 쉬운데, 1에 num이라는 이름표가 달려 있다고 생각하자.

그리고 change_to_zero 함수를 실행하는데, 이때 1이라는 객체는 참조의 형식으로 함수에 전달된다. 따라서 1이라는 객체에 전역 이름표 num, 함수 내부에서의 이름표인 input 두 개가 같이 붙어 있는 셈이다.

이 상황에서 input이 가리키는 객체를 0으로 바꾼다. 중요한 것은, int가 immutable하기 때문에, input 이름표가 붙어 있는 값을 0으로 '수정하는' 것이 아니라, 이름표를 0이라는 새로운 객체에 '옮겨 붙이는' 것이다. 따라서 함수 실행 이후 객체 1에는 num이라는 이름표가, 함수 내의 객체 0에는 input이라는 이름표가 붙게 된다.

따라서 print(num) 에서 num의 값은 0이 아니라 1이다.


2. List (mutable)

...아직은 잘 이해가 되지 않는 것 같다. list의 경우에서 한번 더 생각해 보자.

list는 mutable하기 때문에, 아래의 코드는 우리의 생각대로 잘 동작한다.

def change_to_zero(input : list) -> None:
    input[-1] = 0 # 마지막 원소를 0으로 바꾼다
    
list_ex = [1,2,3]
# [1,2,3] 이라는 객체에 list_ex라는 이름을 붙임

change_to_zero(list_ex)
# [1,2,3] 이라는 객체에 input이라는 이름표가 추가로 붙음 (함수 내부 한정)

print(list_ex) # result : [1,2,0]

위의 코드도 분석해 보자.

먼저 [1,2,3]객체에 list_ex라는 이름표가 붙었다. 이후 함수를 호출하여 [1,2,3]에 input이라는 이름표를 추가로 붙여 주고, input이 가리키는 객체의 마지막 원소를 0으로 수정한다.

여기서는 위와 달리 [1,2,0] 이라는 객체를 새로 생성하여 input이라는 이름표를 옮겨 붙이는 것이 아니라, input이름표가 붙어 있는 객체의 값을 '수정하는' 것이다.

이때 input 이름표와 list_ex 이름표는 같은 객체에 붙어 있는 두 개의 이름표이므로, input이 가리키는 값을 바꾸었을 때 자연스럽게 list_ex의 값도 바뀐다. 따라서 결과값은 [1,2,0] 이다.

 

그렇다면 마지막으로 아래의 코드를 보자.

def change_to_zero(input : list) -> None:
    input = [0,0,0] # 리스트를 [0,0,0] 으로 바꾼다

list_ex = [1,2,3]
change_to_zero(list_ex)
print(list_ex) 

위의 코드는 input의 마지막 원소를 0으로 바꾸는 것이 아니라, 리스트 자체를 [0,0,0]으로 바꾸는 것이다. 두 연산에는 중요한 차이가 있는데, 전자는 리스트를 수정하는 것이고, 후자는 리스트를 아예 재할당한다는 것이다. 결국 가장 처음의 int와 같은 경우인데, 따라서 함수 내에서 input이라는 이름표가 [0,0,0]으로 옮겨 붙기만 하는 결과가 되어 list_ex의 값은 변하지 않는다. 따라서 함수를 작성할 때는, 이 함수가 인자를 수정하는지, 또는 다른 것으로 바꾸어 버리는지 유의하며 작성할 필요가 있다.

'파이썬(Python) > 문법 내용정리' 카테고리의 다른 글

빗물 트래핑  (0) 2021.02.11
객체 in Python  (0) 2021.02.07
Collections 모듈 - Counter  (0) 2021.01.31
Type Hint (타입 힌트)  (0) 2021.01.29
Generator (제너레이터)  (0) 2021.01.28

댓글