Mutable vs Immutable
Python에서 모든 변수는 객체를 참조하며, 이 객체들은 크게 두 가지 유형으로 나눌 수 있다.
Immutable (변경 불가능한) 객체
- 생성된 후에는 내용을 변경할 수 없는 객체
- 값을 수정하려면 새로운 객체를 생성해야 함
- 예: int, float, str, tuple, bytes
Mutable (변경 가능한) 객체
- 생성된 후에도 내용을 변경할 수 있는 객체
- 동일한 객체를 유지하면서 내부 값 수정 가능
- 예: list, dict, set, 사용자 정의 클래스
Python 기본 자료형의 특성
Immutable 타입
정수(int)
a = 5
id_before = id(a)
a += 1 # a = a + 1
id_after = id(a)
print(f"이전 ID: {id_before}, 이후 ID: {id_after}")
print(f"같은 객체? {id_before == id_after}") # False
문자열(str)
s = "hello"
id_before = id(s)
s += " world" # s = s + " world"
id_after = id(s)
print(f"이전 ID: {id_before}, 이후 ID: {id_after}")
print(f"같은 객체? {id_before == id_after}") # False
튜플(tuple)
t = (1, 2, 3)
id_before = id(t)
t += t
id_after = id(t)
print(f"이전 ID: {id_before}, 이후 ID: {id_after}")
print(f"같은 객체? {id_before == id_after}") # False
Mutable 타입
리스트(list)
my_list = [1, 2, 3]
id_before = id(my_list)
my_list.append(4) # 내용 변경
id_after = id(my_list)
print(f"이전 ID: {id_before}, 이후 ID: {id_after}")
print(f"같은 객체? {id_before == id_after}") # True
딕셔너리(dict)
my_dict = {'a': 1, 'b': 2}
id_before = id(my_dict)
my_dict['c'] = 3 # 내용 변경
id_after = id(my_dict)
print(f"이전 ID: {id_before}, 이후 ID: {id_after}")
print(f"같은 객체? {id_before == id_after}") # True
객체 ID와 값 비교: is vs ==
is 연산자
- 두 변수가 메모리 상의 동일한 객체를 참조하는지 확인
- 객체의 ID를 비교 (id(a) == id(b))
- 정체성(identity) 비교
== 연산자
- 두 객체의 값이 같은지 확인
- 객체의 내용을 비교
- 동등성(equality) 비교
# 예시
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(f"a == b: {a == b}") # True (값이 같음)
print(f"a is b: {a is b}") # False (다른 객체)
print(f"a == c: {a == c}") # True (값이 같음)
print(f"a is c: {a is c}") # True (같은 객체)
# ID 확인
print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}")
print(f"id(c): {id(c)}")
주의해야 할 점: 정수 객체 인터닝
Python은 메모리 최적화를 위해 작은 정수(-5부터 256 사이)를 미리 생성하여 재사용하는 데, 이를 인터닝(interning)이라고 한다.
a = 5
b = 5
print(a is b) # True: 같은 객체를 참조
x = 257
y = 257
print(x is y) # 구현에 따라 다를 수 있음 (대개 False)
참조와 복사
변수 할당과 참조
Python에서 변수 할당은 값을 복사하는 것이 아닌, 객체에 대한 참조(reference)를 생성하는 것이다.
# Mutable 객체의 참조
original_list = [1, 2, 3]
reference = original_list # 참조 생성
reference.append(4) # 원본 객체 수정
print(original_list) # [1, 2, 3, 4] - 원본도 변경됨
Shallow Copy
shallow copy는 객체 자체는 새로 생성하지만, 내부의 항목은 원본 객체의 항목과 동일한 참조를 유지한다. 따라서 해당 참조의 값이 변경되면, 원본과 복사 객체의 값이 변경된다.
import copy
# 얕은 복사의 예
original = [[1, 2, 3], [4, 5, 6]]
shallow_copied = copy.copy(original) # 또는 original.copy() 또는 list(original)
# 최상위 객체는 다름
print(original is shallow_copied) # False
# 내부 객체는 동일한 참조
print(original[0] is shallow_copied[0]) # True
# 내부 객체 변경 시 영향
original[0][0] = 99
print(shallow_copied) # [[99, 2, 3], [4, 5, 6]]
Deep Copy
deep copy는 객체와 그 내부의 모든 중첩된 객체까지 재귀적으로 복사한다. 전부를 복사하여 새 주소에 담기에 참조를 공유하지 않는다.
import copy
# 깊은 복사의 예
original = [[1, 2, 3], [4, 5, 6]]
deep_copied = copy.deepcopy(original)
# 최상위 객체는 다름
print(original is deep_copied) # False
# 내부 객체도 다름
print(original[0] is deep_copied[0]) # False
# 내부 객체 변경 시 영향 없음
original[0][0] = 99
print(deep_copied) # [[1, 2, 3], [4, 5, 6]]
복사 방법 비교
import copy
# 원본 리스트
original = [[1, 2], [3, 4]]
# 참조
reference = original
# shallow copy (여러 방법)
shallow_copy1 = original.copy()
shallow_copy2 = list(original)
shallow_copy3 = original[:]
shallow_copy4 = copy.copy(original)
# deep copy
deep_copy = copy.deepcopy(original)
# 원본 수정
original[0][0] = 9999
original.append([5, 6])
# 결과 확인
print("참조:", reference)
print("shallow 1:", shallow_copy1)
print("shallow 2:", shallow_copy2)
print("shallow 3:", shallow_copy3)
print("shallow 4:", shallow_copy4)
print("deep:", deep_copy)
함수 매개변수 전달 시 주의점
Python에서 함수에 인자를 전달할 때도 참조 방식이 적용된다.
def modify_list(lst):
lst.append(999) # 원본 객체 수정
def modify_number(num):
num += 10 # 새 객체 할당 (함수 외부에 영향 없음)
# 리스트 (mutable) 전달
my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # [1, 2, 3, 999] - 원본이 수정됨
# 숫자 (immutable) 전달
my_number = 5
modify_number(my_number)
print(my_number) # 5 - 원본이 수정되지 않음
활용과 함정
1. 기본 인자 함정
파이썬에서 함수 정의는 코드가 로드될 때 실행되며, default parameter의 값은 그 시점에 한 번만 생성된다. 그리고 이 기본값 객체는 함수 객체의 속성으로 저장된다.
# 위험한 패턴
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] - 이전 호출의 영향을 받음
# 올바른 패턴
def add_item_safe(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
print(add_item_safe("a")) # ['a']
print(add_item_safe("b")) # ['b']
2. 튜플 속 mutable 객체
# 튜플은 immutable이지만 내용물은 mutable일 수 있음
t = (1, [2, 3], 4)
# t[0] = 99 # TypeError: 'tuple' object does not support item assignment
t[1].append(99) # 내부 리스트는 변경 가능
print(t) # (1, [2, 3, 99], 4)
3. 문자열 수정의 비효율성
# 문자열은 immutable이므로 수정은 새 객체 생성을 의미함
import time
# 비효율적 방식
start = time.time()
s = ""
for i in range(10000):
s += str(i) # 매번 새 문자열 생성
print(f"걸린 시간: {time.time() - start:.5f}초")
# 효율적 방식
start = time.time()
parts = []
for i in range(10000):
parts.append(str(i))
s = "".join(parts) # 한 번만 문자열 생성
print(f"걸린 시간: {time.time() - start:.5f}초")
연습 문제
연습 1: 데이터 변경 추적하기
다음 코드의 출력 결과를 예측한다.
def modify_data(data):
if isinstance(data, list):
data.append(100)
elif isinstance(data, dict):
data['new'] = 100
elif isinstance(data, int):
data += 100
return data
my_list = [1, 2, 3]
my_dict = {'a': 1, 'b': 2}
my_number = 5
result_list = modify_data(my_list)
result_dict = modify_data(my_dict)
result_number = modify_data(my_number)
print("원본 리스트:", my_list)
print("반환된 리스트:", result_list)
print("원본 딕셔너리:", my_dict)
print("반환된 딕셔너리:", result_dict)
print("원본 숫자:", my_number)
print("반환된 숫자:", result_number)
연습 2: 중첩 데이터 구조의 복사
다음 중첩 데이터 구조를 shallow copy와 deep copy로 복제한 후, 변경 사항이 어떻게 전파되는지 확인한다.
import copy
nested_data = {
'name': 'Project A',
'members': ['Alice', 'Bob'],
'details': {
'status': 'active',
'priority': 'high'
}
}
shallow_copy = copy.copy(nested_data)
deep_copy = copy.deepcopy(nested_data)
nested_data['members'].append('Charlie')
nested_data['details']['status'] = 'completed'
# 결과 확인
print("origin:", nested_data)
print("shallow:", shallow_copy)
print("deep:", deep_copy)
'python' 카테고리의 다른 글
파이썬 기초 - 예외처리 (0) | 2025.03.19 |
---|---|
파이썬 기초 - Context Manager (0) | 2025.03.19 |
파이썬 기초 - Iterable, Iterator, Generator (0) | 2025.03.18 |
파이썬 기초 - 클래스 기초 (0) | 2025.03.16 |
파이썬 기초 - 함수 기초 (0) | 2025.03.16 |
댓글