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

Python 개선 제안 한국어 번역

PEP 601 – finally를 벗어나는 return/break/continue 금지

Author:
Damien George, Batuhan Taskaya
Sponsor:
Alyssa Coghlan
Discussions-To:
Discourse thread
Status:
Rejected
Type:
Standards Track
Created:
26-Aug-2019
Python-Version:
3.8
Post-History:
26-Aug-2019, 23-Sep-2019
Superseded-By:
765
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

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

거부 기록

이 PEP는 운영 위원회(Steering Council)의 4/4 표결로 거부되었습니다.

PEP를 거부한 Guido의 인자는 다음과 같습니다: “대부분의 언어가 이런 종류의 구문을 구현하고 있지만, 스타일 가이드나 린터 또는 둘 다에서 이를 거부하는 것으로 보입니다. 이를 PEP 8에 추가하자는 제안이라면 지지하겠습니다”, 그리고 “장난감 예제들이 다소 오해의 소지가 있다는 점을 지적합니다 – 유용할 수 있는 기능은 finally 블록 안의 조건부 return(또는 break 등)입니다.”.

초록

이 PEP는 finally 스위트 안에서 finally를 벗어나게 되는 return, break, continue 문을 금지할 것을 제안합니다. 이러한 위치에서 이들을 사용하면 finally를 거쳐 발생 중인 활성 예외가 조용히 취소되므로, 코드가 불명확해지고 버그가 생길 수 있습니다.

continue는 현재 Python 3.7에서 (구현상의 문제로) finally 안에서 지원되지 않으며, Python 3.8에서도 이에 대한 지원을 추가하지 않을 것을 제안합니다. return과 break에 대해서는 Python 3.9에서 사용을 폐지 예정(deprecate)으로 지정하고, Python 3.10에서 컴파일 경고를 내보낸 다음, 그 이후에는 사용을 금지할 것을 제안합니다.

동기

finally 스위트 안에서 return, break, continue를 사용하면 전혀 명백하지 않은 동작이 일어납니다. 다음 함수를 살펴보십시오:

def foo():
    try:
        foo()
    finally:
        return

이 함수는 무한 재귀가 있고 try 안에서 예외를 발생시키는데도 (예외 없이) 깔끔하게 반환합니다. 그 이유는 finally 안의 return이 finally 스위트를 통해 전파되는 모든 예외를 조용히 취소하기 때문입니다. 이러한 동작은 예상하기 어렵고 전혀 명백하지 않습니다. 이 함수는 다음과 동등합니다:

def foo():
    try:
        foo()
    except:
        pass
    return

break와 continue도 finally 스위트 바깥의 코드로 점프하는 경우 비슷하게 동작합니다(예외를 묵살합니다). 예를 들면 다음과 같습니다:

def bar():
    while True:
        try:
            1 / 0
        finally:
            break

이 동작은 파이썬의 선(The Zen of Python)의 다음 부분에 어긋납니다.

  • 명시적인 것이 암시적인 것보다 낫습니다 - 예외가 암시적으로 묵살됩니다
  • 가독성은 중요합니다 - 코드의 의도가 명백하지 않습니다
  • 오류는 절대 조용히 넘어가서는 안 됩니다; 명시적으로 묵살하지 않는 한 말입니다 - 예외가 암시적으로 묵살됩니다

예외를 묵살하는 이 동작이 정말 필요하다면 대신 명시적인 try-except 형태를 사용할 수 있으며, 이렇게 하면 코드가 더 명확해집니다.

의미론과는 별개로, finally 스위트 안에서 return/break/continue를 구현하는 것은 간단하지 않습니다. 런타임에 활성 예외를 정확히 추적하고(실행 중인 finally 스위트에는 활성 예외가 있을 수도 있고 없을 수도 있습니다) 적절히 취소해야 하기 때문입니다. CPython에는 실제로 continue의 경우에 이와 관련된 버그가 있었고, 그래서 원래는 이를 허용하지 않았습니다 [1]. finally 안에서 return/break/continue가 올바르게 동작하도록 요구하면 Python의 대체 구현들에 불필요한 부담을 지우게 됩니다.

다른 언어

Java는 finally 블록 안에서 반환하는 것을 허용하지만, [2], [3], [4]에 따르면 그러한 사용은 권장되지 않습니다. 이후 Java 컴파일러에는 finally 블록 안에서 return을 사용하는 것에 대해 경고하는 린팅 옵션 -Xlint:finally가 포함되었습니다. Eclipse 편집기도 이러한 사용에 대해 경고합니다.

Ruby는 ensure(Python의 finally) 내부에서 return을 허용하지만, 이 경우 return을 명시적으로 작성해야 합니다. 이 방식은 권장되지 않으며 린터가 이를 처리합니다 [5], [6].

Ruby와 마찬가지로 JavaScript도 finally 안에서 return/break/continue를 사용하도록 허용하지만, 이는 안전하지 않은 것으로 여겨지며 eslint가 이를 처리합니다 [7].

C#은 finally 안에서 return/goto/break 같은 종료 문을 사용하지 못하게 합니다 [8], [9].

근거

finally 안에서 return/break/continue의 동작은 명확하지 않고, 이 패턴은 거의 사용되지 않으며, 같은 동작을 하는 (더 명시적인) 코드를 간단히 작성하는 대안이 있으므로, 이 문법을 금지하는 것이 가장 간단한 방법입니다.

명세

이는 문법이 아니라 컴파일러에 대한 변경입니다. 컴파일러는 finally 스위트에서 다음을 검사해야 합니다:

  • 중첩 수준과 관계없이 어떤 문에든 있는 return.
  • 중첩 수준과 관계없이 어떤 문에든 있으면서, 제어 흐름을 finally 스위트 바깥으로 옮기게 되는 break/continue.

이러한 경우를 발견하면 컴파일러는 적절한 예외를 발생시켜야 합니다:

  • continue의 경우 SyntaxError (이는 3.7의 현재 동작입니다).
  • return/break의 경우 3.10에서는 SyntaxWarning, 그 이후에는 SyntaxError입니다.

예를 들어, 다음은 모두 이 제안에 따라 금지됩니다:

def f():
    try:
        pass
    finally:
        return

def g():
    try:
        pass
    finally:
        try:
            return
        finally:
            pass

def h():
    try:
        pass
    finally:
        try:
            pass
        finally:
            for x in range(10):
                return

다음 코드에서는 continue가 finally를 벗어나지 않으므로 여전히 허용됩니다:

try:
    pass
finally:
    for x in range(10):
        continue

finally 내부에서 yield하는 것은 이 PEP에서도 여전히 허용된다는 점에 유의하십시오. 제너레이터를 재개하면 finally도 재개되어 결국 활성 예외를 모두 발생시키기 때문입니다(따라서 yield 때문에 예외가 묻히는 일은 결코 없습니다).

하위 호환성

이는 return과 break에 대해 하위 호환성을 깨는 변경입니다.

CPython 표준 라이브러리(v3.8.0b1-651-g7fcc2088a5 기준)에서 finally 안에서 return을 사용하는 위치는 다음과 같습니다:

  • Lib/subprocess.py:921 - 여기서의 사용은 버그로 보입니다
  • Lib/multiprocessing/connection.py:316 - 여기서의 사용은 정당해 보이지만 의도가 명확하지 않습니다
  • Lib/multiprocessing/connection.py:318 - 여기서의 사용은 정당해 보이지만 의도가 명확하지 않습니다
  • Lib/test/test_sys_settrace.py:837 - finally 안의 return에 대한 테스트
  • Lib/test/test_sys_settrace.py:1346 - finally 안의 return에 대한 테스트

표준 라이브러리에는 finally 안에서 (finally를 벗어나는) break를 사용하는 경우가 없습니다.

보안 영향

이는 언어를 단순하게 만들고 관련 코드를 제거하는 변경이므로, 보안 취약점을 악용할 새로운 경로를 만들지 않을 것입니다.

교육 방법

이 기능은 매우 드물게 사용되므로, 이를 금지해도 초보자가 아닌 고급 사용자에게만 영향을 줄 가능성이 높으며, 기존 교육 자료에도 아마 영향을 주지 않을 것입니다. 이는 기능을 제거하는 변경이므로, 금지된 기능을 사용하면 발생하는 SyntaxError가 사용자에게 이를 알려 주게 됩니다.

참조 구현

현재 참조 구현은 없지만, finally에서 continue를 처리하는 현재 방식(SyntaxError를 발생시키는 방식)을 return과 break로 확장할 수 있습니다.

참고 문헌