본문 바로가기

Python

Python - Call by Object Reference

프로그래밍 공부를 하다보면, Call by Value와 Call by Reference에 대해서 많이 듣게 되고 해당 개념이 매우 중요하다는 것은 한번쯤 들어봤을 것이다. 이번 기회에 이 둘의 차이점과 함께 파이썬은 어떤 방식을 사용하고 있는지를 확인해보자.

사실 Call by '_____' 는 함수의 Argument를 전달하는 방식에 따라서 구분된다. 즉, 어떤 함수 func(param1, param2) 가 있다고 할 때 이 함수의 변수를 인자로 전달할 때 어떤 방식으로 전달하냐에 따라서 구분된다는 것이다. Python의 경우 Call by Object Reference 혹은 Call by Assignment 라는 방식을 사용한다. 이 방식은 passing되는 객체의 타입에 따라서 인자를 전달하는 방식이 Call by ValueCall by Reference로 나뉘는 방식이다. 그렇다면 Call by Value와 Call by Reference란 무엇일까?

 

Call by Value & Call by Reference


이 두 방식은 함수의 인자로 Value를 복사해서 넘겨주는지 아니면 그 값의 주소를 넘겨주는지에 따라서 구분된다. 이렇게만 본다면 당연히 헷갈릴 수 밖에 없다. 따라서 지금부터 더 자세한 설명과 함께 C++ 예제를 통해서 살펴보겠다. 참고로 Python의 경우 이 둘을 모두 확인해볼 수 있지만, 우선은 최대한 헷갈리지 않으면서 이 두방식을 확연히 구분할 수 있는 C++을 통해 확인해 본 후 아래서 python을 통해 다시 확인해보도록 하겠다.

우선은 앞으로 계속 보게 될 actual parameter와 formal parameter에 대해서 간단히 살펴보겠다. 우리에게는 아마도 prameter와 argument의 차이로 좀 더 익숙할 것이다. 우리가 함수를 정의할 때, 함수의 파라미터로 설정한 것들을 Parameter 혹은 Formal Parameter라고 부른다. 그리고 실제 이 함수를 호출할 때 파라미터로 넘겨주는 실제 값을 인자 Argument 혹은 Actual Parameter라고 부른다.

https://getkt.com/blog/types-of-function-arguments-in-python/

Call by Value

call by value방식은 actual parameter의 값을 복사해서 formal parameter로 넘겨주는 방식이다. 즉 어떤 함수가 call by value에 의해 동작한다고 할 때, 이 함수가 호출되면 이 함수의 Argument로 넣어준 실제 값들을 복사해서 실제 함수의 formal parameter 즉 함수 내로 들어가는 파라미터로 넣어주게 된다. 이 때 값을 복사해서 넣어주기 때문에 저 두 파라미터들은 메모리상에서 서로 다른 위치에 저장된다. 따라서 함수 안에서 formal parameter에 대해서 어떤 변화를 가하더라도, 실제 value인 actual parameter에는 변화가 생기지 않는 것이다. ( 주소가 다르다. 다른 value다.) 즉, 함수를 호출할 때 각 변수의 값을 복사해서 일종의 더미변수들로 호출된 함수의 인자로 넘겨주는 것이다.

// call by value

#include <iostream>

// Function Prototype
void swapx(int x, int y);

// Main function
int main()
{
    int a = 10, b = 20;

    // Pass by Values
    swapx(a, b);

    std::cout >> "a = " a >> "b =" b >>std::endl;

    return 0;
}

// Swap functions that swaps
// two values
void swapx(int x, int y)
{
    int t;

    t = x;
    x = y;
    y = t;

    std::cout >> "x = " x >> "y =" y >>std::endl;
}


Output:
x=20 y=10
a=10 b=20

// 출처 : geeksforgeeks
// https://www.geeksforgeeks.org/difference-between-call-by-value-and-call-by-reference/

해당 예시를 통해 확인해보자. 우리는 swap의 기능을 하는 swapx라는 함수를 통해서 call by value를 확인해보려 한다. 지금 swapx의 formal parameter가 int x, int y처럼 int value를 그대로 넘기는 call by value방식의 함수임을 확인할 수 있다. 그렇다면 우리가 main 함수에서 swapx 함수를 호출했을 때, 인자로 넘어간 변수 a,b의 값은 복사되어서 formal parameter인 x, y로 들어가게 되고, 복사되기 때문에 실제 파라미터 a,b와 formal prameter x,y는 메모리 상의 다른 주소를 가질 것이다. 그러면 xy에 해당하는 값이 바뀌는 swap이 일어난 후 swapx 함수내에서 x, y에 대해 출력을 하게 될 경우 두 값이 바뀌었으므로 x = 20 , y= 10이 출력되게 된다. 이러한 swapx함수가 호출된 후에 main함수에서 a, b의 값을 출력해볼 경우 이 두 값은 x, y와는 다른 메모리 주소를 가지고 있었기에 실제 변수 a,b의 위치에 해당되는 값에는 어떤 변화도 없었을 것이다. 따라서 ab는 그대로 10 , 20으로 출력되게 된다.

 

Call by Reference

반면 call by reference 방식의 경우 주소들을 사용한다. 일반적으로 이 주소에 접근하게 될 경우 실제 값을 확인, 조작할  수 있다. 그래서 함수를 호출하면 actual parameter들의 값을 복사해서 formal parameter로 넘겨주는 것이 아닌, actual parameter에 해당하는 값들의 주소를 formal parameter로 넘겨주게 된다. 그렇게 되면 함수 내에서 접근하는 formal parameter들이 결국은 actual parameter와 주소값을 공유하고 있는 것이기에 이 formal parameter에 해당하는 주소에 담긴 value에 변화를 가하게 될 경우 같은 주소를 공유하는 actual parameter까지 바뀌게 되는 것이다.

// Call by Reference

#include <iostream>

// Function Prototype
void swapx(int*, int*);

// Main function
int main()
{
    int a = 10, b = 20;

    // Pass reference
    swapx(&a, &b);

    std::cout >> "a = " a >> "b =" b >>std::endl;


    return 0;
}

// Function to swap two variables
// by references
void swapx(int* x, int* y)
{
    int t;

    t = *x;
    *x = *y;
    *y = t;

    std::cout >> "x = " *x >> "y =" *y >>std::endl;

}

Output:
x=20 y=10
a=20 b=10

// 출처 : geeksforgeeks
// https://www.geeksforgeeks.org/difference-between-call-by-value-and-call-by-reference/

해당 예시를 통해 확인해보자. 이번에도 우리는 swapx라는 함수를 사용할 것이다. 그런데 이번에 정의된 swapx의 formal parameter의 형식이 조금 다르다. 보이는 바와 같이 주소를 넘겨주기 위해서 int* x와 같이 설정된 것을 볼 수 있다. 즉 call by reference 방식의 함수를 사용했다는 것이다. 그렇다면 어떻게 동작하고 어떤 점에서 확실히 call by reference인지 확인해보자. 보이는 바와 같이 우리는 이번에도 main function에서 swapx function을 호출할 것이다. 이 때 우리는 실제 파라미터 a, b의 주소값을 formal parameter로 넘겨주게 된다. 그렇게 되면 *x에는 a(혹은 x)의 주소에 있는 실제 값이 담겨있을 것이다. ( *xx에 담긴 주소의 값을 dereferencing하는 것이라고 보면 된다.) 그러면 함수내에서 출력되는 출력문에는 이전과 마찬가지로 x, y의 값이 스왑되어서 출력될 것이다. 그런데 이러한 swapx 함수를 호출한 이후에 a, b의 값을 출력해보면 이 둘 또한 swap되어서 출력되는 것을 볼 수 있다. 그 이유는 xy가 각각 a, b의 주소를 공유하는데 x주소에 해당하는 값을 y의 주소에 넣어주고, y 주소에 해당되던 값을 x의 주소에 값으로 바꿔주었기 때문에 동일한 주소를 공유하는 a, b에 담긴 값이 스왑되어서 출력되는 것이다. 결국 주소를 넘겨주기 때문에 해당 값 자체를 넘겨주는 것과 유사한 역할을 하게 되어서 함수 내에서 formal parameter에 변화를 가하더라도 그 값 자체에 해당하는 actual parameter또한 바뀐다는 것이다.

그래서 이 둘의 차이를 정리하자면 call by value의 경우 값을 복사해서 넘겨주기 때문에, 함수 호출을 통해서 실제 값을 변경할 수 없는 반면 call by reference의 경우 값의 주소를 넘겨주기에 함수 호출을 통해서 원래의 값 자체를 바꿀 수 있다는 것이다. 추가적으로 값을 복사해서 넘겨주는 방식인 call by value의 경우 이러한 동작 방식 때문에 큰 데이터를 다루는 등의 경우 기하급수적으로 처리 시간이 늘어나거나, 메모리 사용량을 늘리게 된다. 

 

Python - Call by Object Reference


그렇다면 앞서 언급했던 파이썬에서의 Call by Object Reference는 어떻게 나뉘는 걸까? 앞서 우리는 passing되는 객체의 타입에 따라서 인자를 전달하는 방식이Call by Value와 Call by Reference로 나뉘는 방식이라고 했다. 그렇다면 어떤 타입에 따라서 어떤 방식을 가지게 되는걸까? 결론부터 말하자면, 넘겨지는 객체가 mutable object인지, immutable object인지에 따라서 Call by Reference인지 Call by value인지가 결정된다. ( 아주 Pythonic함을 느낄 수 있다.) 이게 아주 중요한데, 사실 파이썬이 이러한 인자 전달 방식을 사용하는 이유는 파이썬의 모든 것이 객체이고, 객체가 mutable한 것과 immutable한 객체로 나뉘는 것 때문이라고 볼 수 있다. 그래서 먼저 파이썬에서의 mutable object와 immutable object에 대해 살펴보며 파이썬에서 이 두가지 타입의 객체를 다르게 처리한다는 것을 id function을 통해서 확인해보겠다.

 

Mutable Object & Immutable Object

파이썬의 모든 것은 객체이다. ( 추후 글에서 파이썬의 객체지향에 대해 이야기하면서 설명하겠습니다.) 파이썬은 모든 것이 객체이기에 모든 변수에는 객체가 있다. 따라서 객체가 생성되면 각 객체는 모두 고유한 ID를 할당받는다. ( 이 ID의 경우 런타임에 정의되며 일단 설정되면 불변 객체는 변경할 수 없지만, 가변 객체의 경우 변경할 수 있습니다. ) 즉 아래와 같이 list, set, dict같은 가변 객체의 경우 변경할 수 있고, 그 외의 클래스로 정의되는 객체들은 변경할 수 없는 것이다. ( 이러한 내장형 객체외의 사용자 정의 클래스의 경우 일반적으로는 변경이 가능하다.)

https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747

💡id() 내장함수 
id 함수는 객체의 ID(identity)를 정수로 반환한다. 이 ID란 객체를 구별하기 위한 것으로 일반적으로 메모리에서 객체의 위치에 해당하지만, Python을 사용중인 플랫폼이나 버전 등에 따라서 다르다. 
객체간의 ID를 비교하는 is operator를 통해서 확인해보자.
>>> x = "Holberton" 
>>> y = "Holberton" 
>>> id(x) 
140135852055856 
>>> id(y) 
140135852055856 
>>> print(x는 y) '''유형 비교''' 
True

# 출처 : https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747​

💡Binding Names to Objects
우리가 파이썬에서 어떤 변수에 어떤 값이나 컨테이너를 할당할 경우 이것은 객체로 다뤄진다. 즉, 파이썬의 모든 것은 객체다라는 말을 다시한번 떠올려보자. 그렇다면 우리가 변수에 값을 할당할 때, 실제로 이 과정은 객체에 변수명 name을 binding하는 것이라는 것이다.예시를 통해 확실히 이해해보자.
>>> a = 2
>>> b = 2
 
 
# Returns the actual location
# where the variable is stored
>>> print(id(a))
110001234557894

# Returns the actual location
# where the variable is stored
>>> print(id(b))
110001234557894

# Returns true if both the variables
# are stored in same location
>>> print(a is b)​
True

# 출처 : geeksforgeeks
# https://www.geeksforgeeks.org/is-python-call-by-reference-or-call-by-value/
2라는 객체가 할당된 a, b 각각의 변수는 2라는 동일한 객체를 각각의 name인 a, b로 binding하고 있는 것 뿐이라는 것이다. 즉 일종의 alias를 붙이는 것이다. 그렇다면 저 두 변수의 id는 둘다 동일한 2라는 객체를 binding하고 있으므로 같아야할 것이다. 물론 지금 예시의 경우 불변객체일 때라서이다. 우리가 가변객체를 변수에 할당하게 된다면, 가변객체는 기본적으로 다른 id를 가지게 되기 때문에 각 a, b라는 변수에 name으로 binding을 하더라도 다른 주소를 가지게 된다. 어쨋든 우리는 파이썬에서 변수에 값을 할당하는 것이 객체에 name을 binding하는 즉, 별명을 부여한다는 느낌을 살려서 이해해야한다는 것이다.

가변 객체와 불변 객체에 대해서 더 정확히 이해하기 위해서 id 함수를 사용해서 확인해보자. 

# immutable object
>>> x = 10
>>> x = y
>>> id(x) == id(y)
True
>>> id(y) == id(10)
True

>>> x = x+1
>>> id(x) == id(y)
False
>>> id(x) == id(10)
False

# 출처 : https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747​

먼저 불변 객체에 대해서 확인해보면, 10이라는 numeric 객체는 변경될 수 없는 객체다. 따라서 이 10이라는 객체에 별명 x를 붙였을 때, 이 x를 수정해서 11로 만든다면 이 때 x10이라는 객체를 11로 변경한 것이 아닌, 11이라는 새로운 객체를 생성한 것이다. 따라서 객체 10은 수정되지 않았고, x에 대한 복사본(10이라는 값을 가진)에 수정을 가해 새로운 값을 할당하는 방식으로 새로운 객체 11이 생성되어서 x에 할당된 것이기에 서로 다른 id를 가지는 것이다. 즉 불변 객체의 경우 변경하고자 한다면 새로운 인스턴스를 만들고 변수를 새 인스턴스에 바인딩해야하는 것이다. (반면 가변 객체는 제자리에서 변경할 수 있다.)

# mutable object
>>> m = list([1, 2, 3])
>>> n = m
>>> id(m) == id(n)
True

>>> m.pop()
>>> id(m) == id(n)
True

# 출처 : https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747​

반면에 가변 객체에 대해서 확인해보면 m에 할당된 리스트 객체는 가변객체기에 이 m에 대해서 어떤 변화를 가하더라도, 그 변화가 그대로 객체에 적용되어서, 객체자체가 수정이 되기에 동일한 id를 유지하게 되는 것이다.

이렇게 파이썬에서는 가변객체와 불변객체에 대해서 다르게 처리한다. 가변객체의 경우 객체 자체를 변경할 수 있고 반면 불변 객체의 경우 변경보다는 새로운 생성 후 할당으로 동일한 처리를 위해서 복사본을 생성해야하기 때문에 상대적으로 많은 비용이 든다. 이 모습을 살펴보면 앞서 살펴봤던 call by reference와 가변객체가 그리고 call by value와 불변객체가 닮아 있는 것을 볼 수 있다. 파이썬의 call by object reference는 이를 따라간다. 

그래서 파이썬에서 불변객체를 함수의 인자로 사용하게 될 경우 call by value방식으로 (복사해서 넣는) 가변객체를 함수의 인자로 사용하게 될 경우 call by reference방식으로( referencing을 하는 주소를 넣는) 동작하고 그렇기에 파이썬에서의 함수에 인자 전달방식을 call by object reference라고 하는 것이다. 예시를 통해 확인해보자.

# Python code to demonstrate
# call by value
 
 
string = "Geeks"
 
 
def test(string):
     
    string = "GeeksforGeeks"
    print("Inside Function:", string)
     
# Driver's code
test(string)
print("Outside Function:", string)

# 출처 : geeksforgeeks
# https://www.geeksforgeeks.org/is-python-call-by-reference-or-call-by-value/
Inside Function: GeeksforGeeks
Outside Function: Geeks
# Python code to demonstrate
# call by reference
 
 
def add_more(list):
    list.append(50)
    print("Inside Function", list)
 
# Driver's code
mylist = [10,20,30,40]
 
add_more(mylist)
print("Outside Function:", mylist)

# 출처 : geeksforgeeks
# https://www.geeksforgeeks.org/is-python-call-by-reference-or-call-by-value/
Inside Function [10, 20, 30, 40, 50]
Outside Function: [10, 20, 30, 40, 50]

이렇게 각각 Immutable Object에 대해서는 Call by Value가 Mutable Object에 대해서는 Call by Reference가 동작하는 방식으로 함수에 인자를 전달하는 것이 Call by Object Reference이며, 파이썬에서 Immutable Object와 Mutable Object를 앞선 설명처럼 처리를 하기 때문에 ( 값을 변경할 때 새로운 인스턴스를 생성해 복사한 후에 수정후 다시 그 변수에 할당하는지 아니면 즉석에서 변경하게하는지 ) 이와 같이 함수의 인자를 전달하는 방식에서 Object에 타입에 따라서 다르게 처리된다는 것을 확인할 수 있다.

 

 

References 

https://foramonth.tistory.com/20

 

Python - Call by Object Reference

Python에서의 'Call by Object Reference' 파이썬의 변수에 대해 공부하다가 함수 인수 전달 방식인 call by value call by reference call by object reference 에 대해 알게 되었다. 이들의 차이점에 대해 알아..

foramonth.tistory.com

https://www.geeksforgeeks.org/difference-between-call-by-value-and-call-by-reference/

 

Difference between Call by Value and Call by Reference - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://www.geeksforgeeks.org/is-python-call-by-reference-or-call-by-value/

 

Is Python call by reference or call by value - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747

 

Mutable vs Immutable Objects in Python

Everything in Python is an object. And what every newcomer to Python should quickly learn is that all objects in Python can be either…

medium.com

 

'Python' 카테고리의 다른 글

Python - Variable Length Arguments  (0) 2022.02.02
Python - Iterable / Iterator / Generator  (0) 2022.02.01
Python Scoping Rule ( by LEGB rule )  (0) 2022.02.01
Python Dynamic Typing  (0) 2022.01.28
Python이라는 언어  (0) 2022.01.24