게임 상점에 아이템 목록이 있다고 해 보겠습니다. 아이템 이름만 출력할 때는 평범한 for문이면 충분합니다. 그런데 화면에 1번, 2번처럼 번호도 붙이고, 다른 리스트에 저장된 가격까지 함께 보여주려면 반복문이 조금 복잡해집니다.
이럴 때 파이썬의 enumerate()와 zip()을 사용합니다. 두 함수의 역할은 분명히 다릅니다.
enumerate(): 한 목록에서 번호와 값을 함께 꺼냅니다.zip(): 여러 목록에서 같은 위치의 값과 값을 짝지어 꺼냅니다.
이번 글에서는 두 함수가 실제로 무엇을 돌려주는지, start=1이 리스트 인덱스를 바꾸는지, zip()에 길이가 다른 리스트를 넣으면 왜 아이템이 조용히 사라지는지까지 직접 실행해 확인합니다.

실행 기준
- 확인일 : 2026년 8월 26일
- 운영체제 : Windows
- 직접 실행 : Python 3.9.13
- 공식 문서 확인 : Python 3.14.7 튜토리얼과 내장 함수 문서
본문의 예제 파일 6개를 직접 실행했습니다. 정상 예제 5개는 번호, 아이템, 가격의 출력 순서를 확인했고, 길이가 다른 두 리스트를 검사하는 예제는 의도한 ValueError가 발생하는 것을 확인했습니다.
번호를 직접 관리하면 무엇이 불편할까요
먼저 아이템 앞에 번호를 붙이는 코드를 평범한 for문으로 작성해 보겠습니다.
items = ["potion", "sword", "shield"]
number = 1
for item in items:
print(number, item)
number += 1
1 potion
2 sword
3 shield
실행 결과는 맞습니다. 다만 number 변수를 반복문 밖에서 만들고, 반복할 때마다 number += 1도 잊지 않아야 합니다. 중간에 continue가 들어가거나 증가시키는 줄을 빼먹으면 번호와 아이템의 위치가 어긋날 수 있습니다.
Python for문 사용법에서 배운 반복 원리는 그대로 두고 번호 관리만 파이썬에 맡기는 함수가 enumerate()입니다.
enumerate는 번호와 값을 한 쌍으로 만듭니다
같은 코드를 enumerate()로 바꿔 보겠습니다.
items = ["potion", "sword", "shield"]
for index, item in enumerate(items):
print(index, item)
0 potion
1 sword
2 shield
enumerate(items)는 반복할 때마다 (번호, 값) 형태의 쌍을 줍니다. 첫 번째 반복에서는 (0, "potion"), 두 번째 반복에서는 (1, "sword")가 나옵니다.
print(list(enumerate(items)))
[(0, 'potion'), (1, 'sword'), (2, 'shield')]
괄호로 묶인 각 결과는 파이썬 튜플입니다. for index, item은 튜플 안의 두 값을 index와 item 변수로 나누어 받습니다. 이것을 튜플 언패킹이라고 합니다.
따라서 다음 코드를 읽을 때는 “아이템에서 값을 하나 꺼낸다”에서 끝내지 말고, 번호와 아이템을 한 쌍으로 받아 두 변수에 나눈다고 생각하면 됩니다.
enumerate(items)
→ (0, "potion")
→ index에는 0, item에는 "potion"
start=1은 화면 번호만 1부터 시작하게 합니다
사람에게 보여주는 메뉴 번호는 보통 1부터 시작합니다. 이때 index + 1을 매번 계산하는 대신 start=1을 지정할 수 있습니다.
items = ["potion", "sword", "shield"]
for number, item in enumerate(items, start=1):
print(number, item)
1 potion
2 sword
3 shield
여기서 조심할 점이 있습니다. start=1은 리스트 자체의 인덱스를 1부터 바꾸지 않습니다. enumerate()가 붙여 주는 번호의 시작값만 바꿉니다.
print("첫 번째 실제 인덱스:", items.index("potion"))
첫 번째 실제 인덱스: 0
화면에 보이는 1번 potion을 다시 리스트에서 꺼내려면 items[1]이 아니라 items[number - 1]입니다. 파이썬 리스트의 실제 첫 인덱스는 여전히 0이기 때문입니다.
zip은 같은 위치의 값끼리 묶습니다
이번에는 아이템 이름과 가격이 서로 다른 리스트에 들어 있다고 해 보겠습니다.
items = ["potion", "sword", "shield"]
prices = [50, 120, 90]
인덱스로 접근하면 다음처럼 작성할 수 있습니다.
for index in range(len(items)):
print(items[index], prices[index])
동작은 하지만 index가 두 번 나오고, 두 리스트의 길이가 다르면 존재하지 않는 위치를 조회할 위험이 있습니다. zip()은 같은 위치의 값끼리 먼저 짝을 만들어 줍니다.
for item, price in zip(items, prices):
print(item, price)
potion 50
sword 120
shield 90
짝이 만들어지는 순서는 다음과 같습니다.
items[0] + prices[0] → ("potion", 50)
items[1] + prices[1] → ("sword", 120)
items[2] + prices[2] → ("shield", 90)
zip()도 각 반복에서 튜플을 하나씩 줍니다. 그래서 for item, price로 두 값을 나누어 받을 수 있습니다. 지퍼의 양쪽 이빨이 같은 위치에서 맞물리는 모습처럼 생각하면 기억하기 쉽습니다.
zip 결과는 필요할 때 하나씩 만들어집니다
enumerate()와 zip()의 결과를 그대로 출력하면 내용 대신 객체 표시가 나옵니다.
paired = zip(items, prices)
print(paired)
<zip object at 0x...>
zip()은 결과 전체를 미리 리스트로 만들지 않고, for가 다음 값을 요구할 때 한 쌍씩 내놓는 이터레이터를 반환합니다. 내용을 한꺼번에 확인하려면 list()로 감쌉니다.
print(list(zip(items, prices)))
[('potion', 50), ('sword', 120), ('shield', 90)]
한 번 끝까지 사용한 zip 객체는 다시 반복해도 남은 값이 없습니다. 같은 짝이 다시 필요하면 zip(items, prices)를 새로 만들면 됩니다. 초보 단계에서는 zip()을 변수에 오래 보관하기보다 for문 안에서 바로 사용하는 편이 덜 헷갈립니다.
길이가 다르면 zip은 짧은 쪽에서 멈춥니다
zip()에서 가장 중요한 주의점입니다. 아이템은 세 개지만 가격이 두 개뿐인 경우를 실행해 보겠습니다.
items = ["potion", "sword", "shield"]
prices = [50, 120]
for item, price in zip(items, prices):
print(item, price)
print("items 길이:", len(items))
print("prices 길이:", len(prices))
potion 50
sword 120
items 길이: 3
prices 길이: 2
오류가 나지 않았지만 shield가 결과에서 사라졌습니다. 기본 zip()은 가장 짧은 반복 대상이 끝나는 순간 멈추기 때문입니다. 남은 값에 어떤 가격을 붙여야 할지 임의로 결정하지 않습니다.
이 동작이 의도일 수도 있습니다. 하지만 주문명과 결제 금액처럼 두 목록의 개수가 반드시 같아야 하는 데이터라면 조용히 사라지는 값이 버그가 됩니다.
Python 3.9에서는 길이를 먼저 확인할 수 있습니다
이번 글의 직접 실행 환경인 Python 3.9.13에서는 zip()을 사용하기 전에 길이를 비교하는 방법이 분명합니다.
items = ["potion", "sword", "shield"]
prices = [50, 120]
if len(items) != len(prices):
raise ValueError("items와 prices의 길이가 다릅니다.")
for item, price in zip(items, prices):
print(item, price)
실행 결과입니다.
Traceback (most recent call last):
File "05_length_guard.py", line 5, in <module>
raise ValueError("items와 prices의 길이가 다릅니다.")
ValueError: items와 prices의 길이가 다릅니다.

프로그램이 멈추는 것이 불편해 보여도, 잘못 짝지어진 데이터를 계속 처리하는 것보다 문제 지점을 즉시 알 수 있다는 장점이 있습니다. 오류를 잡아 안내 문구로 바꾸는 방법은 파이썬 try except 사용법에서 이어서 적용할 수 있습니다.
Python 3.10 이상에서는 길이가 다를 때 ValueError를 발생시키는 strict=True 옵션도 사용할 수 있습니다.
for item, price in zip(items, prices, strict=True):
print(item, price)
이 옵션은 Python 3.10에서 추가되었습니다. Python 3.9 이하에서는 지원하지 않으므로, 실행 버전을 확인하지 않고 그대로 복사하면 TypeError가 납니다. 버전과 관계없이 동작해야 하는 입문 예제에서는 길이 비교가 이해하기 쉽고 호환 범위도 넓습니다.
enumerate와 zip을 함께 사용할 수 있습니다
이제 번호, 아이템, 가격을 한 번에 출력해 보겠습니다.
items = ["potion", "sword", "shield"]
prices = [50, 120, 90]
for number, (item, price) in enumerate(zip(items, prices), start=1):
print(f"{number}. {item} - {price} gold")
1. potion - 50 gold
2. sword - 120 gold
3. shield - 90 gold
괄호가 많아 보여도 안쪽부터 읽으면 간단합니다.
zip(items, prices) → (아이템, 가격)을 만든다
enumerate(..., start=1) → 그 쌍에 번호를 붙인다
number, (item, price) → 번호와 쌍을 세 변수로 나눈다
enumerate()가 받는 값은 꼭 단순한 문자열일 필요가 없습니다. zip()이 만든 (아이템, 가격) 튜플도 하나의 값이므로 그 바깥에 번호를 붙일 수 있습니다.
enumerate와 zip 중 무엇을 선택할까요
| 필요한 값 | 사용할 함수 | 반복문 모양 |
|---|---|---|
| 목록의 값만 필요 | 사용하지 않음 | for item in items |
| 번호와 값이 필요 | enumerate() |
for number, item in enumerate(items) |
| 두 목록의 값이 필요 | zip() |
for item, price in zip(items, prices) |
| 번호와 두 목록의 값이 필요 | 둘 다 사용 | for number, (item, price) in enumerate(zip(...)) |
아이템 이름처럼 고유한 값으로 가격을 자주 찾아야 한다면 두 리스트보다 딕셔너리 한 개가 더 자연스러울 수 있습니다. 파이썬 딕셔너리 사용법의 키와 값 구조를 사용하면 prices["potion"]처럼 이름으로 가격을 찾을 수 있습니다.
반대로 이미 두 열의 데이터가 각각 리스트로 있고 같은 위치끼리 한 번씩 처리해야 한다면 zip()이 잘 맞습니다. 자료의 관계에 맞는 구조를 고르는 것이 함수를 많이 쓰는 것보다 중요합니다.
두 함수의 역할만 구분하면 됩니다
enumerate()와 zip()은 for문을 없애는 함수가 아닙니다. for문이 한 번 반복할 때 받을 값을 알맞은 묶음으로 준비합니다.
enumerate : 번호 + 값
zip : 값 + 값
enumerate(..., start=1)을 써도 리스트의 실제 인덱스는 0부터 시작합니다. zip()은 입력 길이가 다르면 기본적으로 짧은 쪽에서 멈추므로, 모든 값이 반드시 짝을 가져야 하는 데이터는 길이를 검사하거나 Python 3.10 이상의 strict=True를 사용해야 합니다.
처음에는 두 함수를 따로 실행해 결과 튜플을 확인해 보세요. 그다음 상점 목록처럼 번호와 두 값이 모두 필요한 순간에 enumerate(zip(...))로 합치면, 괄호가 무엇을 묶는지도 자연스럽게 보입니다.
참고한 공식 문서
'IT프로그래밍 > 파이썬' 카테고리의 다른 글
| 파이썬 ModuleNotFoundError 해결 : pip와 실행 Python 경로 맞추기 (0) | 2026.09.04 |
|---|---|
| 파이썬 t-string 사용법 : f-string과 다른 점과 Python 3.14 처리 원리 (0) | 2026.09.02 |
| 파이썬 sorted와 sort 차이 : key와 lambda로 원하는 기준 정렬하기 (0) | 2026.09.01 |
| 파이썬 리스트 컴프리헨션 : for문 한 줄의 순서와 if 위치 (0) | 2026.08.21 |
| 파이썬 파일 읽기 쓰기 : with open으로 주문장을 남기는 방법 (0) | 2026.08.20 |
| 파이썬 try except 사용법 : ValueError와 KeyError 예외 처리 (0) | 2026.08.20 |
| 파이썬 set 사용법 : 리스트 중복 제거와 순서 유지 방법 (0) | 2026.08.18 |
| 파이썬 튜플과 리스트 차이 : 원소 1개에는 왜 쉼표가 필요할까 (0) | 2026.08.14 |
댓글