파이썬은 초심자에게 매우 친절한 언어로 알려져 있지만, 그 이면에는 깊이 있는 메모리 모델과 객체 지향 구조가 존재한다. 특히 함수 호출 시의 인자 전달 방식은 C/C++의 포인터 개념에 익숙한 사람에게 혼란을 줄 수 있다. “파이썬에는 포인터가 없다”고 알려져 있지만, 실질적으로 파이썬의 참조 기반 처리 방식은 포인터가 제공하는 기능 상당 부분을 대체하거나 추상화한다. 이 글에서는 그 내부 메커니즘을 해부하고, 실전 프로그래밍에서 이를 어떻게 이해하고 활용할 수 있는지를 심층적으로 고찰해본다.
함수 호출 시 값은 어떻게 전달되는가?
파이썬에서는 모든 것이 객체이며, 변수는 이 객체에 대한 ‘이름표(name tag)’ 역할을 한다. 이는 파이썬의 모든 함수 인자 전달 방식이 **”객체의 참조(reference) 전달”**이라는 사실을 이해하는 데 핵심이다.
def change(x):
x = 10
a = 5
change(a)
print(a) # 여전히 5
이 예제에서 변수 a
는 정수 5를 가리키고 있다. change()
함수는 x
를 통해 이 값을 받지만, 정수는 불변(immutable) 객체이기 때문에 x = 10
이라는 할당은 새로운 객체를 생성하여 x
에 연결시킬 뿐, 원래 a
와는 아무 관련이 없어진다. 이는 마치 C에서 값이 복사되어 전달된 것처럼 동작한다.
참조에 의한 “수정”이 가능한 경우
가변(mutable) 객체, 예를 들어 리스트나 딕셔너리 등은 다르게 작동한다.
def modify_list(lst):
lst.append(100)
my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # [1, 2, 3, 100]
이 경우 lst
와 my_list
는 동일한 리스트 객체를 참조한다. 함수 내부에서 리스트에 원소를 추가하면, 외부에서도 그 결과를 볼 수 있다. 이것이 참조의 실질적인 위력이다. 이 행동은 마치 C에서 포인터를 이용해 배열을 조작하는 것과 유사하다. 하지만 차이점은 분명히 존재한다. 파이썬 개발자는 메모리 주소나 포인터 산술 연산 등을 직접 다루지 않는다. 참조는 언어가 자동으로 처리한다.
*args
와 **kwargs
는 포인터인가?
*args
는 포인터와 헷갈릴 수 있지만 완전히 다른 개념이다. *args
는 함수 정의에서 가변 positional 인자를 튜플로 수집하는 문법일 뿐이며, 메모리 주소를 직접 넘기는 것이 아니다. 다만 args
내부에 들어있는 객체들이 모두 참조를 통해 접근된다는 점에서는 일종의 ‘포인터 컬렉션’처럼 행동한다.
def func(name, *args):
for arg in args:
print(f"{name}: {arg}")
이러한 문법은 함수 인터페이스를 유연하게 설계할 수 있게 해주지만, 포인터처럼 메모리 조작이 가능한 구조는 아니다.
객체 ID와 참조 비교
파이썬에서 참조의 실체를 확인하려면 id()
함수를 활용할 수 있다. 이는 객체의 고유 식별자(일종의 메모리 주소)를 반환한다.
a = [1, 2]
b = a
print(id(a) == id(b)) # True
이것은 b
가 a
와 동일한 리스트 객체를 참조하고 있음을 보여준다. 새로운 리스트를 할당하면 참조가 바뀐다.
b = [1, 2]
print(id(a) == id(b)) # False
얕은 복사와 깊은 복사: 참조의 함정
참조 기반 언어에서 가장 흔히 발생하는 문제는 복사의 오해다. copy.copy()
는 객체의 얕은 복사를 수행하여 상위 객체는 복제하지만 하위 객체는 그대로 참조한다. 반면 copy.deepcopy()
는 모든 계층을 재귀적으로 복사한다.
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
얕은 복사를 이해하지 못하면, 리스트 안의 리스트처럼 중첩된 구조에서 원치 않는 참조 공유가 발생할 수 있다. 이는 종종 디버깅을 어렵게 만드는 요인이다.
정리하며
파이썬은 포인터 연산이 없는 대신, 모든 객체를 참조로 처리함으로써 메모리 모델을 단순화했다. 이는 개발자에게 불필요한 메모리 관리를 강요하지 않고도 포인터의 주요 기능을 사용할 수 있게 한다. 하지만 참조의 특성을 이해하지 못하면 예기치 않은 동작을 경험할 수 있다.
결국 파이썬의 변수는 ‘값’을 저장하지 않는다. 변수는 단지 객체를 가리키는 이름이며, 그 객체가 어디에 있고 무엇을 하는지는 파이썬이 관리한다. 이것이 바로 고급 언어로서 파이썬이 가진 진정한 추상화의 힘이다.
답글 남기기