본문으로 건너뛰기

튜플: 묶고 풀기

파이썬 목차리스트와 튜플레슨 30 / 59

목차

튜플(tuple)은 값 여럿을 쉼표로 묶은, 바꿀 수 없는 묶음입니다. 대입의 오른쪽에 값을 쉼표로 늘어놓으면 튜플 하나로 묶이고, 왼쪽에 이름을 늘어놓으면 그 묶음을 풀어 이름을 하나씩 붙입니다.

메뉴 이름과 가격처럼 늘 함께 다니는 값은 한 묶음으로 두면 함께 넘기고 함께 꺼낼 수 있습니다. 묶고 푸는 대입을 알면 두 이름이 붙은 값을 한 줄로 맞바꿀 수도 있습니다.

5단계 중 1단계. 이름 a와 b가 붙은 값을 한 줄로 맞바꾸는 코드입니다.

1튜플은 바꿀 수 없는 묶음

괄호 안에 값을 쉼표로 늘어놓으면 튜플이 됩니다. 리스트처럼 자리 번호로 꺼내고, len()으로 칸 수를 셉니다.

item = ('커피', 4000)
print(item[0])
print(item[1])
print(len(item))
실행 결과
커피
4000
2

리스트와 다른 점은 한 번 만들면 칸을 바꿀 수 없다는 것입니다.

item = ('커피', 4000)
item[1] = 4500
실행 결과
Traceback (most recent call last):  File "main.py", line 2, in <module>    item[1] = 4500    ~~~~^^^TypeError: 'tuple' object does not support item assignment

맨 아래 줄은 '튜플은 칸에 대입하는 것을 지원하지 않는다'는 뜻입니다. 문자열 메서드에서 문자열 한 칸을 바꿀 때 본 오류와 같습니다. 바꿀 수 있는 값과 바꿀 수 없는 값을 자료에 따라 가변(mutable)·불변(immutable)이라고 부릅니다. 리스트는 바꿀 수 있고, 문자열과 튜플은 바꿀 수 없습니다. 가격이 바뀌면 새 튜플을 만들어 이름을 다시 붙입니다(item = ('커피', 4500)).

2쉼표로 묶고, 이름을 늘어놓아 푼다

item = '커피', 4000
name, price = item
print(item)
print(name)
print(price)
실행 결과
('커피', 4000)
커피
4000

1번 줄에는 괄호가 없지만 item은 튜플에 붙습니다. 튜플을 만드는 것은 괄호가 아니라 쉼표입니다. print(item)은 튜플을 괄호와 쉼표로 찍습니다. 2번 줄은 왼쪽에 이름 둘을 늘어놓았습니다. 파이썬은 오른쪽 튜플을 풀어 name을 첫째 값 '커피'에, price를 둘째 값 4000에 붙입니다. 값을 묶는 일과 푸는 일을 자료에 따라 패킹(packing)·언패킹(unpacking)이라고 부릅니다.

3오른쪽을 먼저 묶고 나서 푼다

그림의 코드입니다. 2번 줄은 a와 b가 붙은 값을 맞바꿉니다.

a, b = 1, 2
a, b = b, a
print(a, b)
실행 결과
2 1

대입은 오른쪽을 먼저 계산해 값 하나를 만들고, 그다음에 이름을 붙입니다(규칙 2). 2번 줄의 오른쪽 b, a는 먼저 (2, 1)로 묶입니다. 그 뒤에 묶음을 풀어 a를 2에, b를 1에 붙입니다. 이름을 붙이기 전에 오른쪽 값이 다 정해져 있어서 둘이 맞바뀝니다.

튜플 없이 대입 두 줄로 맞바꾸려 했습니다. 무엇이 찍힐까요?

a = 1
b = 2
a = b
b = a
print(a, b)
실행 결과 보기
실행 결과
2 2

예상과 다르면 그곳이 배울 곳입니다. 2 1을 예상했다면 a에 옛 값 1이 남아 있다고 본 것입니다(상자 생각). 줄은 위에서 아래로 하나씩 실행됩니다(규칙 1). 3번 줄이 a를 b와 같은 2에 다시 붙이는 순간 a와 1의 연결이 끊깁니다(규칙 3). 4번 줄의 오른쪽 a는 이미 2라서 b도 2에 붙습니다. a, b = b, a는 오른쪽 값 둘을 먼저 묶어 두므로 이런 일이 생기지 않습니다(규칙 2).

예제

메뉴 묶음을 하나씩 풀어 찍기

메뉴마다 (이름, 가격) 튜플을 하나씩 담은 리스트입니다. 1~4번 줄은 한 문장이고, 폰 화면에 맞게 튜플마다 줄을 나눴습니다.

menu = [
    ('커피', 4000),
    ('라테', 4500),
]
for name, price in menu:
    print(name, price)
실행 결과
커피 4000
라테 4500

for는 바퀴마다 튜플 하나를 꺼내, 2절의 name, price = item처럼 풀어서 name과 price를 값에 하나씩 붙입니다. 딕셔너리 돌기에서 같은 꼴을 다시 씁니다.

자주 틀리는 곳

틀린 생각: 값 하나를 괄호로 감싸면 값이 하나뿐인 튜플이 된다.

튜플을 괄호와 함께 배워서 괄호가 튜플을 만드는 것처럼 보입니다. 튜플을 만드는 것은 쉼표입니다.

a = (4000)
b = (4000,)
print(a, b)
실행 결과
4000 (4000,)

1번 줄의 괄호는 (1 + 2) * 3의 괄호처럼 계산을 묶을 뿐이라, a는 수 4000에 붙습니다. 2번 줄처럼 쉼표가 있어야 값 하나짜리 튜플이 되고, 파이썬도 쉼표를 붙여 (4000,)로 찍습니다.

맞는 규칙: 튜플을 만드는 것은 괄호가 아니라 쉼표입니다. 값이 하나뿐인 튜플은 (4000,)처럼 값 뒤에 쉼표를 붙입니다.

틀린 생각: 이름보다 값이 많으면 남는 값은 버리고 앞에서부터 붙인다.

앞 이름부터 차례로 붙이니, 이름이 모자라면 남는 값은 버려질 것 같습니다. 파이썬은 남는 값을 버리지 않습니다.

a, b = 1, 2, 3
실행 결과
Traceback (most recent call last):  File "main.py", line 1, in <module>    a, b = 1, 2, 3    ^^^^ValueError: too many values to unpack (expected 2, got 3)

파이썬 3.13 이하에서는 끝의 , got 3 없이 찍히지만 오류 이름과 뜻은 같습니다.

맨 아래 줄은 '풀어 붙일 값이 너무 많다 — 이름 2개를 기대했는데 값이 3개'라는 뜻이고, 실행은 이 줄에서 멈춥니다.

맞는 규칙: 풀어 붙이는 대입은 왼쪽 이름 수와 오른쪽 값 수가 같아야 합니다. 수가 맞지 않으면 ValueError로 멈추니 이름 수를 값 수에 맞춥니다.

틀린 생각: 튜플도 리스트처럼 append()로 값을 늘릴 수 있다.

튜플도 리스트처럼 자리 번호로 꺼내니, 리스트의 메서드도 쓸 수 있을 것 같습니다. 바꿀 수 없는 묶음에는 늘리는 메서드가 없습니다.

item = ('커피', 4000)
item.append(2)
실행 결과
Traceback (most recent call last):  File "main.py", line 2, in <module>    item.append(2)    ^^^^^^^^^^^AttributeError: 'tuple' object has no attribute 'append'. Did you mean to use a 'list' object?

파이썬 3.14 이하에서는 끝의 힌트 없이 찍히지만 오류 이름과 뜻은 같습니다.

맨 아래 줄은 '튜플에는 append라는 메서드가 없다'는 뜻입니다. 끝의 Did you mean …은 리스트를 쓰려던 것 아니냐는 파이썬의 짐작입니다.

맞는 규칙: 튜플은 만든 뒤 바꿀 수 없어 칸을 늘리거나 바꾸는 메서드가 없습니다. 값을 더해 가야 하는 묶음은 리스트로 만듭니다.

확인 문제

1. 3절 첫 코드의 값을 바꾸고, 맞바꾸기 전에도 한 번 찍습니다. 무엇이 찍히는지 두 줄을 글자 그대로 쓰세요.

a, b = 3, 5
print(a, b)
a, b = b, a
print(a, b)
힌트

3번 줄의 오른쪽 b, a에 그때 a와 b가 붙은 값을 넣어 적어 봅니다.

정답
실행 결과
3 5
5 3

3번 줄의 오른쪽 b, a가 먼저 (5, 3)으로 묶이고, 풀어서 a는 5에, b는 3에 붙습니다.

흔한 오답은 둘째 줄을 5 5로 쓰는 것입니다. 3번 줄을 a = b 다음 b = a처럼 왼쪽부터 한 이름씩 붙인다고 본 것입니다. 오른쪽을 다 계산한 뒤에 붙입니다(규칙 2).

2. 메뉴 튜플을 풀어 라테 4500원을 찍으려 합니다. 2번 줄의 빈칸을 채우세요.

item = ('라테', 4500)
________ = item
print(f'{name} {price}원')
힌트

3번 줄이 쓰는 이름 둘과, 1번 줄 튜플의 값 차례를 봅니다.

정답

name, price입니다.

item = ('라테', 4500)
name, price = item
print(f'{name} {price}원')
실행 결과
라테 4500원

2번 줄이 튜플을 풀어 name을 '라테'에, price를 4500에 붙입니다.

다르게 써도 되는 곳: (name, price)나 [name, price]처럼 이름 둘을 괄호로 감싸도 같습니다.

흔한 오답은 price, name입니다. 그러면 price가 '라테'에, name이 4500에 붙어 4500 라테원이 찍힙니다. 4500이 가격이라서 이름 price를 찾아간다고 본 것입니다(이름이 뜻을 정한다는 생각). 풀어 붙이는 대입은 왼쪽 이름을 값의 차례대로 짝짓습니다.

3. 달리기 기록에서 금메달(gold)과 은메달(silver) 이름이 거꾸로 적혔습니다. 두 이름이 붙은 값을 맞바꿔 민지 준호를 찍으려 했는데 민지 민지가 찍혔습니다. 몇 번 줄을 어떻게 고쳐야 할까요?

gold = '준호'
silver = '민지'
gold = silver
silver = gold
print(gold, silver)
실행 결과
민지 민지
힌트

3번 줄이 실행된 뒤에 '준호'를 가리키는 이름이 남아 있는지 봅니다.

정답

3·4번 줄을 한 줄 gold, silver = silver, gold⁠로 고칩니다.

gold = '준호'
silver = '민지'
gold, silver = silver, gold
print(gold, silver)
실행 결과
민지 준호

고치기 전 3번 줄은 gold를 '민지'에 다시 붙여 '준호'와의 연결을 끊었습니다. 4번 줄의 오른쪽 gold는 이미 '민지'라서 silver도 '민지'에 붙었습니다. 고친 3번 줄은 오른쪽을 먼저 ('민지', '준호')로 묶은 뒤 풀어 붙입니다(규칙 2).

다르게 고쳐도 되는 곳: 3번 줄 앞에 keep = gold를 두고 4번 줄을 silver = keep으로 써도, 끊기기 전에 '준호'에 이름 keep을 붙여 두어 민지 준호가 찍힙니다.

흔한 오답은 3·4번 줄의 차례만 바꾸는 것입니다. silver = gold가 먼저 실행되면 silver도 '준호'에 붙어 준호 준호가 찍힙니다. 맞바뀌지 않은 까닭이 줄 차례에 있다고 본 것입니다.

아래 코드 2번 줄을 name, price, qty = item⁠으로 바꾸면 무엇이 찍히는지 봅니다.

처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

파이썬 목차로