튜플 (Tuple)
학습 목표
- 튜플의 불변(Immutable) 특성을 이해하고, 리스트와의 차이를 설명할 수 있다
- 튜플 언패킹을 다양한 상황에서 활용할 수 있다
namedtuple로 가독성 높은 데이터 구조를 만들 수 있다
왜 중요한가
튜플은 변경할 수 없는(Immutable) 시퀀스입니다. 이 불변성 덕분에 딕셔너리의 키로 사용할 수 있고, 함수에서 여러 값을 안전하게 반환할 수 있습니다. 또한 리스트보다 메모리를 적게 사용하고 접근 속도가 빠릅니다.
튜플 생성
# 소괄호로 생성
point = (3, 4)
rgb = (255, 128, 0)
single = (42,) # 요소 1개 - 쉼표 필수!
empty = ()
# 소괄호 없이도 가능 (패킹)
coordinates = 37.5, 127.0
print(type(coordinates)) # <class 'tuple'>
# tuple() 생성자
from_list = tuple([1, 2, 3])
from_range = tuple(range(5))
from_string = tuple("abc") # ('a', 'b', 'c')
요소가 하나인 튜플은 반드시 쉼표를 붙여야 합니다. (42)는 정수 42이고, (42,)가 튜플입니다.not_tuple = (42) # int
yes_tuple = (42,) # tuple
print(type(not_tuple)) # <class 'int'>
print(type(yes_tuple)) # <class 'tuple'>
불변성 (Immutability)
point = (3, 4)
# 읽기는 가능
print(point[0]) # 3
print(point[1]) # 4
# 수정은 불가
# point[0] = 5 # TypeError: 'tuple' object does not support item assignment
# 단, 내부에 가변 객체가 있으면 그 객체는 수정 가능
data = ([1, 2], [3, 4])
data[0].append(3)
print(data) # ([1, 2, 3], [3, 4])
# data[0] = [5, 6] # TypeError - 튜플의 참조 자체는 변경 불가
언패킹 (Unpacking)
튜플 언패킹은 Python의 매우 강력한 기능입니다.
기본 언패킹
# 기본 언패킹
point = (3, 4)
x, y = point
print(f"x={x}, y={y}") # x=3, y=4
# 함수에서 여러 값 반환
def get_min_max(numbers):
return min(numbers), max(numbers)
low, high = get_min_max([3, 1, 4, 1, 5, 9])
print(f"최소: {low}, 최대: {high}") # 최소: 1, 최대: 9
확장 언패킹 (Extended Unpacking)
# * 연산자로 나머지 수집
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
*init, last = [1, 2, 3, 4, 5]
print(init) # [1, 2, 3, 4]
print(last) # 5
실용적 언패킹 패턴
# 값 교환
a, b = 10, 20
a, b = b, a
# 루프에서 언패킹
students = [("김철수", 85), ("이영희", 92), ("박민수", 78)]
for name, score in students:
print(f"{name}: {score}점")
# 불필요한 값 무시
_, y, _ = (1, 2, 3) # y만 필요할 때
print(y) # 2
# enumerate와 함께
fruits = ["사과", "바나나", "체리"]
for idx, fruit in enumerate(fruits):
print(f"{idx}: {fruit}")
튜플 vs 리스트
| 특성 | 튜플 | 리스트 |
|---|
| 가변성 | 불변 | 가변 |
| 문법 | () | [] |
| 딕셔너리 키 | 가능 | 불가 |
| 메모리 | 적음 | 많음 |
| 속도 | 빠름 | 느림 |
| 용도 | 고정 데이터, 레코드 | 동적 컬렉션 |
import sys
# 메모리 비교
t = (1, 2, 3, 4, 5)
l = [1, 2, 3, 4, 5]
print(sys.getsizeof(t)) # 80 바이트
print(sys.getsizeof(l)) # 104 바이트
# 딕셔너리 키로 사용
location_names = {
(37.5, 127.0): "서울",
(35.1, 129.0): "부산",
}
namedtuple
collections.namedtuple은 필드에 이름을 붙인 튜플입니다. 인덱스 대신 이름으로 접근할 수 있어 가독성이 높아집니다.
from collections import namedtuple
# 정의
Point = namedtuple("Point", ["x", "y"])
Student = namedtuple("Student", "name age score")
# 생성
p = Point(3, 4)
s = Student(name="김철수", age=25, score=92)
# 접근 (이름 또는 인덱스)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4
print(s.name, s.score) # 김철수 92
# 언패킹도 가능
x, y = p
print(f"좌표: ({x}, {y})")
# _asdict() - 딕셔너리로 변환
print(s._asdict()) # {'name': '김철수', 'age': 25, 'score': 92}
# _replace() - 값 변경 (새 객체 반환)
s2 = s._replace(score=95)
print(s2) # Student(name='김철수', age=25, score=95)
Python 3.5 이상에서는 typing.NamedTuple을 사용하면 타입 힌트를 추가할 수 있습니다. Python 3.7 이상에서는 dataclass가 더 강력한 대안입니다.
AI/ML에서의 활용
# 모델 출력이 종종 튜플
# PyTorch의 model() 호출 결과
# output, hidden_state = model(input)
# 데이터셋 반환값
def load_dataset():
"""학습/검증/테스트 데이터를 튜플로 반환합니다."""
train = [1, 2, 3]
valid = [4, 5]
test = [6, 7]
return train, valid, test
train_data, val_data, test_data = load_dataset()
# 이미지 크기 표현
image_shape = (224, 224, 3) # (높이, 너비, 채널)
height, width, channels = image_shape
# 하이퍼파라미터 조합
from collections import namedtuple
Config = namedtuple("Config", ["lr", "batch_size", "epochs"])
config = Config(lr=0.001, batch_size=32, epochs=100)
데이터가 변경되면 안 되는 경우(좌표, 설정값), 딕셔너리의 키로 사용할 때, 함수에서 여러 값을 반환할 때 튜플을 사용합니다. 데이터를 자주 추가/삭제해야 한다면 리스트를 사용하세요.
namedtuple과 dataclass의 차이는 무엇인가요?
namedtuple은 불변이고 튜플의 서브클래스입니다. dataclass는 가변이고(frozen=True로 불변 가능) 더 많은 기능(기본값, 비교, 해시)을 제공합니다. 새 코드에서는 dataclass를 권장합니다.
체크리스트
다음 문서