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의 break와 continue 문에서 레이블을 지원할 것을 제안합니다. 이 제안은 다른 언어에 있는 레이블이 붙은 break와 continue, 그리고 드물지만 꾸준히 이런 기능이 필요했던 저자 자신의 경험에서 영감을 받았습니다.

소개

break 문을 사용하면 프로그래머가 루프를 일찍 종료할 수 있고, continue 문을 사용하면 루프의 다음 반복으로 일찍 넘어갈 수 있습니다. 현재 Python에서 break와 continue는 자신을 둘러싼 가장 안쪽 루프에만 적용될 수 있습니다.

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

간결함을 위해 이 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
    ...

이 방법은 바깥쪽 (a) 루프를 언제 빠져나갈지 추적하기 위해 다섯 줄의 코드와 추가 변수 time_to_break_out_of_a가 필요합니다. 게다가 이 다섯 줄은 여러 줄의 코드에 걸쳐 흩어져 있어 제어 흐름을 이해하기 어렵게 만듭니다.

이 기법은 오류가 생기기도 쉽습니다. 이 코드를 수정하는 프로그래머가 새 코드를 time_to_break_out_of_a 검사 뒤에 넣지 않고, 실수로 안쪽 (b) 루프가 끝난 뒤이면서 그 검사 앞인 곳에 넣을 수 있습니다. 그러면 바깥쪽 루프를 빠져나가면서 건너뛰었어야 할 코드가 잘못 실행됩니다.

예외를 사용해서 작성할 수도 있습니다. 프로그래머는 특수한 예외를 선언하고, 안쪽 루프를 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 루프로 새어 나온다는 사실에 의존합니다. 이 동작은 (명시적으로 지원되기는 하지만) 초보자에게는 놀라운 일이며, 제 생각에는 직관에 어긋나는 나쁜 관행입니다.

또한 프로그래머는 condition two에서 break를 두 개 모두 넣어야 하고, 두 번째 break 앞에는 코드를 끼워 넣지 말아야 한다는 점을 여전히 기억해야 합니다. condition_two()일 때 두 루프를 모두 빠져나간다는 하나의 개념적 동작에 두 들여쓰기 수준에 걸친 네 줄의 코드가 필요하며, 이 코드는 안쪽 (b) 루프 끝에서 그 사이에 들어간 많은 줄 때문에 서로 떨어져 있을 수도 있습니다.

다른 언어

이제 다른 프로그래밍 언어에 어떤 반감을 가지고 있든 잠시 접어 두고, 레이블이 붙은 break와 continue의 문법을 살펴봅시다. 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 레이블은 생략할 수 있으며, last와 continue는 기본적으로 가장 안쪽 루프에 적용됩니다.)

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) 루프 안에서 탈출 조건을 다시 검사하기 전 위치에 코드가 실수로 삽입될 위험도 없습니다. 이것이 레이블이 있는 break와 continue가 Python에 가져다줄 이점입니다.

이 PEP는 무엇이 아닌가

이 PEP는 Python에 GOTO를 추가하자는 제안이 아닙니다. GOTO는 프로그래머가 임의의 코드 블록이나 줄로 점프할 수 있게 해 주며, 일반적으로 제어 흐름을 따라가기 더 어렵게 만듭니다. break와 continue는 (레이블 지원 여부와 관계없이) GOTO의 일종으로 볼 수도 있지만, 훨씬 더 제한적입니다. 또 다른 Python 구문인 yield 역시 GOTO의 한 형태로 볼 수 있으며, 이는 훨씬 덜 제한적인 형태입니다. 이 PEP의 목표는 기존 제어 흐름 도구인 break와 continue의 확장을 제안하여, 제어 흐름을 더 어렵게 만드는 것이 아니라 더 이해하기 쉽게 만드는 것입니다.

레이블이 붙은 break와 continue는 다른 함수나 메서드로 제어를 넘길 수 없습니다. 현재 스코프 안의 임의의 코드 줄로 제어를 넘기는 것조차 불가능합니다. 현재 이 문들은 루프의 동작에만 영향을 줄 수 있으며, GOTO와는 상당히 다르고 훨씬 더 제한적입니다. 이 확장은 이 문들이 현재 네임스페이스에서 자신을 감싸는 어떤 루프에든 영향을 줄 수 있게 하지만, 그 동작을 GOTO처럼 바꾸지는 않습니다.

명세

이 모든 제안에서 break와 continue를 단독으로 사용하면 지금과 똑같이 동작하며, 기본적으로 가장 안쪽 루프에 적용됩니다.

제안 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
        ...
    ...

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

제안 B - 숫자를 사용하는 break와 continue

for와 while 루프의 문법은 바꾸지 않습니다. 대신 PHP처럼 break와 continue가 숫자 인자를 받고, 이 숫자로 제어할 바깥 루프를 지정합니다.

PHP처럼 1부터 번호를 매기는 것보다 break와 continue가 0부터 번호를 매겨 루프를 가리키는 편이 더 파이썬답다고 생각합니다.

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의 문법을 바꿔서 한 줄에 break 문과 continue 문을 여러 개 쓸 수 있게 합니다. 따라서 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 - 명시적 이터레이터

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를 변형한 것입니다. break와 continue를 가장 기본적인 방식이 아닌 다른 방식으로 사용하려면 이터레이터를 명시적으로 만들어야 합니다. break와 continue의 문법을 수정하는 대신, 이터레이터(Iterator) 타입에 .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가 채택되었는데 이 기능을 작성할 사람이 없다면 제가 직접 구현해 보겠습니다.

각주

참고 자료

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

  • comp.lang.python에 올라온 labeled breaks, do...while 루프와 관련된 논의
  • python-list에 올라온 break LABEL vs. exceptions + PROPOSAL, 흐름 제어에 예외를 사용하는 방식과 비교한 논의
  • python-list에 올라온 Named code blocks, 레이블이 붙은 break / continue를 원하는 데서 비롯된 제안
  • mod_python bug fix 레이블이 붙은 break와 continue가 없는 점을 우회하기 위해, 안쪽 루프에서 플래그를 설정하여 이를 감싸는 루프에서 continue가 실행되도록 한 예