Skip to content
This page has been auto-translated and may contain errors.View in English

제어 흐름

지금까지 작성한 모든 프로그램은 항상 같은 방식으로 실행됩니다. 위에서 아래로, 한 줄씩. 간단한 스크립트에는 충분하지만, 실제 프로그램은 결정을 내리고 작업을 반복해야 합니다. 퀴즈는 답이 맞는지 확인해야 하고, 게임은 플레이어가 이기거나 질 때까지 계속 실행되어야 합니다. 이 장에서는 프로그램을 분기하고 반복하는 방법을 배웁니다.

제어 흐름은 프로그램이 따르는 경로를 결정합니다. 조건문(if/elif/else)은 분기 중 하나를 선택하고, 반복문(while, for)은 블록을 반복합니다. Python의 for는 인덱스를 세는 대신 항목을 하나씩 순회하기 때문에 많은 것들을 깔끔하게 반복할 수 있습니다. 이 장에서는 각 도구를 언제 사용할지, 그리고 각각의 함정이 무엇인지 다룹니다.

Python은 세 가지 제어 흐름 도구를 제공합니다: 분기를 위한 if/elif/else, 반복을 위한 whilefor. for 반복문은 iterable(리스트나 파일처럼 한 번에 하나씩 순회할 수 있는 것)을 순회하고, 항목이 다 떨어지면 자동으로 멈추므로 인덱스를 직접 관리할 일이 거의 없습니다. while 반복문은 조건이 참인 동안 계속 실행됩니다. breakcontinue는 자신을 감싼 가장 가까운 반복문에만 영향을 주며, 반복문의 else 절(나중에 다룸)은 break 없이 반복문이 종료될 때만 실행됩니다. 이 장의 나머지 부분은 올바른 도구를 선택하고 각각의 함정을 피하는 방법에 관한 것입니다.

비교

결정을 내리려면 먼저 값들을 비교해야 합니다. 비교 연산자는 True 또는 False를 반환합니다. 초반에 제대로 알아야 할 가장 중요한 것: =는 값을 할당하고, ==는 두 값이 같은지 확인합니다. 이 둘을 혼동하는 것은 가장 흔한 초보자 실수 중 하나입니다.

비교 연산자는 bool을 반환하고, Python은 연쇄 비교를 허용합니다: 0 < x < 100 < x and x < 10을 의미하며, 대부분의 다른 언어들은 이를 좌에서 우로 평가하다가 잘못 이해합니다. 문자열은 사전식 순서로 문자별로 비교되므로, 대문자가 소문자보다 먼저 정렬됩니다. 값을 비교할 때는 ==을 사용하고, is는 거의 사용하지 않습니다(Deep Dive에서 다룸).

비교는 bool을 반환하고, 실제 코드에서 중요한 두 가지가 있습니다. 첫째, Python은 연쇄 비교를 지원합니다: a < b < ca < b and b < c로 읽히고, 중간값 b는 한 번만 평가됩니다. 이는 b가 비싼 함수 호출이라면 알아두면 좋습니다. 둘째, ==은 두 값이 같은지 확인하는 반면, is동일성(두 이름이 메모리의 정확히 같은 객체를 가리키는지)을 확인합니다. 이 둘은 서로 바꿔 쓸 수 없습니다: 값을 비교할 때는 ==을 사용하고, None, True, False(정말로 유일한 객체가 필요한 경우)에는 is를 사용하세요. 문자열이나 숫자에 is를 사용하면 작은 입력에서는 통과하고 큰 입력에서 실패하는 버그가 생깁니다.

python
5 > 3     # True
5 < 3     # False
5 == 5    # True   (주의: 이중 등호; = 는 할당, == 는 비교)
5 != 3    # True   ("같지 않음")
5 >= 5    # True   ("크거나 같음")
5 <= 4    # False  ("작거나 같음")

===의 구분은 거의 모든 사람이 초반에 헷갈립니다. 할당(=)은 값을 저장하고, 비교(==)는 두 값이 같은지 확인합니다.

문자열도 비교할 수 있습니다. Python은 사전식으로 문자 단위로 문자열을 비교하며, 이에 대한 자세한 내용은 문자열 장에서 다룹니다:

python
"apple" == "apple"   # True
"apple" < "banana"   # True  (a가 b보다 먼저 옴)
"apple" == "Apple"   # False (대소문자 구분)
JunoComparisons=는 값을 저장하고, ==는 두 값이 같은지 확인합니다. 피곤할 때 여전히 확인하고 두 배로 확인하므로, 혼동한다면 좋은 회사에 있는 것입니다. 비교는 True 또는 False를 반환하고, 문자열에도 작동합니다.
JunoComparisons 비교는 bool을 반환합니다. 0 < x < 10처럼 연쇄된 형태는 0 < x and x < 10으로 읽히며, 대부분의 다른 언어들은 이를 잘못 이해합니다. 문자열 비교는 유니코드 순서로 이루어지므로 대소문자를 구분합니다.
JunoComparisons 값 비교에는 ==, 동일성 비교에는 is를 사용하고, isNone, True, False에만 사용하세요. 함정은 문자열이나 정수에 is를 사용하는 것입니다. 작은 입력에서는 통과하고 큰 입력에서 실패합니다. 연쇄 비교는 중간 항을 한 번만 평가하므로, 비싼 계산일 때 유용합니다.

조건 결합하기

and, or, not은 비교를 결합합니다. and는 양쪽이 모두 참이어야 합니다. or은 한쪽이라도 참이면 됩니다. not은 결과를 뒤집습니다. 이들은 "점수가 통과하고 사용자가 활성 상태"와 같은 실제 조건을 표현하게 해줍니다.

andor은 단락 평가(short-circuit)됩니다: and는 첫 번째 거짓 피연산자에서 멈추고, or은 첫 번째 참 피연산자에서 멈춥니다. 이들은 항상 True 또는 False가 아니라 실제 피연산자를 반환하므로, name or "guest"는 깔끔한 기본값 제공 방법입니다. not은 항상 순수한 bool을 반환합니다.

andorTrue 또는 False를 반환하지 않으며, 피연산자 중 하나를 반환합니다. and는 첫 번째 거짓 피연산자를 반환하거나, 모두 참이면 마지막 것을 반환합니다. or은 첫 번째 참 피연산자를 반환하거나, 모두 거짓이면 마지막 것을 반환합니다. 왼쪽이 결과를 결정하면 오른쪽은 평가되지 않으며, 이 단락 평가 보장(Python은 답이 정해지자마자 멈춤)은 다음과 같이 활용할 수 있습니다: user and user.nameuser가 존재할 때만 user.name을 읽으므로, 누락된 값에서 절대 오류가 발생하지 않습니다. 반대쪽은 실제 함정입니다: 오른쪽에 로깅이나 쓰기 같이 당신이 기대하는 부작용이 있다면, 왼쪽이 단락 평가되면 조용히 실행되지 않습니다.

python
age = 25
score = 88

age >= 18 and score >= 80    # True  (둘 다 참이어야 함)
age < 18 or score >= 80      # True  (하나라도 참이면 됨)
not age >= 18                # False (결과 뒤집기)

and는 양쪽을 요구합니다. or은 한쪽이라도 요구합니다. not은 반대로 뒤집습니다.

JunoCombining conditionsand는 양쪽이 참이길 원하고, or은 한쪽이라도 참이길 원하며, not은 답을 뒤집습니다. 이것이 "점수가 통과하고 사용자가 활성"과 같은 대부분의 실제 조건을 작성할 때 필요한 것입니다. Python은 답을 알 순간 체크를 멈춥니다.
JunoCombining conditionsand는 첫 번째 거짓 값에서 멈추고, or은 첫 번째 참 값에서 멈추며, 둘 다 항상 순수한 True/False가 아니라 실제 피연산자를 반환합니다. x and x.method()x가 비어있을 때 안전한 이유가 이 단락 평가 때문입니다.
JunoCombining conditionsand/or은 피연산자를 반환하며, 왼쪽이 결과를 결정하면 오른쪽은 평가되지 않습니다. user and user.name 같은 보호 구문으로 활용하세요. 다른 한편을 주의하세요: 오른쪽의 부작용은 단락 평가되면 조용히 실행되지 않습니다.

참과 거짓

Python의 모든 값은 불린 해석을 가지고 있으며, True 또는 False가 아니어도 그렇습니다. 빈 문자열, 0, 빈 리스트, None은 모두 조건에서 False처럼 동작합니다. 다른 모든 것은 True처럼 동작합니다. 이것은 if results: 리스트가 비어있지 않은지 확인하는 것을 if len(results) > 0:을 쓰지 않고도 확인할 수 있다는 의미입니다.

Python의 거짓 값은 False, 0, 0.0, "", [], (), {}, set(), None입니다. 다른 모든 것은 참이므로, 빈 컨테이너는 거짓이고 비어있지 않은 것은 참입니다. 이것이 if results:를 관용적인 빈 확인으로 만드는 이유입니다: if len(results) > 0: 대신 이것을 작성하세요.

조건에 객체를 넣으면, Python은 __bool__ 메서드(Python이 뒤에서 호출하는 이중 밑줄 메서드 중 하나인 dunder)를 호출해서 예/아니오 답변을 요청합니다. 타입이 __bool__을 정의하지 않으면, Python은 __len__으로 돌아가므로 길이가 0인 것은 거짓입니다. 표준 거짓 값은 False, 0, 0.0, "", 빈 컨테이너([], (), {}, set()), None입니다. 다른 모든 것은 참입니다. 주의할 함정: if results:는 빈 리스트와 None 모두 거짓이므로, 특별히 "비어있음"과 "아직 설정되지 않음"을 구분해야 한다면 참의 거짓 해석에 의존하는 대신 if results is None:을 테스트하세요.

python
# 이 모든 것은 조건에서 False처럼 동작합니다:
False, 0, 0.0, "", [], {}, (), None

# 다른 모든 것은 True처럼 동작합니다

이것은 if results:가 "리스트가 비어있지 않다면"의 자연스러운 방법이고, if name:은 문자열에 내용이 있는지 확인한다는 의미입니다.

JunoTruthy and falsy 빈 것들과 0은 조건에서 False로 간주됩니다: 0, "", [], None. 다른 모든 것은 True로 간주됩니다. 이것이 if results:가 "리스트에 뭔가 있다면"으로 그렇게 깔끔하게 읽히는 이유이며, len()이 필요 없습니다.
JunoTruthy and falsy 거짓은 False, 0, 빈 문자열, 빈 컨테이너, None입니다. 다른 모든 것은 참입니다. 빈 리스트는 거짓이고 찬 것은 참입니다. 이것이 if my_list:가 빈 확인으로 작동하는 이유이며, len()이 필요 없습니다.
JunoTruthy and falsy 조건은 __bool__을 호출한 후 __len__을 호출하므로, 자신의 클래스는 자신의 참 거짓을 선택할 수 있습니다. 함정: if results:는 빈 리스트와 None을 구분할 수 없습니다. 구분이 중요하면 is None을 사용하세요.

if / elif / else

if 문은 조건이 True일 때만 코드 블록을 실행합니다. elif는 첫 번째 조건이 거짓이면 더 많은 조건을 확인합니다. else는 어떤 조건도 일치하지 않은 모든 것을 처리합니다. Python은 중괄호가 아니라 들여쓰기를 사용해서 각 블록에 속하는 것을 정의합니다.

if/elif/else는 조건을 위에서 아래로 평가하고 일치하는 첫 번째 블록을 실행합니다. Python은 들여쓰기(관례상 4개 공백)를 사용해서 블록 범위를 정의합니다. 일관성 없는 들여쓰기는 SyntaxError입니다. 정확히 한 개의 분기가 실행됩니다: 조건이 일치하자마자, 뒤따르는 모든 elifelse는 건너뜁니다.

Python은 조건을 위에서 아래로 확인하고 정확히 한 개의 분기를 실행합니다: 조건이 참인 첫 번째, 또는 어떤 것도 일치하지 않으면 else. 각 조건은 지연 평가(Python은 위의 모든 조건이 거짓일 때만 이를 테스트)되므로, 순서는 논리의 일부입니다. 가장 저렴하거나 가장 가능성 높은 테스트를 먼저 두고, 겹치는 범위를 더 좁은 경계가 더 넓은 것보다 먼저 오도록 정렬하세요. 그렇지 않으면 나중 분기는 도달 불가능해집니다. 들여쓰기는 블록 구조이며, 탭과 공백을 섞으면 무음 의미 변화가 아니라 TabError입니다.

python
score = 87

if score >= 90:
    print("A 학점")
elif score >= 80:
    print("B 학점")
elif score >= 70:
    print("C 학점")
else:
    print("C 미만")

규칙:

  • if는 필수이며 항상 먼저 옵니다
  • elif("else if"의 약자)는 선택 사항이며 필요한 만큼 많이 가질 수 있습니다
  • else는 선택 사항이고 일치하지 않은 모든 것을 처리하며 마지막에 옵니다
  • Python은 들여쓰기(4개 공백)를 사용해서 각 블록에 속하는 것을 표시합니다. 중괄호는 없습니다

들여쓰기는 선택 사항이거나 미적인 것이 아닙니다. Python은 이를 사용해서 구조를 정의합니다. 일관성 없는 들여쓰기는 구문 오류입니다.

Junoif / elif / elseif는 조건이 참일 때 블록을 실행하고, elif는 더 많은 경우를 확인하며, else는 나머지를 처리합니다. 첫 번째 일치만 실행됩니다. 초반에 나를 당황하게 한 것: 들여쓰기는 Python에 각 블록이 어디서 시작하고 끝나는지를 알려주므로, 일관성 있게 유지하세요.
Junoif / elif / else 조건은 위에서 아래로 확인되고 첫 번째 일치만 실행되므로, 순서가 중요합니다: 더 좁은 범위를 더 넓은 범위 앞에 두세요. 들여쓰기(4개 공백)는 블록을 표시하고, 섞으면 SyntaxError입니다. 무음 버그가 아닙니다.
Junoif / elif / else 조건은 지연 평가되며, 위에서 아래로 평가되므로, 가장 저렴하거나 가장 가능성 높은 테스트를 먼저 두고 겹치는 경계를 좁은 것부터 넓은 것 순서로 두세요. 그렇지 않으면 분기가 죽습니다. 들여쓰기는 구조이고, 스타일이 아니며, 탭 대 공백은 무음 이동이 아니라 TabError입니다.

한 줄 조건

간단한 예/아니오 할당의 경우, Python은 삼항식이라는 컴팩트한 한 줄 형식을 가지고 있습니다: 참이면_값 if 조건 else 거짓이면_값. 논리가 작고 문장처럼 읽힐 때만 사용하세요.

조건식(삼항 연산자)은 조건에 따라 두 값 중 하나로 평가됩니다. 이것은 문이 아니라 식이므로, 값이 필요한 곳 어디든 나타날 수 있습니다: f-문자열 내부에서, 함수 인자로서, 할당에서. 간단한 예/아니오 경우에 사용하세요. elif를 포함하는 것은 전체 버전을 작성하세요.

조건식 x if 조건 else y는 조건을 평가하고 다른 쪽을 실행하지 않고 한 쪽을 반환하므로, 한 분기가 오류를 일으킬 곳에 안전하게 사용할 수 있습니다. 이것이 식이므로, 값이 가는 곳 어디든 들어갑니다: f-문자열, 함수 인자, 기본값. 규율은 언제 멈춰야 하는지 아는 것입니다. elif를 가질 수 없고 이를 연결하면(a if p else b if q else c) 대체하는 블록보다 더 나쁘게 읽힙니다. 따라서 두 개 이상의 결정이 생기면 전체 if/elif/else를 사용하세요. 나중에 이를 유지할 사람이 감사할 것입니다.

python
label = "통과" if score >= 50 else "불통과"

이것은 삼항식이며, 문장처럼 읽힙니다. 논리가 작을 때 사용하세요. elif를 포함하는 어떤 것이든 전체 버전을 작성하세요.

JunoOne-line conditions참이면_값 if 조건 else 거짓이면_값은 한 줄에서 두 값 중 하나를 선택합니다. "통과" if score >= 50 else "불통과"처럼 깔끔한 작은 선택을 위해 훌륭합니다. 논리가 그 이상으로 커지는 순간, 일반 if 블록으로 돌아가면 더 잘 읽힐 것입니다.
JunoOne-line conditions 삼항식은 식이므로 값이 가는 곳 어디든 맞습니다: f-문자열, 인자, 할당. 간단한 두 방향 선택에 사용하세요. elif를 가진 어떤 것이든 전체 블록에 속합니다.
JunoOne-line conditionsx if cond else y는 선택한 쪽만 실행하므로 한 분기가 발생할 곳에서 안전하며, 어떤 식 슬롯이든 떨어집니다. 이를 연결하지 마세요: 연결된 삼항식은 대체하는 블록보다 더 나쁘게 읽히므로, 한 결정을 넘어서면 이를 작성하세요.

while 반복문

while 반복문은 조건이 True인 동안 블록을 반복합니다. 반복문이 몇 번 실행되어야 하는지 미리 모를 때 사용하세요. 예를 들어 유효한 입력을 기다리거나 작업이 성공할 때까지 재시도합니다.

while은 각 반복 전에 조건을 평가하고 조건이 참일 때만 블록을 실행합니다. 반복문의 종료 조건이 반복문 내부에서 변하는 것에 따라 달라질 때 사용하세요. 반복 횟수가 알려져 있거나 컬렉션을 순회 중이면 for가 보통 더 깔끔합니다.

while은 매 패스마다 조건을 다시 테스트하고, 본문이 그 조건을 바꾸므로, 함정은 구조적입니다: 종료로 이동하는 것(카운터 감소, 큐 소비)을 잊으면 절대 끝나지 않는 반복문을 얻고 프로세스가 행을 겁니다. 종료가 본문의 중간이나 끝에서 결정되어야 하는 관용적 형태는 내부 break를 가진 while True이며, 조건을 헤더에 강제하는 대신 조건이 실제로 발생하는 곳으로 유지합니다. 컬렉션을 순회하거나 알려진 횟수만큼 실행하려면, for 반복문이 더 깔끔하고 카운터에서 off-by-one의 기회를 제거합니다.

python
lives = 3

while lives > 0:
    print(f"남은 생명: {lives}")
    lives -= 1

print("게임 종료")

while은 반복문이 몇 번 실행될지 미리 모를 때 가장 좋습니다. 미리 알거나 컬렉션을 순회 중이면 for가 더 깔끔합니다.

Junowhile loopswhile은 조건이 참인 동안 블록을 계속 실행합니다. 유효한 입력을 기다릴 때처럼 몇 라운드가 필요한지 모를 때 사용하세요. 반복문 안에서 뭔가가 종료로 이동하는지 확인하세요. 그렇지 않으면 영원히 실행되고, 이 실수는 한 번 이상 저질렀습니다.
Junowhile loopswhile은 각 패스마다 조건을 확인하고 본문이 이를 바꿉니다. 종료가 반복문 내부에서 발생하는 것에 달려 있을 때 사용하세요. 세고 있거나 컬렉션을 순회 중이면, for가 더 깔끔한 선택입니다.
Junowhile loops 본문은 종료로 이동해야 하므로, 그것이 첫 번째로 확인할 것입니다. 종료가 중간 본문에 내려앉을 때 깔끔한 형태는 내부 break를 가진 while True입니다. 알려진 개수나 컬렉션의 경우, for를 선호하고 off-by-one 위험을 건너뜁니다.

break와 continue

break는 남은 반복이 얼마나 있든 반복문을 즉시 빠져나갑니다. continue는 현재 반복의 나머지를 건너뛰고 조건 확인으로 돌아갑니다. 둘 다 자신을 감싼 가장 가까운 반복문에만 영향을 줍니다.

break는 가장 가까운 반복문을 종료하고, 그 다음 문으로 제어를 이전합니다. continue는 현재 반복문 본문의 나머지를 건너뛰고 조건 확인으로 다시 시작합니다(for 반복문에서 다음 반복). 둘 다 가장 가까운 반복문에만 영향을 줍니다.

break는 반복문을 즉시 떠나고 else 절을 가지면 이를 건너뜁니다. continue는 반복문 헤더로 다시 뛰어갑니다. 둘 다 가장 가까운 반복문으로 바인딩되고, Python은 라벨이 붙은 break가 없으므로, 안쪽 반복문 내부에서 바깥쪽에 도달할 수 없습니다. 한 번에 여러 레벨을 탈출해야 하면, 깔끔한 이동은 중첩된 반복문을 함수로 들어올리고 return하는 것입니다. 이는 누락된 라벨을 피하기 위해 두 반복문을 통해 플래그 변수를 스레드하는 것보다 더 잘 읽힙니다. 플래그가 작동하지만, 피하기 위해 추가하는 모든 추가 if found:는 나중 편집이 이를 잘못 얻을 수 있는 장소입니다.

break는 반복문을 즉시 빠져나갑니다:

python
target = 5
num = 0

while True:
    num += 1
    if num == target:
        print(f"{target}를 찾음")
        break   # 반복문 멈추기

while True:break는 종료 조건이 복잡하거나 반복문 본문의 끝에서 발생해야 할 때 유효하고 일반적인 패턴입니다.

continue는 현재 반복의 나머지를 건너뛰고 조건 확인으로 돌아갑니다:

python
num = 0

while num < 10:
    num += 1
    if num % 2 == 0:
        continue    # 짝수 건너뛰기
    print(num)      # 홀수만 출력: 1, 3, 5, 7, 9
Junobreak and continuebreak는 반복문에서 바로 뛰어나가고, continue는 이 라운드의 나머지를 건너뛰고 조건 확인으로 돌아갑니다. 둘 다 자신들이 직접 안에 있는 반복문만 건드립니다. while True:는 처음에는 이상해 보이지만, 뭔가 발생할 때까지 반복하는 일반적인 방법입니다.
Junobreak and continuebreak는 가장 가까운 반복문을 떠나고, continue는 조건에서 다시 시작합니다. 어느 것도 바깥쪽 반복문에 도달하지 못합니다. 중첩된 반복문 밖으로 나가려면, 플래그를 설정하거나 안쪽 반복문을 함수로 가져가 return하세요.
Junobreak and continue 둘 다 가장 가까운 반복문으로 바인딩되고, 라벨이 붙은 break가 없으므로, 여러 레벨 탈출은 플래그 또는 더 깔끔하게 return이 있는 함수를 의미합니다. break는 또한 반복문의 else를 건너뛰며, 이것은 정확히 검색 패턴이 의존하는 것입니다.

for 반복문

for 반복문은 수열을 한 번에 하나씩 항목으로 이동합니다: 리스트, 문자열, 숫자 범위. for 뒤에 이름을 지은 변수는 각 항목을 차례로 받습니다. 카운터를 관리하거나 길이를 직접 확인할 필요가 없습니다.

for는 주어진 것에 한 번에 하나씩 항목을 요청하고 항목이 떨어지면 멈추며, 인덱스 위치를 세지 않습니다. 이것이 리스트보다 훨씬 더 많은 것에서 작동하는 이유입니다: 문자열, 사전, 범위, 파일 객체는 모두 같은 방식으로 반복되며, 길이 확인이나 카운터가 필요 없습니다. 컬렉션을 순회할 때마다 while 대신 for를 사용하세요.

for는 인덱스를 세지 않으며, 주어진 것에 반복자(한 번에 하나의 항목을 건네주고 멈춘 위치를 기억하는 객체)를 요청하고 떨어질 때까지 항목을 끌어옵니다. 실제적 보상은 for가 리스트보다 훨씬 더 많은 것에서 작동한다는 것입니다: 문자열, 사전, 범위, 파일 객체, lazy 소스인 제너레이터(메모리에 절대 모두 유지되지 않고 필요할 때 계산되는 수열) 모두 같은 방식으로 반복됩니다. 마지막 그룹이 한 가지 함정이 사는 곳입니다: lazy 반복자는 일회용이므로, for 반복문이 제너레이터나 파일을 비우면, 다시 반복하면 아무것도 주지 않습니다. 항목이 필요하면 두 번, 먼저 리스트로 구체화하거나 소스를 다시 열으세요.

python
players = ["민지", "준호", "지은"]

for player in players:
    print(f"안녕하세요, {player}!")

for 반복문은 문자열(문자 단위로 반복)과 다른 수열 타입에도 작동합니다.

Junofor loopsfor item in sequence는 차례로 각 항목을 순회하며, 카운터나 길이를 직접 건드리지 않습니다. 리스트, 문자열, 범위, 많은 것에서 작동합니다. 다른 언어에서 오면, 인덱스를 관리하지 않는 것이 처음에는 낄낄거렸고, 그 다음에는 선물 같았습니다.
Junofor loopsfor는 항목을 하나씩 끌어내 떨어질 때까지, 따라서 인덱스 수열에 묶이지 않습니다. 리스트, 문자열, 사전, 범위, 파일: 모두 같은 반복문. 인덱스 부기 없고, 길이 확인 없음.
Junofor loopsfor는 어떤 반복자도 구동하므로, 리스트, 파일, 제너레이터 모두 같이 반복됩니다. 함정은 lazy 것입니다: 제너레이터나 파일 반복자는 일회용이며, 한 패스 후 소비됩니다. 항목이 필요하면 두 번, 먼저 리스트로 당기거나 소스를 다시 열으세요.

range()

range()는 반복할 숫자 수열을 생성합니다. range(5)0, 1, 2, 3, 4를 줍니다. 시작, 끝, 스텝 크기를 제어할 수 있습니다. 반복문이 특정 횟수 실행되기를 원할 때 사용하세요.

range(start, stop, step)start에서 (포함하지 않고) stop까지의 정수를 step씩 생성합니다. 이것은 lazy 수열입니다: 리스트를 생성하지 않으며, 필요할 때 숫자를 생성합니다. 이것은 range(10_000_000)을 메모리 효율적으로 만듭니다. 모든 세 형태는 역순 세기를 위해 음수 인자를 허용합니다.

range는 절대 숫자 리스트를 빌드하지 않으며, 시작, 멈춤, 단계만 저장하고 가면서 각 값을 해결합니다. 이것은 range(10_000_000)range(10)과 같은 메모리 비용을 하게 만들므로, 반복하기만 원하면 range를 직접 반복하세요. list()로 감싸서 천만 저장된 정수 비용을 지불하지 마세요. 여전히 개수할 때 수열처럼 동작합니다: 인덱스할 수 있고, 슬라이스할 수 있고, len()을 요청할 수 있고, in으로 멤버십을 테스트할 수 있으며, 모두 저렴하게. list(range(n))을 호출하는 것은 실제로 가져올 실제 재사용 가능 리스트가 필요할 때만 가치가 있습니다.

python
for i in range(5):
    print(i)    # 0, 1, 2, 3, 4

range()에는 세 가지 형태가 있습니다:

호출생성되는 것
range(5)0, 1, 2, 3, 4
range(2, 6)2, 3, 4, 5
range(0, 10, 2)0, 2, 4, 6, 8 (2씩 증가)
range(5, 0, -1)5, 4, 3, 2, 1 (역순)

range()는 리스트를 생성하지 않습니다. 한 번에 하나씩 숫자를 생성하며, 매우 큰 범위에서도 효율적입니다.

Junorange()range(5)0, 1, 2, 3, 4를 주며, 시작, 멈춤, 단계를 설정할 수 있습니다. 반복문이 설정된 횟수를 실행하기를 원할 때 사용하세요. 리스트를 빌드하지 않으며, 라운드마다 하나씩 숫자를 건네줍니다.
Junorange()range(start, stop, step)start에서 stop 미만까지 셉니다. 이것은 lazy 수열이므로, 리스트를 빌드하는 대신 필요할 때 숫자를 만드므로, range(10_000_000)은 거의 아무것도 비용이 들지 않습니다. 음수 단계는 역순입니다.
Junorange()range는 시작, 멈춤, 단계만 저장하므로, 얼마나 큰지에 관계없이 평평한 메모리입니다. list(range(n))이 아니라 직접 반복하세요. 정말로 재사용 가능한 리스트가 필요하지 않으면. 여전히 인덱스하고, 슬라이스하고, inlen()을 저렴하게 지원합니다.

enumerate()

enumerate()는 반복 중에 인덱스와 값을 모두 제공하므로, 카운터를 따로 추적할 필요가 없습니다. i, player 부분은 각 반복에서 자동으로 값의 쌍을 받습니다.

enumerate(iterable, start=0)은 모든 반복자를 감싸고 (index, value) 튜플을 낳습니다. start 매개변수는 카운터를 오프셋하지만 기본 인덱스를 바꾸지 않습니다. 카운터 변수 관리보다 enumerate()를 선호하세요. 더 깔끔하고 오류에 덜 취약합니다.

enumerate는 모든 반복 가능을 감싸고 (count, value) 쌍을 건네주며, for 헤더는 언팩을 통해 두 이름으로 이를 분할합니다(Python은 한 단계에서 튜플의 각 부분을 자신의 이름에 할당). 이것은 lazy이고 카운터 이상을 추가하지 않으므로, 같은 비용이 맨 반복문입니다. 항상 손으로 관리하는 index = 0; index += 1 대신 사용하는 이유는 신뢰성입니다: 수동 버전은 증가를 잊거나 잘못된 장소에서 범프하는 버그를 초대하고, enumerate는 그 전체 실수 클래스를 제거합니다. 사람들을 위해 출력을 번호 매기고 있을 때 start=1을 전달하세요. 그들은 0이 아니라 1부터 셉니다.

python
players = ["민지", "준호", "지은"]

for i, player in enumerate(players):
    print(f"{i + 1}. {player}")
# 1. 민지
# 2. 준호
# 3. 지은

i, player 구문은 언팩이라고 불립니다. Python은 (index, value) 쌍을 자동으로 두 이름으로 분할합니다.

기본적으로 enumerate()는 0부터 시작합니다. 이를 바꾸려면 시작 값을 전달하세요:

python
for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")    # 1부터 시작
Junoenumerate()enumerate()는 위치와 값을 함께 건네주므로, 동기화된 별도 카운터가 없습니다. i, player 부분은 한 번에 둘 다를 캡처합니다. 누군가 읽기 위해 리스트를 번호 매기고 있을 때 start=1을 전달하세요.
Junoenumerate()enumerate(iterable, start=0)for 헤더에서 바로 언팩되는 (index, value) 쌍을 낳습니다. 카운터 변수를 선호하세요. 더 깔끔하고 증가를 잊을 수 없습니다. start은 표시된 숫자만 이동하고 실제 인덱스는 아닙니다.
Junoenumerate() Lazy, 카운터 이상으로 무료, 그리고 수동 인덱스 초대하는 "증가 잊음" 버그 전체를 삭제합니다. 기본적으로 사용하세요. 각 항목은 튜플이므로 for i, v 언팩 작동. 인간 대면 번호 매기기 위해 start=1.

중첩된 반복문

한 반복문을 다른 반복문 안에 넣을 수 있습니다. 바깥쪽 반복문의 각 단일 반복에 대해 안쪽 반복문이 완전히 실행됩니다. 이것은 그리드, 조합, 또는 두 단계 구조를 가진 모든 데이터를 처리하는 방법입니다.

중첩된 반복문은 바깥쪽 길이 m과 안쪽 길이 n에 대해 O(m × n) 반복 개수를 가집니다. 중첩된 반복문 내부의 breakcontinue는 가장 안쪽 반복문에만 영향을 줍니다. 여러 레벨에서 벗어나려면, 플래그 변수를 사용하거나 함수로 재구성하세요.

중첩된 반복문은 곱해집니다: 길이 m의 바깥쪽 반복문이 길이 n의 안쪽 반복문을 감싸면, 본문이 m배 n배 실행되고, 비용이 빠르게 올라가며, 큰 입력에 대한 반복문 쌍은 느린 스크립트 뒤의 일반적인 범인입니다. 세 번째 레벨에 가기 전에, 데이터 구조가 실제 고정인지 스스로에게 물어보세요(사전 조회는 안쪽 검색 반복문을 축소할 수 있음). 두 수열의 모든 조합이 필요할 때(데카르트 곱, 서로 모든 쌍), 표준 라이브러리의 itertools.product는 중첩보다 더 잘 읽힙니다. 그리고 기억하세요 break는 안쪽 반복문만 빠져나갑니다: 둘 다 떠나려면, 이를 함수로 들어올리고 return하세요.

python
rows = [1, 2, 3]
cols = ["A", "B"]

for row in rows:
    for col in cols:
        print(f"{col}{row}", end=" ")
    print()   # 각 행 후 새로운 줄
# A1 B1
# A2 B2
# A3 B3

중첩된 반복문 내부의 breakcontinue는 가장 안쪽 반복문에만 영향을 줍니다.

JunoNested loops 반복문 안의 반복문은 바깥쪽의 모든 단일 라운드에 대해 전체 안쪽 반복문을 실행합니다. 이것이 그리드와 조합을 처리하는 방법입니다. breakcontinue는 내부에 있는 반복문에만 작동합니다.
JunoNested loops 안쪽 반복문은 바깥쪽 단계마다 완전히 실행되므로, 개수는 m배 n입니다. 큰 입력을 주의하세요. breakcontinue는 가장 안쪽 반복문에만 도달합니다. 둘 다 탈출하려면, 플래그를 사용하거나 return을 가진 함수로 끌어올리세요.
JunoNested loops 비용이 곱해지므로 큰 입력에 대한 중첩 반복문이 일반적인 느린 지점입니다. 때때로 사전 조회는 안쪽 것을 완전히 대체합니다. 모든 쌍을 위해, itertools.product는 중첩을 이깁니다. break는 안쪽 반복문만 치므로, 함수와 return을 가진 깔끔한 방식은 둘 다 벗어나는 것입니다.

반복문-else

Python 반복문은 break에 도달하지 않고 반복문이 완료된 경우에만 실행되는 else 절을 가질 수 있습니다. 흔하게 사용되지는 않지만, "리스트를 검색하고 아무것도 찾지 못하면 이것을 하세요"를 작성하는 가장 깔끔한 방법입니다.

for 또는 while 반복문의 else은 반복문이 정상적으로 완료되면(반복 가능을 소비하거나 조건이 거짓이 되면) break 없이 실행됩니다. 이것은 별도의 찾은 플래그 변수가 필요 없이 "검색하고 찾지 못하면 보고" 패턴입니다.

반복문 elsebreak 없이 반복문이 완료될 때 실행되며, 이것은 대부분의 사람들이 읽는 것과 반대입니다(반복문이 실행되지 않았다는 의미가 아닙니다). 정확히 이 명칭이 독자들을 혼동하는 이유이므로, 생산 규칙은 한 가지에만 사용하는 것입니다: 검색 및 찾지 못한 패턴, 여기서 break는 히트를 표시하고 else는 미스를 보고합니다. 이것은 반복문을 통해 수행할 found = False 플래그를 대체합니다. 다른 곳 어디든, 영리함은 혼동된 검토자보다 더 많은 비용을 들이므로, else에 주석을 다는 것은 줄을 지나갑니다.

python
target = "수호"
names = ["민지", "준호", "지은"]

for name in names:
    if name == target:
        print(f"{target}을 찾음")
        break
else:
    print(f"{target}은 리스트에 없음")   # break가 실행되지 않아서 실행됨

break가 실행되면, else는 건너뜁니다. 반복문이 수열을 소비하면, else는 실행됩니다. 이것은 틈새 패턴이지만 플래그 변수보다 깔끔합니다.

JunoLoop-else 반복문은 break 없이 끝날 때만 실행되는 else를 가질 수 있습니다. 자주 사용되지 않지만, "이 리스트를 검색하고 아무것도 일치하지 않으면 이것을 하세요"라고 말할 가장 깔끔한 방법입니다. 이름이 좀 오해의 소지가 있으므로, 짧은 주석이 도움이 됩니다.
JunoLoop-else 반복문 else는 반복문이 정상적으로 끝날 때 발생하고, break 없습니다. 이것은 깔끔한 "검색하고 찾지 못하면 보고" 패턴이며, 찾은 플래그가 필요 없습니다. 이름이 사람들을 헷갈리게 하므로 한 가지 사용만 하세요.
JunoLoop-elsebreak 없이 반복문이 끝날 때 실행되며, 이것은 단어가 읽는 것의 역입니다. 검색 및 찾지 못한 경우에만 유지하세요, 여기서 found = False 플래그를 대체합니다. 그리고 주석을 달아주세요. 다른 곳 어디든 비용이 더 듭니다.

정렬

sorted()는 새 정렬된 리스트를 반환하고 원본은 바꾸지 않습니다. .sort()는 제자리에서 리스트를 정렬하고 None을 반환합니다. key= 인자를 사용하면 원시 값이 아닌 다른 항목으로 정렬할 수 있습니다. 예를 들어, 이름을 대소문자를 구분하지 않고 정렬하거나 플레이어 튜플을 점수로 정렬합니다.

sorted()는 안전한 기본값입니다: 절대 원본을 수정하지 않습니다. .sort()는 제자리에서 수정하고 None을 반환합니다. 둘 다 내림차순의 경우 reverse=True를 허용합니다. key= 인자는 비교 전에 각 요소에 적용되는 함수입니다. 이것은 정렬 기준을 데이터와 분리합니다.

Python의 정렬은 안정적이므로, 비교에서 같은 항목은 원래 순서를 유지하며, 이것이 단계적으로 정렬할 수 있는 것입니다: 먼저 이름으로 정렬하고, 그 다음 점수로 정렬하면, 같은 점수를 가진 플레이어는 이름 순서를 유지합니다. key= 함수는 비교당 한 번이 아니라 요소당 한 번 실행되므로, 비싼 키는 n배로 계산되지 n log n배로 계산되고, 자신 캐싱이 거의 필요하지 않습니다. .sort()은 목적에서 None을 반환하며, 이것은 scores = scores.sort()를 작성하고 조용히 리스트를 지우는 것을 멈추기 위함이므로, 제자리 호출은 자신의 줄에 서있어야 합니다. 기본적으로 sorted()를 사용하고 실제로 원본을 변경하고 싶을 때 .sort()를 유지하세요.

python
scores = [87, 42, 96, 55, 71]

ranked = sorted(scores)           # [42, 55, 71, 87, 96] (새 리스트)
scores.sort()                     # 원본 리스트를 정렬하며, None을 반환
scores.sort(reverse=True)         # [96, 87, 71, 55, 42]

둘 다 key= 인자를 허용합니다: 비교 전에 각 항목에 적용되는 함수:

python
names = ["지은", "민지", "준호"]
sorted(names, key=str.lower)       # 대소문자 구분 없이 정렬

players = [("민지", 87), ("준호", 96), ("지은", 55)]
sorted(players, key=lambda p: p[1])   # 점수로 정렬

lambda p: p[1]은 한 줄 함수입니다: 플레이어 튜플을 받고 점수를 반환합니다. 람다는 람다와 컴프리헨션 장에서 충분히 다룹니다.

간단한 경우에는 sorted()를 사용하세요. 제자리 수정을 원하는 리스트의 경우 .sort()를 사용하세요.

JunoSortingsorted()는 새 정렬된 리스트를 건네주고 원본은 혼자 두며, .sort()는 리스트 자체를 다시 정렬하고 None을 반환합니다. 원시 값 이외의 다른 항목으로 정렬하려면 key=을 전달합니다. 확실하지 않으면 sorted()를 사용하세요. 안전합니다.
JunoSortingsorted()는 절대 원본을 건드리지 않으며, .sort()는 제자리에서 변경하고 None을 반환합니다. 둘 다 reverse=True와 비교 전에 각 요소에 적용되는 key= 함수를 받습니다. 기본적으로 sorted()를 사용하세요. 리스트를 바꾸려고 할 때만 .sort()를 사용하세요.
JunoSorting 정렬은 안정적이므로, 단계적으로 정렬하고 동점은 순서를 유지합니다. key=는 비교당이 아니라 요소당 한 번 실행되므로 미리 캐싱하지 마세요. 그리고 .sort()는 목적에서 None을 반환합니다: x = x.sort()는 리스트를 지우므로, 제자리 호출을 자신의 줄에 유지하세요.

실제로

점수를 반복하고, 합계를 누적하고, 통과 학점을 세고, 요약을 출력합니다:

python
raw_scores = [87, 42, 96, 55, 71, 63]

total = 0
passing = 0

for score in raw_scores:
    total += score
    if score >= 60:
        passing += 1

average = total / len(raw_scores)
print(f"평균: {average:.1f}")
print(f"통과: {passing}/{len(raw_scores)}")
print(f"최고 점수: {sorted(raw_scores, reverse=True)[0]}")

정렬된 순서로 파일 리스트를 처리하고, 너무 큰 것들을 건너뛰고, 몇 개를 건너뛰었는지 보고합니다:

python
files = [
    {"name": "report_jan.csv", "size_mb": 12},
    {"name": "report_feb.csv", "size_mb": 850},
    {"name": "report_mar.csv", "size_mb": 7},
]

MAX_SIZE = 100
skipped = 0

for f in sorted(files, key=lambda x: x["name"]):
    if f["size_mb"] > MAX_SIZE:
        print(f"{f['name']} 건너뛰는 중 ({f['size_mb']} MB, 너무 큼)")
        skipped += 1
    else:
        print(f"{f['name']} 처리 중...")

print(f"\n완료. {skipped}개 파일 건너뜀.")

요청 로그에서 오류를 검색하고, 성공 또는 시도 제한이 도달될 때까지 종료되는 재시도 반복문을 사용합니다:

python
requests = [
    {"method": "GET", "path": "/users", "status": 200},
    {"method": "POST", "path": "/users", "status": 201},
    {"method": "GET", "path": "/broken", "status": 500},
]

errors = []

for req in requests:
    if req["status"] >= 400:
        errors.append(req)

if errors:
    print(f"요청 로그에서 {len(errors)}개 오류:")
    for err in errors:
        print(f"  {err['method']} {err['path']} -> {err['status']}")
else:
    print("모든 요청이 성공함")

attempts = 0
max_retries = 3
success = False

while attempts < max_retries and not success:
    attempts += 1
    print(f"시도 {attempts}...")
    success = attempts >= 2   # 두 번째 시도에서 성공 시뮬레이션

print("연결됨" if success else "모든 재시도 후 실패")