Python 반복문

src/content/documents/python/pythonic-loops-enumerate-zip.json

결론부터 보기

처리할 iterable의 항목이 있다면 for를, 특정 조건이 참인 동안 반복해야 한다면 while을 먼저 검토한다. Python의 for는 단순한 ‘횟수 반복문’이 아니라 iterable에서 항목을 하나씩 받아 순회하는 문장이다.

도구

선택 기준

대표 상황

for

iterable의 각 항목을 처리

행, 파일, 키, 정해진 범위 순회

while

종료 조건이 될 때까지 반복

재시도, 입력 대기, 상태 변화

for·range·enumerate로 데이터 순회하기

range(start, stop, step)는 정수의 불변 산술 시퀀스를 나타내며 stop은 포함하지 않는다. 실제 데이터 항목이 아니라 정수 범위가 필요할 때 사용한다.

for batch_number in range(1, 4):
    print(f"batch {batch_number}")
batch 1
batch 2
batch 3

항목과 순번이 함께 필요하면 range(len(items))로 다시 인덱싱하기보다 enumerate(items)를 사용한다. iterable에서 항목을 직접 받으면서 카운터도 얻어 의도가 선명하고 인덱스 실수를 줄인다. start로 첫 번호를 바꿀 수 있다.

scores = [88, 91, 76]

for row_number, score in enumerate(scores, start=1):
    print(row_number, score)
1 88
2 91
3 76

while은 종료 조건을 코드로 보이게 만든다

while은 매 반복 전에 조건을 다시 평가한다. 조건에 쓰인 상태를 갱신하지 않으면 무한 반복이 될 수 있으므로 최대 횟수나 시간 제한처럼 안전한 경계를 둔다.

attempt = 0
max_attempts = 3

while attempt < max_attempts:
    attempt += 1
    print(f"attempt {attempt}")
    if attempt == 2:
        print("connected")
        break
else:
    print("failed")
attempt 1
attempt 2
connected

break·continue로 흐름 제어하기

break는 가장 가까운 반복문을 즉시 끝낸다. continue는 현재 항목의 남은 코드를 건너뛰고 다음 반복으로 간다. 무시할 항목을 초반에 continue하는 guard 형태는 깊은 중첩을 줄이는 데 유용하다.

raw_values = ["10", "", "bad", "20"]
valid = []

for item in raw_values:
    if not item:
        continue
    if not item.isdigit():
        print(f"stopped: {item}")
        break
    valid.append(int(item))

print(valid)
stopped: bad
[10]

반복문의 else는 break가 없을 때 실행된다

for의 iterable이 정상 소진되거나 while 조건이 거짓이 되어 끝나면 else가 실행된다. 반복이 한 번도 실행되지 않았을 때만 실행되는 문법이 아니다. break로 빠져나가면 건너뛰고, continue만으로는 건너뛰지 않는다. 반복 중 처리되지 않은 예외가 전파되면 정상 종료가 아니므로 else까지 도달하지 않는다.

values = [2, 4, 7, 9]
target = 5

for index, value in enumerate(values):
    if value == target:
        print(f"found at {index}")
        break
else:
    print("not found")
not found

검색 성공 시 break하고, 끝까지 찾지 못했을 때 else에서 처리하는 구조다. 상태 변수 found = False를 따로 관리하지 않아도 된다.

zip으로 여러 iterable을 나란히 순회하기

zip은 각 iterable에서 같은 순서의 항목을 튜플로 묶는 지연 iterator를 반환한다. 값을 미리 모두 만들지 않으며 순회할 때 소비된다.

names = ["Ada", "Linus"]
scores = [95, 88]
pairs = zip(names, scores)

print(list(pairs))
print(list(pairs))
[('Ada', 95), ('Linus', 88)]
[]

두 번째 결과가 빈 이유는 같은 iterator를 이미 소비했기 때문이다. 다시 필요하면 원본 iterable로 zip을 다시 만들거나, 데이터가 작고 재사용이 꼭 필요할 때만 list로 보관한다. dict(zip(keys, values))처럼 튜플을 딕셔너리 항목으로 변환할 수도 있다.

길이가 다르면 목적에 맞는 정책을 고른다

기본 zip은 가장 짧은 iterable이 끝나면 조용히 멈추고 긴 쪽의 나머지를 버린다. 의도적인 잘라내기라면 사용할 수 있지만, 길이가 같아야 하는 데이터에서는 오류를 숨길 수 있다. Python 3.10 이상에서는 strict=True로 불일치를 ValueError로 바꾼다.

print(list(zip([1, 2], ["a"])))

try:
    list(zip([1, 2], ["a"], strict=True))
except ValueError as error:
    print(type(error).__name__)
[(1, 'a')]
ValueError

zip_longest는 누락을 보존해야 할 때 사용한다

itertools.zip_longest는 가장 긴 iterable이 끝날 때까지 순회하고 짧은 쪽의 누락을 fillvalue로 채운다. 데이터 손실 없이 불일치 자체를 후속 검증해야 할 때 사용한다.

from itertools import zip_longest

rows = zip_longest([1, 2], ["a"], fillvalue="MISSING")
print(list(rows))
[(1, 'a'), (2, 'MISSING')]

입력 중 하나가 무한할 수 있다면 zip_longest도 끝나지 않을 수 있다. islice나 takewhile 같은 명시적 제한을 함께 둔다.

순회 중 같은 컬렉션을 수정하지 않는다

리스트를 순회하면서 그 리스트의 항목을 제거하거나 삽입하면 다음 항목을 건너뛰거나 같은 항목을 다시 처리할 수 있다. 수정이 필요하면 복사본을 순회하거나 새 컬렉션을 만든다.

numbers = [1, -2, 3, -4]

for number in numbers.copy():
    if number < 0:
        numbers.remove(number)

print(numbers)
[1, 3]

필터링 결과를 새 리스트로 만드는 컴프리헨션은 9번 글에서, 데이터를 하나씩 지연 생성하는 generator는 11번 글에서 자세히 다룬다.

실전: 두 컬럼을 검증하며 결합하기

names = ["Ada", "Linus"]
scores = [88, None]

for row_number, (name, score) in enumerate(
    zip(names, scores, strict=True), start=1
):
    if score is None:
        print(row_number, name, "missing")
        continue
    print(row_number, name, score)
1 Ada 88
2 Linus missing

strict zip으로 두 컬럼의 행 수 계약을 검증하고, enumerate로 사람이 읽는 행 번호를 붙이며, guard continue로 누락 처리 뒤의 정상 경로를 단순하게 유지했다.

흔한 실수와 핵심 체크리스트

☐ iterable 항목 순회는 for, 조건 기반 반복은 while을 먼저 검토한다.

☐ 순번과 항목이 함께 필요하면 enumerate와 start를 사용한다.

☐ while의 상태 갱신과 최대 반복 또는 종료 경계를 코드에서 확인한다.

☐ loop else는 break 없이 정상 종료했을 때 실행된다고 설명할 수 있다.

☐ zip이 한 번 소비되는 lazy iterator이며 기본적으로 짧은 입력에서 멈춤을 안다.

☐ 같은 길이가 계약이면 strict=True, 누락 보존이면 zip_longest를 선택한다.

☐ 순회 중 원본 컬렉션을 수정하지 않고 복사본 순회나 새 결과 생성을 사용한다.

다음 글: 리스트·튜플·집합·딕셔너리 비교

반복문은 어떤 iterable도 순회할 수 있지만 자료구조마다 순서, 중복, 변경 가능성, 조회 방식이 다르다. 다음 글에서는 리스트·튜플·집합·딕셔너리를 같은 기준으로 비교해 데이터에 맞는 구조를 고른다.

참고 자료

댓글 0

댓글을 불러오는 중…