Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 3136 – 레이블이 지정된 break 및 continue

Author:
Matt Chisholm <matt-python at theory.org>
Status:
Rejected
Type:
Standards Track
Created:
30-Jun-2007
Python-Version:
3.1
Post-History:


Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

거부 통지

이 PEP는 거부되었습니다. 다음을 참조하십시오: https://mail.python.org/pipermail/python-3000/2007-July/008663.html.

초록

이 PEP는 Python의 breakcontinue 문에서 레이블을 지원할 것을 제안합니다. 이는 다른 언어의 레이블이 지정된 breakcontinue에서 영감을 받았으며, 이러한 기능이 드물지만 지속적으로 필요했던 작성자의 경험에서도 영감을 받았습니다.

서론

break 문은 프로그래머가 루프를 조기에 종료할 수 있도록 하며, continue 문은 프로그래머가 루프의 다음 반복으로 조기에 이동할 수 있도록 합니다. 현재 Python에서는 breakcontinue를 가장 안쪽에 있는 바깥쪽 루프에만 적용할 수 있습니다.

breakcontinue 문에 레이블 지원을 추가하는 것은 breakcontinue 문의 기존 동작을 논리적으로 확장하는 것입니다. 레이블이 지정된 breakcontinue는 중첩 루프를 사용하는 복잡한 코드의 가독성과 유연성을 향상할 수 있습니다.

간결하게 하기 위해 이 PEP의 예제와 논의에서는 일반적으로 break 문을 언급합니다. 그러나 모든 예제와 동기는 레이블이 지정된 continue에도 동일하게 적용됩니다.

동기

프로그래머가 바깥쪽의 외부 루프를 다음 반복으로 진행하거나 여러 루프를 한 번에 종료하려는 경우, 그에게는 그다지 우아하지 않은 몇 가지 선택지가 있습니다.

다음은 Python에서 레이블이 지정된 break를 모방하는 일반적인 방법입니다(이 예제와 이후의 예제에서 ...는 그 사이에 있는 임의 개수의 코드 행을 나타냅니다).:

for a in a_list:
    time_to_break_out_of_a = False
    ...
    for b in b_list:
        ...
        if condition_one(a, b):
            break
        ...
        if condition_two(a, b):
            time_to_break_out_of_a = True
            break
        ...
    if time_to_break_out_of_a:
        break
    ...

이를 위해서는 다섯 줄과 추가 변수 time_to_break_out_of_a가 필요하며, 바깥쪽 (a) 루프를 언제 빠져나와야 하는지 추적해야 합니다. 또한 이 다섯 줄이 많은 코드 행에 걸쳐 분산되어 있어 제어 흐름을 이해하기 어렵게 만듭니다.

이 기법은 오류가 발생하기도 쉽습니다. 이 코드를 수정하는 프로그래머는 실수로 안쪽 (b) 루프의 끝난 뒤, time_to_break_out_of_a에 대한 검사 앞에 새로운 코드를 넣을 수 있습니다. 이는 바깥쪽 루프에서 빠져나오면서 건너뛰었어야 하는 코드가 잘못 실행된다는 의미입니다.

이를 예외를 사용해 작성할 수도 있습니다. 프로그래머는 특수 예외를 선언하고, 안쪽 루프를 try 블록으로 감싼 다음, 예외를 발견하면 이를 잡아 루프를 중단합니다.:

class BreakOutOfALoop(Exception): pass

for a in a_list:
    ...
    try:
        for b in b_list:
            ...
            if condition_one(a, b):
                break
            ...
            if condition_two(a, b):
                raise BreakOutOfALoop
            ...
    except BreakOutOfALoop:
        break
    ...

그러나 이 방법 역시 다섯 줄과 새로운 단일 목적 예외 클래스(새 변수 대신)가 필요하며, 기본적인 제어 흐름을 여러 코드 행에 걸쳐 분산시킵니다. 또한 안쪽 루프에서는 break로 빠져나오고 다른 루프에서는 예외로 빠져나오므로 우아하지 않습니다. [1]

condition_two()를 계산하는 비용이 저렴하다고 가정하면, 다음 전략이 가장 우아한 해결책일 수 있습니다.:

for a in a_list:
    ...
    for b in b_list:
        ...
        if condition_one(a, b):
            break
        ...
        if condition_two(a, b):
            break
        ...
    if condition_two(a, b)
        break
    ...

두 번 중단하는 것 역시 여전히 우아하지 않습니다. 이 구현은 안쪽 (b) 루프가 b를 바깥쪽 for 루프로 누출한다는 사실에도 의존합니다. 이는 명시적으로 지원되는 동작이지만 초보자에게는 놀라울 뿐 아니라, 제 생각에는 직관에 어긋나고 좋지 않은 관행입니다.

프로그래머는 조건 2에서 두 번 모두 중단하도록 작성하고 두 번째 중단 앞에는 코드를 삽입하지 않아야 한다는 점도 여전히 기억해야 합니다. 두 루프 모두에서 condition_two()일 때 빠져나오는 하나의 개념적 동작에 네 줄의 코드가 필요하고, 들여쓰기 수준도 두 단계이며, 안쪽 (b) 루프의 끝에서 여러 코드 행이 그 사이에 들어갈 수도 있습니다.

다른 언어들

이제 다른 프로그래밍 언어에 대해 어떤 반감을 가지고 있든 잠시 제쳐 두고, 레이블이 지정된 breakcontinue의 구문을 살펴보십시오. Perl에서는:

ALOOP: foreach $a (@a_array){
    ...
    BLOOP: foreach $b (@b_array){
        ...
        if (condition_one($a,$b)){
            last BLOOP; # same as plain old last;
        }
        ...
        if (condition_two($a,$b)){
            last ALOOP;
        }
        ...
    }
    ...
}

(참고: Perl에서는 break대신 last를 사용합니다. BLOOP 레이블은 생략할 수 있으며, lastcontinue는 기본적으로 가장 안쪽 루프에 적용됩니다.)

PHP에서는 레이블 대신 탈출할 루프 수를 나타내는 숫자를 사용합니다.:

foreach ($a_array as $a){
    ....
    foreach ($b_array as $b){
        ....
        if (condition_one($a, $b)){
            break 1;  # same as plain old break
        }
        ....
        if (condition_two($a, $b)){
            break 2;
        }
        ....
    }
    ...
}

C/C++, Java 및 Ruby에는 모두 이와 유사한 구문이 있습니다.

바깥쪽 (a) 루프에서 탈출할 시점에 관한 제어 흐름은 탈출 조건이 충족될 때 실행되는 break문에 완전히 캡슐화됩니다. break 문의 깊이는 중요하지 않습니다. 제어 흐름이 분산되지 않습니다. 추가 변수나 예외가 필요하지 않으며, 제어 조건을 다시 확인하거나 저장할 필요도 없습니다. 안쪽 (b) 루프의 끝난 뒤와 바깥쪽 (a) 루프 안에서 탈출 조건을 다시 확인하기 전 사이에 코드가 실수로 삽입될 위험도 없습니다. 이것이 레이블이 지정된 breakcontinue가 Python에 제공할 이점입니다.

이 PEP가 아닌 것

이 PEP는 Python에 GOTO를 추가하자는 제안이 아닙니다. GOTO를 사용하면 프로그래머가 임의의 코드 블록이나 줄로 이동할 수 있으며, 일반적으로 제어 흐름을 따라가기 더 어려워집니다. breakcontinue(레이블 지원 여부와 관계없이)는 GOTO의 한 유형으로 간주할 수 있지만, 훨씬 더 제한적입니다. 또 다른 Python 구성 요소인 yield도 GOTO의 한 형태, 즉 훨씬 더 제한이 적은 형태로 간주할 수 있습니다. 이 PEP의 목표는 기존 제어 흐름 도구인 breakcontinue를 확장하여 제어 흐름을 더 이해하기 쉽게 만드는 것이지, 더 어렵게 만드는 것이 아닙니다.

레이블이 지정된 breakcontinue는 다른 함수나 메서드로 제어를 넘길 수 없습니다. 현재 범위 내의 임의의 코드 줄로 제어를 넘길 수도 없습니다. 현재 이들은 루프의 동작에만 영향을 줄 수 있으며, GOTO와는 상당히 다르고 훨씬 더 제한적입니다. 이 확장 기능을 사용하면 현재 네임스페이스의 모든 상위 루프에 영향을 줄 수 있지만, 동작이 GOTO와 같아지는 것은 아닙니다.

사양

이러한 모든 제안에서 breakcontinue는 그 자체로 현재와 동일하게 동작하며, 기본적으로 가장 안쪽 루프에 적용됩니다.

제안 A - 명시적 레이블

for 및 while 루프 구문 뒤에는 선택적인 as또는 label(문맥에 따라 의미가 달라지는) 키워드 [2]가 오고, 그 뒤에 식별자가 옵니다. 이 식별자를 사용하여 탈출할 루프 또는 계속 실행할 루프를 지정할 수 있습니다.

break(및 continue) 문 뒤에는 탈출할 루프 또는 계속 실행할 루프를 가리키는 선택적 식별자가 옵니다. 다음은 as 키워드를 사용하는 예입니다.:

for a in a_list as a_loop:
    ...
    for b in b_list as b_loop:
        ...
        if condition_one(a, b):
            break b_loop  # same as plain old break
        ...
        if condition_two(a, b):
            break a_loop
        ...
    ...

as 대신 label을 사용하는 경우는 다음과 같습니다.:

for a in a_list label a_loop:
    ...
    for b in b_list label b_loop:
        ...
        if condition_one(a, b):
            break b_loop  # same as plain old break
        ...
        if condition_two(a, b):
            break a_loop
        ...
    ...

위에서 설명한 모든 이점을 갖습니다. 언어 구문을 수정해야 합니다. 즉, breakcontinue 구문 문과 for 및 while 문의 구문을 수정해야 합니다. 새로운 조건 키워드인 label을 도입하거나, 조건 키워드인 as를 확장해야 합니다. [3] 기존 Python 프로그램을 변경해야 할 가능성은 낮습니다. 지역 스코프에 정의되지 않은 식별자를 break 또는 continue에 전달하면 NameError가 발생합니다.

제안 B - 숫자형 break 및 continue

forwhile 루프의 구문을 변경하는 대신, breakcontinue는 PHP와 유사하게 제어할 바깥쪽 루프를 나타내는 숫자 인자를 받습니다.

PHP처럼 1부터 인덱싱하는 것보다, breakcontinue가 0부터 인덱싱하는 루프를 참조하는 편이 더 Pythonic하다고 생각합니다.

for a in a_list:
    ...
    for b in b_list:
        ...
        if condition_one(a,b):
            break 0  # same as plain old break
        ...
        if condition_two(a,b):
            break 1
        ...
    ...

너무 크거나 0보다 작거나 정수가 아닌 숫자를 break 또는 continue에 전달하면 (아마도) IndexError가 발생합니다.

이 제안은 기존 Python 프로그램을 변경할 필요가 없습니다.

제안 C - 반복 기법

같은 줄에 여러 break 및 continue 문을 허용하도록 breakcontinue의 구문이 변경됩니다. 따라서 break break는 첫 번째와 두 번째 바깥쪽 루프를 빠져나옵니다.

for a in a_list:
    ...
    for b in b_list:
        ...
        if condition_one(a,b):
            break  # plain old break
        ...
        if condition_two(a,b):
            break break
        ...
    ...

또한 break continue라고 간단히 작성하여 내부 루프를 빠져나오고 그다음 가장 바깥쪽 루프에서 계속할 수 있습니다. [4] 등도 마찬가지입니다. 기존 루프보다 많은 break 또는 continue 문을 작성하면 어떤 예외가 발생할지 확실하지 않습니다 (SyntaxError일 수도 있습니다).

이해하기 너무 어렵다고 판단되어 이 제안이 거부될 것으로 예상합니다.

이 제안은 기존 Python 프로그램을 변경할 필요가 없습니다.

제안 D - 명시적 이터레이터

레이블을 사용한 break를 위해 for 및 while 루프 구문을 꾸미는 대신, 레이블이 지정된 break를 사용하려는 프로그래머는 더 깊은 루프 안에서 해당 루프를 break로 빠져나오거나 continue하기를 원할 경우 이터레이터를 명시적으로 만들고 식별자에 할당해야 합니다.

a_iter = iter(a_list)
for a in a_iter:
    ...
    b_iter = iter(b_list)
    for b in b_iter:
        ...
        if condition_one(a,b):
            break b_iter  # same as plain old break
        ...
        if condition_two(a,b):
            break a_iter
        ...
    ...

이터레이터가 아닌 객체를 break 또는 continue에 전달하면 TypeError가 발생하고, 존재하지 않는 식별자를 전달하면 NameError가 발생합니다. 이 제안에서는 레이블이 지정된 루프를 만드는 데 한 줄만 추가하면 되며, 포함하는 루프를 빠져나오는 데 추가 줄이 필요하지 않고, 기존 Python 프로그램도 변경할 필요가 없습니다.

제안 E - 명시적 이터레이터 및 이터레이터 메서드

이는 제안 D의 변형입니다. breakcontinue를 가장 기본적으로 사용하는 것 이외의 기능이 필요하다면 이터레이터를 명시적으로 만들어야 합니다. breakcontinue의 구문을 수정하는 대신, .break().continue() 메서드를 이터레이터 타입에 추가할 수 있습니다.

a_iter = iter(a_list)
for a in a_iter:
    ...
    b_iter = iter(b_list)
    for b in b_iter:
        ...
        if condition_one(a,b):
            b_iter.break()  # same as plain old break
        ...
        if condition_two(a,b):
            a_iter.break()
        ...
    ...

순전히 보기 흉하다는 이유로 이 제안이 거부될 것으로 예상합니다. 그러나 이 제안은 언어 구문을 전혀 변경할 필요가 없으며, 기존 Python 프로그램을 변경할 필요도 없습니다.

구현

Python 언어 구현 자체를 살펴본 적이 없으므로 이것을 구현하기가 얼마나 어려울지 전혀 알지 못합니다. 이 PEP가 채택되었지만 이 기능을 작성할 사람이 없다면 직접 구현해 보겠습니다.

각주

리소스

이 문제는 이전에도 제기된 적이 있지만, 제가 아는 한 해결된 적은 없습니다.

  • 레이블이 지정된 breakdo...while 루프의 맥락에서 comp.lang.python에 게시된 글입니다.
  • break LABEL 대 예외 + 제안은 흐름 제어에 예외를 사용하는 경우와 비교한 내용으로, python-list에 게시되었습니다.
  • 이름이 지정된 코드 블록은 레이블이 지정된 break / continue를 사용하고자 하는 바람에서 비롯된 python-list의 제안입니다.
  • mod_python 버그 수정은 레이블이 지정된 break와 continue가 없는 문제를 우회하기 위해, 내부 루프에서 포함하는 루프의 continue를 트리거하는 플래그를 설정하는 사례입니다.