프로그램을 작성하다 보면 같은 작업을 여러 번 수행해야 하는 순간이 많다. 파이썬은 반복 작업을 처리하기 위해 while 문과 for 문을 제공한다. 여기에 range(), enumerate(), zip()과 같은 도구를 함께 사용하면 반복 코드를 간결하고 읽기 쉽게 작성할 수 있다.
이번 글에서는 파이썬 반복문의 기본 사용법부터 흐름 제어, 중첩 반복문, 컴프리헨션까지 정리한다.
while 문while 문은 조건식이 True인 동안 코드 블록을 반복해서 실행한다. 반복 횟수가 정해져 있지 않고, 특정 조건이 만족될 때까지 작업해야 할 때 유용하다.
while 조건식:
실행할 코드
while 문을 사용할 때는 언젠가 조건식이 False가 되도록 상태를 변경해야 한다. 그렇지 않으면 반복이 끝나지 않는 무한 루프가 발생한다.
i = 1
while i <= 5:
print("Hello Python")
i += 1
위 코드는 i가 1부터 5까지 증가하는 동안 문자열을 다섯 번 출력한다. i += 1이 없다면 i <= 5가 계속 참이므로 반복이 종료되지 않는다.
i = 1
total = 0
while i <= 10:
total += i
i += 1
print(f"1부터 10까지의 합: {total}")
반복문 안에서 현재 숫자를 total에 누적하고, 다음 숫자를 처리하기 위해 i를 1씩 증가시킨다.
i = 1
total = 0
while i <= 100:
if i % 2 == 0:
total += i
i += 1
print(f"1부터 100까지 짝수의 합: {total}")
i % 2 == 0은 i를 2로 나눈 나머지가 0인지 검사한다. 따라서 짝수만 합계에 포함된다.
dan = int(input("원하는 단을 입력하세요: "))
print(f"{dan}단")
i = 1
while i <= 9:
print(f"{dan} * {i} = {dan * i}")
i += 1
사용자로부터 입력을 받거나 어떤 이벤트가 발생할 때까지 기다리는 로직에서는 while 문이 자연스럽게 사용된다.
for 문for 문은 반복 가능한 객체(iterable)의 요소를 하나씩 꺼내 처리한다. 리스트, 튜플, 문자열, 딕셔너리, range 객체 등이 반복 가능한 객체에 해당한다.
for 변수 in 반복_가능한_객체:
실행할 코드
while 문이 조건 중심의 반복이라면, for 문은 데이터의 요소를 순회하는 데 적합하다.
for char in "Hello":
print(char)
# 실행 결과
# H
# e
# l
# l
# o
문자열도 반복 가능한 객체이므로 문자 하나씩 꺼내 처리할 수 있다.
numbers = [10, 20, 30, 40]
for number in numbers:
print(number)
# 실행 결과
# 10
# 20
# 30
# 40
number에는 반복할 때마다 리스트의 요소가 차례로 저장된다.
scores = [90, 30, 50, 60, 80, 70, 100, 40, 20, 10]
count = 0
for score in scores:
if score >= 60:
count += 1
print(f"60점 이상인 학생의 수는 {count}명이다.") # 60점 이상인 학생의 수는 6명이다.
반복문과 조건문을 함께 사용하면 원하는 조건에 맞는 데이터만 계산할 수 있다.
딕셔너리는 key: value 쌍으로 데이터를 관리한다. 딕셔너리를 그대로 반복하면 기본적으로 키가 나온다.
user = {
"no": 1,
"userid": "apple",
"name": "김사과",
"phone": "010-1234-5678"
}
for key in user:
print(key)
# 실행 결과
# no
# userid
# name
# phone
# 같은 의미의 코드
for key in user.key():
print(key)
값만 필요하면 values()를 사용한다.
for value in user.values():
print(value)
# 실행 결과
# 1
# apple
# 김사과
# 010-1234-5678
# 같은 의미의 코드
for value in user():
print(user[value])
for value in user():
print(user.get(value))
키와 값을 함께 사용하려면 items()를 사용한다.
for i in user.items():
print(i)
# 실행 결과
# ('no', 1)
# ('userid', 'apple')
# ('name', '김사과')
# ('hp', '010-1234-5678')
for key, value in user.items():
print(f"{key}: {value}")
# 실행 결과
# no: 1
# userid: apple
# name: 김사과
# phone: 010-1234-5678
range() 함수range()는 일정한 규칙을 가진 정수 범위를 표현하는 객체를 반환한다. 실제 숫자 목록을 한꺼번에 저장하지 않고 필요한 시점에 값을 제공하므로, 반복문에서 효율적으로 사용할 수 있다.
range(stop)
range(start, stop)
range(start, stop, step)
| 인자 | 설명 |
|---|---|
start | 시작값이며, 생략하면 0이다. |
stop | 종료 기준값이며, 이 값은 포함되지 않는다. |
step | 증가 또는 감소 간격이며, 생략하면 1이다. |
for i in range(10):
print(i, end=" ")
출력 결과는 다음과 같다.
0 1 2 3 4 5 6 7 8 9
range(10)은 0부터 9까지의 값을 제공한다. 마지막 값인 10은 포함하지 않는다는 점이 중요하다.
for i in range(1, 10, 2):
print(i, end=" ")
출력 결과는 다음과 같다.
1 3 5 7 9
total = 0
for number in range(0, 101, 2):
total += number
print(f"1부터 100까지 짝수의 합: {total}")
짝수만 생성하도록 간격을 2로 설정하면 반복문 안에서 별도의 조건 검사를 하지 않아도 된다.
enumerate() 함수반복문에서 데이터의 값뿐 아니라 인덱스도 필요할 때 enumerate()를 사용한다. enumerate()는 반복 가능한 객체의 각 요소를 (인덱스, 값) 형태로 제공한다.
enumerate(iterable, start=0)
fruits = ["apple", "banana", "orange"]
for index, fruit in enumerate(fruits):
print(f"인덱스: {index}, 값: {fruit}")
출력 결과는 다음과 같다.
인덱스: 0, 값: apple
인덱스: 1, 값: banana
인덱스: 2, 값: orange
목록 번호처럼 1부터 출력하고 싶다면 start=1을 지정한다.
fruits = ["apple", "banana", "orange"]
for number, fruit in enumerate(fruits, start=1):
print(f"{number}. {fruit}")
출력 결과는 다음과 같다.
1. apple
2. banana
3. orange
인덱스를 직접 관리하는 변수보다 enumerate()를 사용하면 코드의 목적이 더 분명해진다.
반복문을 이해하려면 이터러블(iterable)과 이터레이터(iterator)의 차이를 알아두는 것이 좋다.
이터러블은 요소를 하나씩 반복해서 꺼낼 수 있는 객체다. 리스트, 문자열, 튜플, 집합, 딕셔너리 등이 여기에 해당한다.
numbers = [10, 20, 30]
for number in numbers:
print(number)
이터레이터는 값을 하나씩 꺼낼 수 있는 상태를 가진 객체다. iter() 함수로 이터러블에서 이터레이터를 만들고, next() 함수로 다음 값을 가져올 수 있다.
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator)) # 10
print(next(iterator)) # 20
print(next(iterator)) # 30
더 이상 꺼낼 값이 없는데 next()를 호출하면 StopIteration 예외가 발생한다.
# print(next(iterator)) # StopIteration 예외가 발생한다.
for 문은 내부적으로 이터레이터에서 값을 하나씩 꺼내고, 값이 모두 소진되면 반복을 종료하는 동작을 수행한다.
zip() 함수zip()은 여러 반복 가능한 객체에서 같은 위치의 값을 하나씩 꺼내 튜플로 묶는다. 서로 관련된 두 개 이상의 데이터를 함께 순회할 때 유용하다.
names = ["김사과", "반하나", "오렌지"]
scores = [95, 87, 100]
for name, score in zip(names, scores):
print(f"{name}: {score}점")
출력 결과는 다음과 같다.
김사과: 95점
반하나: 87점
오렌지: 100점
길이가 다른 반복 가능한 객체를 zip()에 전달하면 가장 짧은 객체의 길이에 맞춰 반복이 종료된다.
names = ["김사과", "반하나", "오렌지"]
scores = [95, 87]
print(list(zip(names, scores)))
# [('김사과', 95), ('반하나', 87)]
continue와 break반복문은 전체 요소를 끝까지 처리하는 경우가 많지만, 특정 상황에서는 현재 반복을 건너뛰거나 반복 자체를 끝내야 한다.
continue: 현재 반복 건너뛰기continue가 실행되면 그 아래의 코드는 수행하지 않고 다음 반복으로 이동한다.
logs = [
"INFO: 사용자 로그인 성공",
"DEBUG: 세션 생성 중",
"ERROR: 데이터베이스 연결 실패",
"INFO: 요청 처리 완료",
"ERROR: 파일 저장 실패"
]
error_logs = []
for log in logs:
if not log.startswith("ERROR"):
continue
error_logs.append(log)
for error in error_logs:
print(error)
이 코드는 ERROR로 시작하는 로그만 수집해 출력한다.
break: 반복 종료하기break가 실행되면 현재 반복문을 즉시 빠져나간다.
orders = ["마우스", "키보드", "모니터", "그래픽카드", "프린터"]
stock = {
"마우스": 10,
"키보드": 5,
"모니터": 0,
"그래픽카드": 2,
"프린터": 3
}
for item in orders:
if stock[item] == 0:
print(f"{item} 재고가 없어 주문 처리를 중단한다.")
break
print(f"{item} 주문을 완료한다.")
재고가 없는 상품을 발견한 뒤에는 뒤의 주문을 확인하지 않고 반복을 종료한다.
중첩 반복문은 반복문 안에 또 다른 반복문을 작성한 구조다. 바깥 반복이 한 번 진행될 때마다 안쪽 반복은 처음부터 끝까지 수행된다.
for outer in range(3):
for inner in range(2):
print(outer, inner)
위 코드는 바깥 반복 3회와 안쪽 반복 2회를 조합하므로 총 6번 출력한다.
0 0
0 1
1 0
1 1
2 0
2 1
for row in range(1, 6):
for _ in range(row):
print("*", end=" ")
print()
출력 결과는 다음과 같다.
*
* *
* * *
* * * *
* * * * *
사용하지 않는 반복 변수는 관례적으로 _로 작성한다.
for dan in range(2, 10):
print(f"{dan}단")
for number in range(1, 10):
print(f"{dan} * {number} = {dan * number}")
print()
중첩 반복문은 행과 열을 가진 데이터, 구구단, 좌표 조합 등을 처리할 때 자주 사용된다. 다만 반복 횟수는 두 반복 범위의 곱만큼 증가하므로 데이터가 클 때는 실행 비용을 살펴야 한다.
컴프리헨션(comprehension)은 반복문을 사용해 새로운 컬렉션을 간결하게 생성하는 문법이다. 리스트, 집합, 딕셔너리를 만들 때 사용할 수 있다.
다음 코드는 0부터 9까지의 값을 가진 리스트를 생성한다.
numbers = [number for number in range(10)]
print(numbers)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
각 값에 연산을 적용할 수도 있다.
numbers = [10, 20, 30, 40]
doubled = [number * 2 for number in numbers]
print(doubled)
# [20, 40, 60, 80]
for 문 뒤의 if는 결과에 포함할 요소를 고르는 필터 역할을 한다.
even_numbers = [number for number in range(10) if number % 2 == 0]
print(even_numbers)
# [0, 2, 4, 6, 8]
위 문법은 다음 반복문과 같은 의미다.
even_numbers = []
for number in range(10):
if number % 2 == 0:
even_numbers.append(number)
모든 요소를 유지하면서 조건에 따라 값을 다르게 저장하려면 표현식 위치에 if ... else ...를 작성한다.
numbers = [-1, 0, -4, 24, 5, -10, 2, 20]
converted = [number if number > 0 else 0 for number in numbers]
print(converted)
# [0, 0, 0, 24, 5, 0, 2, 20]
두 문법은 모양이 비슷하지만 목적이 다르다.
| 문법 | 목적 | 결과의 길이 |
|---|---|---|
[값 for 요소 in 데이터 if 조건] | 조건에 맞는 요소만 선택한다. | 줄어들 수 있다. |
[값1 if 조건 else 값2 for 요소 in 데이터] | 모든 요소를 조건에 따라 변환한다. | 원본과 같다. |
else를 사용하는 조건 표현식은 for 앞에 작성해야 한다. 다음 문법은 필터 위치에서 else를 사용했으므로 오류가 발생한다.
# SyntaxError가 발생한다.
# result = [number for number in numbers if number > 0 else 0]
products = [i * j for i in range(1, 4) for j in range(1, 3)]
print(products)
# [1, 2, 2, 4, 3, 6]
이는 다음 중첩 반복문과 동일한 순서로 실행된다.
products = []
for i in range(1, 4):
for j in range(1, 3):
products.append(i * j)
컴프리헨션이 지나치게 길거나 조건이 복잡해지면 일반 반복문이 더 읽기 쉬울 수 있다.
세트은 중복 값을 허용하지 않는다. 따라서 중복을 제거하면서 값을 가공할 때 세트 컴프리헨션을 사용할 수 있다.
numbers = [1, 2, 3, 4, 5, 2, 3, 4]
unique_numbers = {number for number in numbers}
print(unique_numbers)
# {1, 2, 3, 4, 5}
딕셔너리 컴프리헨션은 키와 값을 함께 지정해 새로운 딕셔너리를 생성한다.
names = ["apple", "banana", "orange"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)
# {'apple': 5, 'banana': 6, 'orange': 6}
반복 코드를 작성할 때는 목적에 맞는 문법을 선택하는 것이 중요하다.
| 상황 | 적합한 문법 |
|---|---|
| 조건이 참인 동안 반복해야 한다. | while 문 |
| 컬렉션의 요소를 순서대로 처리한다. | for 문 |
| 일정 범위의 숫자를 반복한다. | for 문과 range() |
| 순서 번호와 값을 함께 사용한다. | enumerate() |
| 여러 컬렉션의 값을 함께 처리한다. | zip() |
| 특정 요소를 건너뛴다. | continue |
| 특정 조건에서 반복을 종료한다. | break |
| 간단한 규칙으로 새 컬렉션을 만든다. | 컴프리헨션 |