티스토리 뷰

반응형

파이썬을 공부하다 보면 C / C++ 언어와 다르게 포인터라는 개념을 전혀 마주친 경험이 없을 것이다.

 

정상이다. 파이썬은 포인터라는 개념을 직접 활용하지 않는다. 물론, 파이썬에도 포인터에서 파생된 개념(예를 들어, call by value, call by reference...) 활용하긴 하지만, 직접 사용자가 쉽게 메모리에 접근할 수 있는 방법은 따로 없다.

 

그럼, 파이썬은 어떻게 변수에 데이터를 저장하는가? '='의 역할은 무엇인가? 또한, 이때 내부적으로 파이썬은 포인터를 활용할까? 이번 포스팅에서는 이에 대한 답을 알아보자.

thumbnail
파이썬은 pointer란 개념이 있을까?


1. id 함수란?

파이썬의 내장 함수 중에는 id라는 함수가 존재한다. 이 함수의 역할을 매우 간단하다. 객체의 고윳값(identity)을 반환한다.

 

파이썬에서는 각 객체마다 객체의 수명 동안 항상 하나의 고윳값을 객체에 부여하며, 객체의 수명이 끝나지 않은 한 무슨 일이 있어도 변하지 않는다.

 

다음 예시를 보면 a, b의 메모리상에서의 주소를 id를 통해 출력하는 것을 볼 수 있다.

 

1
2
3
a, b = 12
print(id(a), id(b))
# a, b의 메모리 주소를 십진법 형태로 보여준다.
cs

 

C언어를 중심으로 작성된, 대부분의 사용자가 사용하는 파이썬(CPython)에서의 id 함수는 객체가 메모리에서 어디에 위치에 있는지 알려주는 함수로 작동한다.

 

실제 파이썬의 소스코드 중 id 함수가 구현되어 있는 부분을 살펴보면 다음과 같다. 모든 코드를 정확히 이해할 필요는 없고, 이렇게 구현되어 있다까지만 알면 될 듯하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* AC: gdb's integration with CPython relies on builtin_id having
 * the *exact* parameter names of "self" and "v", so we ensure we
 * preserve those name rather than using the AC defaults.
 */
/*[clinic input]
id as builtin_id
    self: self(type="PyModuleDef *")
    obj as v: object
    /
Return the identity of an object.
This is guaranteed to be unique among simultaneously existing objects.
(CPython uses the object's memory address.)
[clinic start generated code]*/
 
static PyObject *
builtin_id(PyModuleDef *self, PyObject *v)
/*[clinic end generated code: output=0aa640785f697f65 input=5a534136419631f4]*/
{
    PyObject *id = PyLong_FromVoidPtr(v);
 
    if (id && PySys_Audit("builtins.id""O", id) < 0) {
        Py_DECREF(id);
        return NULL;
    }
 
    return id;
}
cs

 

부연설명을 하자면, builtin_id 함수의 매개변수 중 PyObject는 파이썬의 객체를 의미한다. 함수 내부를 살펴보면 PyLong_FromVoidPtr를 통해 인자의 포인터를 id에 담고, 이를 return 하는 것을 확인할 수 있다.

 

정리하자면, id 함수는 객체 고윳값을 반환하는 함수이며, 통상 객체를 담고 있는 메모리 주소를 반환한다.

 

id에 대한 추가적인 설명은 파이썬의 메모리 관리(Garbage Collector)에 대한 포스팅에서 추가하겠다.


2. 그럼, '='는 무엇을 할당하는 건가요? 포인터로 할당하는 것인가요?

[※ 주의 ※] 아래를 이해하지 않고 이 글을 볼 경우, 이해가 되지 않는 부분이 있을 수 있습니다.

 

1. [Python] 가변, 불변(mutable, immutable)에 대한 모든 것

 

[Python] 가변, 불변(mutable, immutable)에 대한 모든 것

파이썬에는 다른 언어에서는 찾아볼 수 없는 특징들이 몇 가지 있는데, 대표적으로 파이썬은 모든 객체를 가변 객체(mutable object), 불변 객체(immutable object) 두 종류의 객체로 구분하고 있다. 어렵

0xffffffff.tistory.com

 

파이썬은 '='라는 연산자를 통해 변수에 값 혹은 데이터를 할당한다. 정확히는 operator=를 통해 할당한다. ​

 

만약, a = 1이라면, 다음과 같이 풀어쓸 수 있다.

a = 1 은 a.operator=(1)과 같다.

 

이 말은 즉 '=' operator를 활용한다는 것이다.

 

우리가 알고있는 '='의 역할은 새로운 변수에 메모리를 할당하는 것이다. 그러나, 변수가 100만개, 1000만개와 같이 변수가 많아질 경우 메모리 공간의 낭비가 발생할 수 있다.

 

대신, 파이썬은 몇몇의 경우 '='를 사용할 때 메모리 주소만 복사하여 연결짓는 경우가 있다. 파이썬 자체적으로 포인터 연산을 활용하는 것이다. (C++의 shared_ptr 개념과 비슷하다 생각하면 된다.)

 

예를 들어 보자. 다음과 같이 dictionary을 할당하였고, 안의 데이터를 바꾸었다고 하자. 이때의 '='는 우측에 있는 변수의 메모리 주소를 좌측 변수에 할당하는 역할을 한다.

 

1
2
3
4
5
dict1 = {1 : 'hello'2 : 'world!'}
dict2 = dict1 # 포인터를 할당한다
dict2[1= 'bye'
print(dict1)
# {1: 'bye', 2: 'world!'}
cs

 

dict1의 id의 값을 dict2 변수에 할당하였기 때문에, dict2에 변화를 주면 dict1도 영향을 받는 모습을 확인할 수 있다.

 

그러나, 다음 예시와 같이 변수에 새로운 정수를 넣는 경우 '='의 역할이 id가 아닌, 새로운 메모리 주소 및 값이 할당될 수 있다.

 

1
2
3
4
5
6
7
int1 = 1
int2 = int1
# 여기까진 id(int2) == id(int1)이다. 포인터로 할당
int2 = 2
# 이 경우 새로운 메모리가 할당되고, 포인터가 아닌 새로운 값이 할당되었다.
print(int1)
# 1
cs

 

 

이와 같이 '='의 역할이 달라지는 것은 '='의 우측에 있는 값이 mutable한지, immutable한지에 따라 달라진다. mutable의 경우 기존의 메모리 주소가 할당되며, immutable의 경우 기존의 메모리 주소가 할당되지만, 새로운 메모리 주소가 할당될 수 있다.

 

그 이유는 immutable의 경우 메모리 내의 데이터를 바꿀 수 없기 때문에 만약 같은 객체를 가리킬 경우 최적화를 위해 같은 메모리 주소로 할당해도 되고, 만약 다른 객체로 바뀐다면 다른 메모리 주소에 새로운 값을 넣고 변수가 가리키는 주소를 새로운 메모리 주소로 바꾸어 주면 되기 때문이다.

 

따라서, 파이썬은 자체적으로 포인터를 활용하지만, 할당하려는 객체가 mutable한지, immutable한지에 대하여 다르게 작동한다.

 

 

 

 

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함