Programming Language/Python

파이썬 함수 호출시 참조 호출 방법(Call by assignment)

HaneeOh 2023. 3. 19. 19:52

기본적으로 c언어에서는 함수에 인자를 넘겨줄 때 두 가지 방법을 사용한다.

 

C언어

Call by value

함수에 값을 복사해서 전달하는 방식이다.

인자로 전달되는 변수의 값을 함수의 파라미터에 저장한다.

이때 인자로 전달한 변수와 함수의 파라미터는 별개의 변수가 되며, 서로 영향을 미치지 않는다.

 

#include <stdio.h>

void swap(int a, int b)
{
	int temp;
	
	temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a, b;
	
	a = 10;
	b = 20;
	
	printf("swap 전 : %d %d\n", a, b);
	
	swap(a, b);
	
	printf("swap 후 : %d %d\n", a, b);
	
	return 0;
}

즉 main에서의 a, b와 swap에서의 a, b는 완전히 별개의 변수이다.

 

// 결과

swap 전 : 10 20
swap 후 : 10 20

swap 함수 내부에서 값을 바꾸어도 함수 밖에서는 바뀌지 않는 것을 확인할 수 있다.

 

 

 

Call by reference

함수에서 값을 전달하는 대신 주소값을 전달하는 방식이다.

사실 엄밀히 따지자면 C언어의 참조 방식은 주소값 자체를 "복사"해서 넘겨주는 것이라서 call by value라고 한다. 또 이렇게 주소값을 복사해서 넘겨주는 방식을 call by address라고 부른다고 한다.

하지만 결과적으로는 call by reference처럼 사용하기 때문에 일반적으로 call by reference라고 설명한다.

(나도 학부 때 이렇게 배웠던 것 같다.)

#include <stdio.h>

void swap(int *a, int *b)
{
	int temp;

	temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int a, b;

	a = 10;
	b = 20;

	printf("swap 전 : %d %d\n", a, b);

	swap(&a, &b);

	printf("swap 후 : %d %d\n", a, b);

	return 0;
}

이번에는 인자로 넘겨주는 a, b앞에 & 표시가 붙어있다. 변수의 값을 넘겨주는 것이 아니라 변수의 값이 저장된 메모리 주소를 넘겨주고 있는 것을 알 수 있다.

swap 내부에서도 포인터 변수 *a, *b를 통해 메모리 주소에 저장된 값에 접근하므로 원래 변수에 저장된 값도 변경된다.

// 결과

swap 전 : 10 20
swap 후 : 20 10

 

 

 

Python

그렇다면 파이썬에서는 어떤 참조 호출 방식을 사용할까?

바로 Call by assignment라는 방식이다.

넘겨지는 객체의 종류에 따라 Call by Reference 또는 Call by Value가 결정된다.

 

Call by assignment

1. immutable 자료형: Call by Value

int, float, str, tuple

단일값이거나 static 속성

함수에 인자로 넘겨줄 때 call by value로 넘어간다. 함수 내부에서 값이 바뀌어도 외부의 변수에 영향이 없다.

 

2. mutable 자료형: Call by Reference

list, dict, set

함수에 인자로 넘겨줄 때 object의 주소가 넘어간다. 함수 내부에서 object의 element를 변경할 수 있다.

 

 

꼭 함수를 호출하지 않아도 이를 확인할 수 있다.

다음의 코드를 보자.

list1 = [1,2,3,4]
print("original list", id(list1))
list1.append(5)
print("new list", id(list1))

tuple1 = (1,2,3,4)
print("original tuple", id(tuple1))
tuple2 = tuple1 + (5,)
print("new tuple", id(tuple2))
# 결과

original list 4368266432
new list 4368266432
original tuple 4369138208
new tuple 4368232944

mutable한 리스트는 값을 추가했어도 메모리 주소가 변경되지 않는 반면

immutable한 튜플은 값을 추가했더니 새로운 메모리 주소에 저장된 것을 확인할 수 있다.

 

Mutable한 객체가 새로 할당되는 경우

그러나 mutable한 객체라고 해서 무조건 함수 내부의 변화가 함수 외부에도 적용되는 것은 아니다.

다음의 예를 보자.

def test(lst):
    print("original list", id(lst))
    lst.append(5)
    print("append list", id(lst))
    lst[0] = 100
    print("index list", id(lst))
    lst = lst + [6]
    print("new list", id(lst))

lst = [1, 2, 3, 4]
print("before list", id(lst)) # [1, 2, 3, 4]
test(lst)
print("after list", id(lst)) # [100, 2, 3, 4, 5]
before list 4373853376
original list 4373853376
append list 4373853376
index list 4373853376
new list 4374684352
after list 4373853376

리스트의 메모리 주소를 확인해보니 new list에서 변경되었다. 왜 그럴까?

 

리스트에 append하거나 element를 변경할 때는 원래의 리스트에 접근하지만

새로운 element를 더해서 할당할 때는 원래의 리스트를 참조하지 않고 새로운 메모리 영역에 값을 할당한다.

그래서 이때부터 함수 내부의 리스트는 별개의 리스트가 되어 외부에 영향을 주지 않는다.

 

함수 호출 이후 리스트의 주소를 찍어보면 이전의 메모리 주소와 동일한 것을 확인할 수 있다.

그리고 리스트도 새로운 할당 이전 [100, 2, 3, 4, 5]로 남아 있다.

 

이러한 call by reference는 잘 사용하면 유용할 수 있겠지만 할당 과정에서 실수가 생길 수 있기 때문에 

되도록이면 return 값으로 객체의 값을 변경하도록 하자...!

 

 

 

 

참고 자료

https://edu.goorm.io/learn/lecture/201/한-눈에-끝내는-c언어-기초/lesson/1271949/call-by-value-call-by-reference