쉼표가 묶음을 만든다
괄호가 아니라 쉼표가 하는 일입니다.
점 하나의 좌표를 다뤄야 한다고 합시다. 변수를 따로 두면 이렇게 씁니다.
point = (3, 7) # 쉼표로 묶고, 보통 소괄호로 감싼다
print(point) # (3, 7)
print(point[0]) # 3 인덱스로 꺼내는 것은 리스트와 같다
print(len(point)) # 2
one = (5,) # 값이 하나면 「쉼표」를 빠뜨리지 않는다
print(type(one)) # <class 'tuple'>
print(type((5))) # <class 'int'> 쉼표가 없으면 그냥 괄호다튜플(tuple)
여러 값을 쉼표로 이어 하나로 묶은 자료형입니다. 리스트처럼 인덱스로 요소를 꺼내 쓰지만, 만든 뒤에는 요소를 바꿀 수 없다는 점이 다릅니다.
묶는 것은 괄호가 아니라 쉼표입니다
(5)는 괄호로 감싼 정수 5일 뿐이고, (5,)라야 값이 하나인 튜플입니다. 값이 하나일 때 쉼표를 빠뜨리는 것이 흔한 실수입니다. 값이 하나도 없는 빈 튜플 ()만은 괄호로 만듭니다.
스스로 확인
point = 3, 7처럼 괄호 없이 쓰면 어떻게 될까요? 괄호와 쉼표 중 무엇이 튜플을 만드는지 생각해 보세요.
바꿀 수 없다는 것
바꿀 수 없다는 제약이 왜 쓸모 있는지 살펴봅니다.
같은 값 세 개를 리스트와 튜플에 각각 저장해 보면 차이가 드러납니다.
scores = [90, 85, 72]
scores[0] = 100 # 리스트는 요소를 바꿀 수 있다
print(scores) # [100, 85, 72]
point = (3, 7)
# point[0] = 100 # TypeError. 튜플은 요소를 바꿀 수 없다
new_point = (100, 7) # 다른 값이 필요하면 「새로 만든다」
print(new_point) # (100, 7)튜플은 요소를 바꿀 수도, 추가하거나 삭제할 수도 없습니다. 다른 값이 필요하면 새 튜플을 만듭니다.
바꿀 수 없어서 좋은 점
바뀌면 안 되는 값을 튜플로 저장해 두면, 그 값을 실수로 바꾸려는 코드가 바로 오류를 냅니다. 값이 조용히 달라진 채 실행이 이어지는 것보다 훨씬 찾기 쉽습니다. 좌표, 요일 이름, 색을 나타내는 빨강 · 초록 · 파랑 세 값처럼 함께 있어야 의미가 있는 값에 씁니다.
| 리스트 | 튜플 | |
|---|---|---|
| 만드는 법 | [90, 85] |
(90, 85) |
| 꺼내기 | a[0] |
a[0] — 같다 |
| 요소 바꾸기 | 된다 | 안 된다 |
| 추가 · 삭제 | append·pop |
없다 |
| 어울리는 것 | 개수가 늘고 주는 값 | 함께 있어야 의미가 있는 값 |
이 수업에서 쓰는 범위
튜플 안에 리스트를 넣으면 그 리스트는 바뀔 수 있습니다. 이 수업에서는 숫자 · 문자열처럼 그 자체가 바뀌지 않는 값만 튜플에 저장합니다. 그 범위에서는 「튜플은 바꿀 수 없다」가 그대로 성립합니다.
스스로 확인
요일 이름 일곱 개를 저장한다면 리스트와 튜플 중 어느 쪽이 알맞을까요? 그렇게 고른 이유도 말해 보세요.
묶음을 풀어 나누어 대입하기
입력을 나눠 받을 때 쓰는 문법에 이름을 붙입니다.
묶은 것은 다시 풀 수 있습니다.
scores = (90, 85, 72)
kor, eng, math_ = scores # 묶음이 풀려 변수 셋에 차례로 대입된다
print(kor, eng, math_) # 90 85 72
# 입력을 나누어 받을 때 쓰는 이 줄도 언패킹이다
kor, eng = map(int, "80 91".split())
print(kor + eng) # 171언패킹(unpacking)
튜플 같은 묶음을 풀어 그 값을 여러 변수에 차례로 나누어 대입하는 것입니다. 왼쪽에 적은 변수의 개수와 오른쪽 묶음에 든 값의 개수가 같아야 합니다.
입력을 나누어 받을 때 흔히 쓰는 kor, eng = map(int, input().split())도 언패킹입니다. map이 만든 값 둘이 변수 둘에 나뉘어 대입됩니다.
scores = (90, 85, 72)
# a, b = scores # ValueError: too many values to unpack
# a, b, c, d = scores # ValueError: not enough values to unpack
a, b, c = scores # 변수 셋, 값 셋 — 개수가 맞는다
print(a, b, c) # 90 85 72개수가 맞지 않으면 바로 멈춥니다
값이 남아도, 모자라도 ValueError가 납니다. 오류가 나는 편이 낫습니다 — 조용히 넘어가면 어디서 어긋났는지 나중에 찾아야 합니다.
두 변수의 값을 맞바꾸는 일도 한 줄로 끝납니다.
a = 1
b = 2
a, b = b, a # 오른쪽이 「먼저」 (2, 1)로 묶이고, 그 묶음이 왼쪽에 풀린다
print(a, b) # 2 1=는 오른쪽을 먼저 계산합니다. 오른쪽 b, a가 먼저 (2, 1)이라는 묶음이 되고, 그 묶음이 왼쪽으로 풀립니다. 그래서 값을 잠시 저장해 둘 임시 변수가 필요 없습니다.
스스로 확인
a, b = b, a를 a = b와 b = a 두 줄로 나눠 쓰면 어떤 값이 될까요? 왜 그런지 설명해 보세요.
함수가 여러 값을 반환할 때
반환값은 언제나 하나입니다.
가장 낮은 점수와 가장 높은 점수를 한 번에 알고 싶습니다. 함수는 값을 하나만 반환하는데, 어떻게 해야 할까요?
def min_max(scores):
return min(scores), max(scores) # 쉼표로 묶은 「튜플 하나」를 반환한다
result = min_max([90, 85, 72])
print(result) # (72, 90) 튜플 그대로 받았다
print(type(result)) # <class 'tuple'>
low, high = min_max([90, 85, 72]) # 받으면서 바로 언패킹할 수도 있다
print(low, high) # 72 90반환값은 여전히 하나입니다
return low, high는 값 둘을 반환하는 것이 아니라 값 둘을 묶은 튜플 하나를 반환합니다. 호출한 곳에서 언패킹할지 튜플째 받을지를 고르는 것입니다.
result = min_max(...)로 받으면 튜플이 그대로 저장되고, low, high = min_max(...)로 받으면 받으면서 바로 언패킹됩니다. 어느 쪽이 옳다기보다, 반환값을 뒤에서 어떻게 쓸지에 따라 고릅니다.
스스로 확인
low, high, avg = min_max(scores)라고 적으면 어떻게 될까요? min_max를 고치지 않는다면요?
반복과 함께 쓰기
쌍으로 나오는 값은 쌍으로 받습니다.
리스트를 순회하면서 각 값의 인덱스도 함께 알고 싶을 때가 있습니다.
fruits = ["사과", "바나나", "포도"]
for i, name in enumerate(fruits): # 인덱스와 값이 「쌍」으로 나온다
print(i, name) # 0 사과 / 1 바나나 / 2 포도
for pair in enumerate(fruits):
print(pair) # (0, '사과') / (1, '바나나') / (2, '포도')enumerate
리스트를 순회하면서 인덱스와 값의 쌍을 하나씩 만들어 주는 내장 함수입니다. 그 쌍이 바로 튜플이라서, for i, name in ...처럼 받으면서 언패킹할 수 있습니다.
따로 세지 않아도 됩니다
i = 0으로 시작해 반복할 때마다 i = i + 1로 1씩 더하는 방법도 있습니다. 틀리지는 않지만 더하는 줄을 빠뜨리면 i가 0에 멈춘 채 반복이 이어집니다. enumerate는 그럴 일이 없습니다.
두 리스트를 나란히 순회하고 싶을 때도 있습니다.
names = ["학생 A", "학생 B", "학생 C"]
scores = [90, 85, 72]
for name, score in zip(names, scores):
print(name, score) # 학생 A 90 / 학생 B 85 / 학생 C 72
short = [1, 2]
print(list(zip(short, scores))) # [(1, 90), (2, 85)] 짧은 쪽에서 멈춘다zip
여러 리스트에서 같은 인덱스의 값끼리 튜플로 묶어 하나씩 만들어 주는 내장 함수입니다. 길이가 다르면 짧은 쪽에서 멈춥니다.
길이가 다르면 조용히 잘립니다
오류가 나지 않으므로 뒤쪽 값이 사라진 것을 눈치채기 어렵습니다. 두 리스트가 같은 길이라야 하는 경우라면 len으로 먼저 확인합니다.
배운 것을 모으면 표 하나를 이렇게 순회할 수 있습니다.
def summary(scores):
"""가장 낮은 점수, 가장 높은 점수, 평균을 한 번에 반환한다."""
return min(scores), max(scores), round(sum(scores) / len(scores), 1)
names = ["학생 A", "학생 B", "학생 C"]
table = [[90, 85, 72], [60, 71, 95], [88, 88, 84]]
for name, scores in zip(names, table):
low, high, avg = summary(scores)
print(f"{name}: 최저 {low}, 최고 {high}, 평균 {avg}")
# 학생 A: 최저 72, 최고 90, 평균 82.3
# 학생 B: 최저 60, 최고 95, 평균 75.3
# 학생 C: 최저 84, 최고 88, 평균 86.7zip이 이름과 점수 리스트를 쌍으로 묶어 주고, summary가 값 셋을 튜플로 반환하고, 호출한 곳에서 변수 셋으로 언패킹됩니다. 이 강의노트에서 본 것이 한 줄씩 그대로 쓰였습니다.
스스로 확인
enumerate와 zip을 함께 써서 「1번 학생 A 90점」처럼 출력하려면 코드를 어떻게 작성해야 할까요?
핵심 정리
묶는 것은 쉼표다
(5)는 정수 5이고 (5,)라야 튜플이다. 괄호는 대개 읽기 좋으라고 붙이는 것이고, 튜플을 만드는 것은 쉼표다. 빈 튜플 ()만 예외다.
바꿀 수 없다는 것이 쓸모다
튜플은 요소를 바꿀 수 없다. 그래서 바뀌면 안 되는 값을 튜플로 저장하면 그 값을 실수로 바꾸려는 코드가 바로 오류를 낸다.
언패킹은 개수가 맞아야 한다
왼쪽 변수의 개수와 오른쪽 값의 개수가 같아야 한다. 어긋나면 ValueError다. a, b = b, a가 되는 것도 오른쪽이 먼저 묶이기 때문이다.
반환값은 튜플 하나다
return low, high는 튜플 하나를 반환한다. 호출한 곳에서 튜플째 받을지 언패킹할지를 고른다.
쌍으로 나오면 쌍으로 받는다
enumerate는 인덱스와 값을, zip은 여러 리스트에서 같은 인덱스의 값을 튜플로 묶어 하나씩 만들어 준다. zip은 짧은 쪽에서 멈춘다.
확인 퀴즈
배운 내용을 확인해 봅시다.
1. x = (7)일 때 type(x)는?
2. a, b = (1, 2, 3)을 실행하면?
3. 다음 중 틀린 설명은?