초보가 처음 듣는 tuple 의 정의 — "list 인데 못 바꾼다" — 는 핵심을 놓쳐. tuple 이 존재하는 이유는 다른 모양을 표현하기 위해서야. 고정된 모양의 레코드. list 는 "같은 종류 여러 개 순서대로" (주문 list, 사용자 list). tuple 은 "한 가지의 부분들" (좌표의 x/y, 데이터베이스 한 행). 모양이 달라. 그리고 Python 은 두 모양 다 idiomatic 하게 써.
tuple 만드는 법 — 진짜 연산자는 콤마
tuple 주변에 늘 보이는 괄호는 옵션 이야. tuple 을 만드는 건 콤마. x = 1, 2, 3 이 tuple. x = (1, 2, 3) 이 같은 tuple — 괄호는 단지 모호함을 없애줘. 예외 — 빈 tuple 은 () 가 필요하고, 원소 하나짜리는 끝에 콤마 ((1,)) 가 있어야 돼. 콤마 없으면 그냥 괄호 친 표현식.
Unpacking — Python 의 조용한 초능력
tuple unpacking 은 한 번 익히면 모든 곳에서 쓰게 되는 기능. x, y = 1, 2 가 한 줄 swap. 함수가 여러 값 반환하는 것도 사실 tuple 반환이야. tuple 을 yield 하는 iterator 는 for 안에서 unpacking 가능. * 가 들어간 first, *rest = my_list 는 "나머지 전부" 를 list 로.
불변성 — 무슨 뜻인지 정확히
tuple 의 원소를 재할당 못 해. t[0] = 99 는 TypeError. 근데 tuple 안에 mutable 객체가 있으면, 그 객체는 여전히 mutable. t = ([1, 2], 3) — t[0] 을 다른 list 로 바꿀 순 없지만 t[0].append(99) 는 가능. tuple 의 *구조* 는 얼었어도 *내용* 은 안 얼었을 수 있어.
원칙: tuple 이 hashable (dict 키 / set 원소로 사용 가능) 하려면 모든 원소가 각각 hashable 해야 돼. (1, 2, 3) 은 키 OK. ([1, 2], 3) 은 X — 안의 list 가 체인을 끊어.
namedtuple — tuple 에 이름 붙이기
tuple 을 위치로 인덱싱하는 건 잘 안 읽혀. row[3] 만 봐서는 3 이 뭔지 모르잖아. collections.namedtuple 은 각 필드에 이름이 붙은 tuple 서브클래스를 줘. tuple 의 모든 성질 (불변, hashable, 인덱싱, unpacking) 다 유지하면서 row.email 같은 접근도 가능. 더 현대적인 옵션 — typing.NamedTuple + 타입 힌트, dataclass(frozen=True) — 는 OOP 트랙에서. namedtuple 은 여전히 가볍고 빠른 첫 단추.
Code
tuple 만드는 법 — 그리고 원소 하나짜리 함정·python
# 모두 tuple — 콤마가 진짜 연산자
empty = ()
single = (1,) # 끝 콤마 필수
pair = 1, 2 # 괄호 옵션
triple = (1, 2, 3)
mixed = "a", 1, [2, 3] # tuple 안에 뭐든
# 끝 콤마 없으면 그냥 괄호 친 식
not_a_tuple = (1)
print(type(not_a_tuple)) # <class 'int'>
print(type(single)) # <class 'tuple'>
# tuple() 생성자 — 어떤 iterable 도
t = tuple("abc")
print(t) # ('a', 'b', 'c')
Unpacking — 일상의 초능력·python
# 다중 할당
x, y, z = 1, 2, 3
# swap
a, b = 10, 20
a, b = b, a
print(a, b) # 20 10
# 여러 값 반환하는 함수도 사실 tuple 반환
def divmod_(a, b):
return a // b, a % b
q, r = divmod_(17, 5)
print(q, r) # 3 2
# star-unpacking — 나머지 잡기
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last) # 1 [2, 3, 4] 5
tuple 을 키로 — 단, 모든 원소가 hashable 일 때만·python
# hashable 한 것들의 tuple 은 hashable
grid = {}
grid[(0, 0)] = "start"
grid[(3, 4)] = "goal"
print(grid[(3, 4)]) # 'goal'
# list 가 들어간 tuple 은 hashable X
bad_key = (1, [2, 3])
try:
grid[bad_key] = "oops"
except TypeError as e:
print("실패:", e) # unhashable type: 'list'
불변성 ≠ 깊이 얼었다·python
t = ([1, 2], 3)
# t[0] 자체는 재할당 불가
try:
t[0] = [99]
except TypeError as e:
print(e) # 'tuple' object does not support item assignment
# 근데 t[0] 이 가리키는 객체는 mutable
t[0].append(99)
print(t) # ([1, 2, 99], 3)
# 그래서 이 tuple 은 hashable 도 아님 — 안의 list 가 깨
try:
hash(t)
except TypeError as e:
print(e) # unhashable type: 'list'
namedtuple — tuple 의 모든 것 + 이름·python
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4 — 이름 접근
print(p[0], p[1]) # 3 4 — 인덱싱도 됨
x, y = p # unpacking 도 됨
print(x, y) # 3 4
# 불변
try:
p.x = 99
except AttributeError as e:
print(e) # can't set attribute
# _replace — 새 namedtuple 반환
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=4)
namedtuple 로 Stock 정의 — 필드는 ticker, price, shares. 세 종목 만들어. (a) 각 보유 가치 (price × shares) 출력. (b) 총 가치 내림차순 정렬. (c) _replace 로 한 종목 가격이 10% 떨어진 새 tuple 만들기 — 원본은 그대로. 마지막에 원본 출력해서 확인.
Progress
Progress is local-only — sign in to sync across devices.