PEP 348 – Python 3.0을 위한 예외 재구성
- Author:
- Brett Cannon <brett at python.org>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 28-Jul-2005
- Post-History:
Table of Contents
- 초록
- 변경을 원하는 근거
- 재구성의 철학
- 새 계층 구조
- Python 2.4와 비교한 차이점
raise에 필요한 슈퍼클래스- 맨
except절의 Exception 예외 포착 - 전환 계획
- 기각된 아이디어
- DeprecationWarning이 PendingDeprecationWarning을 상속하는 방안
- AttributeError가 TypeError 또는 NameError를 상속하는 방안
- EnvironmentError 제거
- MacError와 UnixError 도입
- SystemError가 SystemExit를 서브클래싱하는 안
- Exception 예외 아래의 ControlFlowException
- NameError를 NamespaceError로 이름 변경
- 기존 예외의 이름 변경
- EOFError를 IOError의 서브클래스로 만들기
- MemoryError와 SystemError에 공통 슈퍼클래스 두기
- PendingDeprecationWarning과 DeprecationWarning의 공통 슈퍼클래스
- WindowsError 제거
- KeyboardInterrupt와 SystemExit의 슈퍼클래스
- 감사의 글
- 참고 문헌
- Copyright
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
Note
이 PEP는 거부되었습니다 [16].
초록
Python 2.4 버전 기준으로 내장 네임스페이스에는 38개의 예외(경고 포함)가 있으며, 이들은 상당히 얕은 계층 구조를 이루고 있습니다. 이 클래스들은 경험을 통해 배울 기회 없이 수년에 걸쳐 생겨났습니다. 이 PEP는 하위 호환성이 그다지 큰 문제가 되지 않는 Python 3.0에 맞춰 계층 구조를 재구성할 것을 제안합니다.
이 재구성과 함께, raise 문에 전달되는 모든 객체가 특정 슈퍼클래스를 상속해야 한다는 요구 사항을 추가할 것을 제안합니다. 이는 예외의 기본 인터페이스를 보장하고 예외의 자연스러운 계층 구조를 한층 더 강화하기 위한 것입니다.
마지막으로, 단독 except 절은 의미상 except Exception과 동등하도록 변경됩니다. 현재 대부분의 사람들이 이 목적으로 단독 except 절을 사용하고 있으며, 예외 계층 구조를 재구성하면 이것이 실용적인 기본 동작이 됩니다.
변경을 원하는 근거
예외는 Python에서 매우 중요한 부분입니다. 예외는 전통적으로 프로그램의 오류를 알리는 데 사용되지만, 이터레이터 같은 것의 흐름 제어에도 사용되는 방향으로 발전해 왔습니다.
예외는 매우 중요하지만, 예외에는 구조가 부족합니다. 이는 어떤 객체든 예외로 발생시킬 수 있다는 사실에서 비롯됩니다. 이 때문에 어떤 종류의 객체가 발생할지 전혀 보장할 수 없으며, 발생한 객체들이 따를 수 있는 계층 구조가 모두 무너집니다.
그러나 예외에는 분명히 예외의 심각도를 보여 주는 계층 구조가 있습니다. 이 계층 구조는 또한 관련된 예외들을 함께 묶어 except 절에서 예외를 쉽게 포착할 수 있게 해 줍니다. 사람들이 이 계층 구조에 의존할 수 있도록, 발생시키는 모든 객체가 상속해야 하는 공통 슈퍼클래스를 제안합니다. 이는 또한 발생한 객체의 인터페이스를 보장할 수 있게 해 줍니다(PEP 344 참조). 이 모든 것에 대한 논의는 이전에 python-dev에서 이루어진 바 있습니다 [1].
현재 단독 except 절은 모든 예외를 포착합니다. 이는 편리할 수도 있지만, 일반적인 경우에는 지나치게 광범위합니다. 필수 슈퍼클래스가 있으므로, 모든 예외를 포착하는 것은 특정 예외 하나만 포착하는 것만큼 쉬워집니다. 이로써 단독 except 절을 더 유용한 목적으로 사용할 수 있게 됩니다. 이 역시 python-dev에서 논의된 바 있습니다 [2].
마지막으로, 예외 계층 구조를 약간 변경하면 구조 면에서 훨씬 더 합리적이 됩니다. 약간의 재배치를 통해, 일반적으로 포착해서는 안 되는 예외가 실행 스택의 최상위까지 전파되어 의도한 대로 인터프리터를 종료하도록 할 수 있습니다.
재구성의 철학
계층 구조를 재편할 때는 이 PEP의 이전 초안에 대한 논의 [4], [5], [6], [7], [8], [9]에서 형성된 일반 원칙을 따랐습니다. 가장 중요한 원칙은 잘 동작하는 것은 아무것도 망가뜨리지 않는다는 것이었습니다. 이는 이름이 심각하게 나쁘다고 판단되지 않는 한 예외의 이름을 바꾸는 일은 고려하지 않는다는 뜻이었습니다. 또한 정말로 잘못된 위치에 있다고 여겨지지 않는 한 예외를 제거하지 않는다는 뜻이기도 했습니다. 새 예외는 어떤 범주에 속한 예외들의 상위 클래스를 포착할 필요가 있을 수 있는 상황에서만 도입했습니다. 마지막으로, 기존 예외의 상속 트리는 애초에 정말로 잘못된 위치에 있었다고 판단되는 경우에만 변경하기로 했습니다.
모든 새 예외에 대해 적절한 접미사를 골라야 했습니다. 오류를 알리는 예외에는 “Error”를 사용합니다. 예외가 경고라면 “Warning”을 사용합니다. 다른 접미사가 모두 적절하지 않고 더 잘 맞는 특정 접미사도 없을 때에는 “Exception”을 사용합니다.
그다음에는 어떤 예외가 Exception을 상속해야 하고 어떤 예외가 상속하지 말아야 하는지를 정하는 일이 남았습니다. 이는 예외를 지정하지 않은 except 절을 더 유용하게 만들기 위한 것이었습니다.
마지막으로, 기존 계층 구조 전체가 새 예외를 상속해야 했습니다. 이 새 예외는 모든 예외가 상속해야 하는 필수 상위 클래스 역할을 하도록 만든 것입니다.
새 계층 구조
Note
“stricter inheritance”(더 엄격한 상속) 표시가 붙은 예외는 더 이상 특정 클래스를 상속하지 않습니다. “broader inheritance” 플래그는 예외의 상속 트리에 클래스가 추가되었음을 의미합니다. 모든 비교는 Python 2.4의 예외 계층 구조를 기준으로 합니다.
+-- BaseException (new; broader inheritance for subclasses)
+-- Exception
+-- GeneratorExit (defined in PEP 342)
+-- StandardError
+-- ArithmeticError
+-- DivideByZeroError
+-- FloatingPointError
+-- OverflowError
+-- AssertionError
+-- AttributeError
+-- EnvironmentError
+-- IOError
+-- EOFError
+-- OSError
+-- ImportError
+-- LookupError
+-- IndexError
+-- KeyError
+-- MemoryError
+-- NameError
+-- UnboundLocalError
+-- NotImplementedError (stricter inheritance)
+-- SyntaxError
+-- IndentationError
+-- TabError
+-- TypeError
+-- RuntimeError
+-- UnicodeError
+-- UnicodeDecodeError
+-- UnicodeEncodeError
+-- UnicodeTranslateError
+-- ValueError
+-- ReferenceError
+-- StopIteration
+-- SystemError
+-- Warning
+-- DeprecationWarning
+-- FutureWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+ -- WindowsError
+-- KeyboardInterrupt (stricter inheritance)
+-- SystemExit (stricter inheritance)
Python 2.4와 비교한 차이점
상속 변경을 논의할 때는 용어를 더 자세히 설명해야 합니다. 상속이 변경되면 상속은 더 넓어지거나 더 제한적이 됩니다. “Broader”(더 넓은)는 클래스의 상속 트리가 cls, A와 같은 형태였다가 cls, B, A가 되는 경우를 말합니다. “Stricter”(더 엄격한)는 그 반대입니다.
BaseException
모든 예외가 상속해야 하는 상위 클래스입니다. 이 이름은 이 클래스가 예외 계층 구조의 기반(base)에 있으면서도 그 자체로 예외라는 점을 반영하도록 선택되었습니다. “Raisable”도 이름으로 고려되었지만, 이 클래스가 그 자체로 예외라는 사실을 제대로 반영하지 못하므로 채택되지 않았습니다.
BaseException을 직접 상속하는 것은 예상되지 않으며, 일반적인 경우에는 권장되지 않을 것입니다. 대부분의 사용자 정의 예외는 대신 Exception을 상속해야 합니다. 이렇게 하면 포착해야 하는 모든 예외를 포착하는 일반적인 경우에 Exception을 포착하는 방식이 계속 동작합니다. BaseException은 완전히 새로운 범주의 예외가 필요한 경우에만 직접 상속해야 합니다.
하지만 모든 예외를 무조건 포착해야 하는 경우에는 except BaseException을 사용하면 됩니다.
KeyboardInterrupt와 SystemExit
두 예외는 더 이상 Exception 아래에 있지 않습니다. 이는 예외를 지정하지 않은 except 절이 Exception을 상속하는 예외를 포착함으로써 더 실용적인 기본 처리 역할을 할 수 있도록 하기 위한 것입니다. KeyboardInterrupt와 SystemExit는 모두 인터프리터가 종료되어야 한다는 신호 역할을 하므로, 일반적인 경우에 이들을 포착하는 것은 의미상 잘못된 동작입니다.
NotImplementedError
RuntimeError 대신 Exception 예외 클래스를 상속합니다.
원래 RuntimeError를 상속하던 NotImplementedError는 사용자 코드에서 임시방편 예외로 쓰도록 의도된 예외와 직접적인 관련이 없습니다. 따라서 이제 Exception 예외 클래스를 직접 상속합니다.
raise에 필요한 슈퍼클래스
raise 문에 전달되는 모든 객체가 특정 슈퍼클래스를 상속하도록 요구하면 모든 예외가 특정 속성을 가진다는 것이 보장됩니다. PEP 344가 채택되면 그 PEP에 기술된 속성이 발생하는 모든 예외에 존재하는 것이 보장됩니다. 이는 예외에서 정보를 조회하는 일을 훨씬 쉽게 만들어 디버깅을 수월하게 하는 데 도움이 될 것입니다.
제안된 계층 구조에서는 BaseException이 필수 베이스 클래스입니다.
구현
강제하는 방법은 간단합니다. 예외를 발생시키기 전에 먼저 상속 검사를 하도록 RAISE_VARARGS를 수정하는 것으로 충분할 것입니다. C API의 경우, 예외를 설정하는 모든 함수에 같은 상속 검사가 적용됩니다.
맨 except 절의 Exception 예외 포착
기존 Python 2.4 코드 대부분에서 단독 except 절은 잡는 예외의 범위가 너무 넓습니다. 일반적으로는 오류를 알리는 예외만 잡는 것이 바람직합니다. 즉, 인터프리터가 종료되어야 함을 나타내는 데 쓰이는 예외는 일반적인 경우에 잡혀서는 안 됩니다.
KeyboardInterrupt와 SystemExit가 Exception 대신 BaseException을 상속하도록 옮겨졌으므로, 맨 except 절이 except Exception처럼 동작하여 Exception 예외를 잡도록 바꾸는 것은 훨씬 더 합리적인 기본 동작이 됩니다. 이러한 의미가 대부분의 사람들이 단독 except 절에 원하는 것이므로, 이 변경으로 인해 깨지는 코드도 매우 적을 것입니다.
단독 except 절을 완전히 제거하자는 주장도 있었습니다. 이는 Zen of Python에 나열된 Only One Way To Do It(OOWTDI)과 Explicit Is Better Than Implicit(EIBTI)를 모두 위반한다는 주장이 제기되었습니다. 하지만 이 경우에는 역시 Zen of Python에 있는 Practicality Beats Purity(PBP)가 이 둘보다 우선합니다. BDFL은 단독 except 절이 이런 방식으로 동작할 것이라고 밝혔습니다 [14].
구현
컴파일러는 단독 except 절을 만날 때마다 except Exception에 해당하는 바이트코드를 생성합니다.
전환 계획
이 PEP에서 계획한 모든 기능을 추가하려면 복잡성과 난잡함이 따르기 때문에, 전환 계획은 매우 단순합니다. Python 2.5에서는 BaseException이 추가됩니다. Python 3.0에서는 나머지 모든 기능(필수 슈퍼클래스, 상속 변경, 단독 except 절이 except Exception과 같아지는 것)이 적용됩니다. Python 2.5에서 이 모든 것을 하위 호환성을 유지하는 방식으로 동작하게 하려면 예외 메커니즘에 매우 깊은 해킹이 필요한데, 이는 오류가 발생하기 쉽고 얻는 이점은 적은 반면 성능 저하로 이어질 수 있습니다.
전환을 돕기 위해, 몇 가지 프로그래밍 지침을 반영하도록 문서가 변경됩니다:
- 모든 예외를 잡으려면 BaseException을 잡으십시오
- 인터프리터 종료를 나타내지 않는 모든 예외를 잡으려면 Exception을 명시적으로 잡으십시오
- KeyboardInterrupt와 SystemExit를 명시적으로 잡으십시오. Exception 예외 클래스로부터의 상속으로 포착되리라고 기대하지 마십시오
- RuntimeError를 상속한다는 점에 기대지 말고 NotImplementedError는 항상 명시적으로 잡으십시오
예외를 다루는 ‘exceptions’ 모듈 [3], 튜토리얼 [15], 그리고 PEP 290의 문서는 모두 갱신이 필요합니다.
기각된 아이디어
DeprecationWarning이 PendingDeprecationWarning을 상속하는 방안
이 방안은 원래 DeprecationWarning을 다음 버전에서 제거될 PendingDeprecationWarning으로 볼 수 있다는 이유로 제안되었습니다. 그러나 상속 관계를 논리적으로 반대로 두어도 성립한다고 생각한 사람이 적지 않았기 때문에 이 아이디어는 철회되었습니다.
AttributeError가 TypeError 또는 NameError를 상속하는 방안
속성을 타입 인터페이스의 일부로 보는 관점에서 TypeError를 상속하자는 아이디어가 나왔습니다. 그러나 이는 덕 타이핑의 사고방식에 부분적으로 어긋나므로 이 아이디어는 철회되었습니다.
객체마다 속성이 들어 있는 자체 네임스페이스가 있다고 볼 수 있고, 속성을 찾지 못하는 것은 네임스페이스 실패라고 볼 수 있다는 이유로 NameError를 상속하자는 제안도 있었습니다. 이 관점에 모두가 동의하지는 않았기 때문에 이 방안도 철회되었습니다.
EnvironmentError 제거
EnvironmentError가 불필요한 구분이라는 생각에서 처음 제안되었으나, BDFL이 이 아이디어를 기각했습니다 [10].
MacError와 UnixError 도입
WindowsError와 대칭을 맞추기 위해 제안되었으나, BDFL은 이 예외들이 충분히 사용되지 않을 것이라고 말했습니다 [10]. 그러자 WindowsError를 제거하자는 아이디어가 제안되어 합리적인 것으로 받아들여졌고, 이로써 이 예외들을 추가하자는 아이디어는 완전히 무의미해졌습니다.
SystemError가 SystemExit를 서브클래싱하는 안
SystemError는 시스템 종료로 이어지도록 의도된 예외라는 이유로 제안되었으나, CriticalError가 이를 더 잘 나타내므로 이 아이디어는 제외되었습니다.
Exception 예외 아래의 ControlFlowException
ControlFlowException이 Exception 예외 클래스를 상속해야 한다는 제안이 있었습니다. 제어 흐름 예외는 보통 하나의 except 절에서 모두 잡을 필요가 없다는 판단에 따라 이 아이디어는 기각되었습니다.
NameError를 NamespaceError로 이름 변경
NameError가 더 간결하며, “Namespace”(네임스페이스)의 대소문자 표기를 잘못 입력할 여지도 전혀 없다고 여겨졌습니다 [11].
RuntimeError 이름 변경 또는 SimpleError 도입
새 예외를 만들 필요가 없는 상황에서 쓰도록 의도된 예외에 RuntimeError라는 이름은 전혀 명확하지 않다는 것이 이 제안의 취지였습니다. 이 예외가 이미 인터프리터 전반에서 사용되고 있다는 이유로 이름 변경은 기각되었습니다 [12]. SimpleError를 기각한 것은 사람들이 원하는 예외를 자유롭게 골라 쓸 수 있어야 하며, 특정 예외를 그토록 노골적으로 권해서는 안 된다는 생각 때문이었습니다 [13].
기존 예외의 이름 변경
여러 가지 이름 변경이 제안되었지만 +0을 넘는 표를 얻은 안은 하나도 없었습니다(ReferenceError를 WeakReferenceError로 이름 변경하는 안). 기존 이름으로도 충분하며, 지금까지 그 이름에 대해 적극적으로 불만을 제기한 사람이 아무도 없었다는 것이 당시의 판단이었습니다. 하위 호환성 문제를 최소화하고 기존 Python 프로그래머에게 불필요한 부담을 주지 않기 위해 이름 변경안은 삭제되었습니다.
EOFError를 IOError의 서브클래스로 만들기
원래는 EOFError가 I/O를 직접 다루므로 IOError의 서브클래스여야 한다고 생각했습니다. 그러나 EOFError는 어떤 사건(I/O 포트의 소진)이 발생했다는 신호로 더 많이 사용되므로, 그렇게 구체적인 오류 예외의 서브클래스가 되어서는 안 됩니다.
MemoryError와 SystemError에 공통 슈퍼클래스 두기
두 클래스 모두 인터프리터와 관련이 있으니, 공통 슈퍼클래스를 두면 어떻겠습니까? 둘 중 하나는 인터프리터가 복구해서는 안 되는 상태에 있음을 의미하지만, 다른 하나는 그렇지 않기 때문입니다.
PendingDeprecationWarning과 DeprecationWarning의 공통 슈퍼클래스
폐지 경고 예외들을 하나로 묶는 것은 직관적으로 타당합니다. 그러나 두 경고 모두 거의 사용되지 않으며, 동시에 사용되는 경우는 더더욱 드물다는 점을 고려하면 이 타당한 아이디어는 잘 들어맞지 않습니다.
WindowsError 제거
원래는 이처럼 플랫폼에 특화된 예외가 내장 네임스페이스에 있어서는 안 된다는 생각에 근거하여 제안되었습니다. 그러나 이 예외를 사용하는 코드가 이를 유지할 만큼 충분히 존재한다는 사실이 드러났습니다.
KeyboardInterrupt와 SystemExit의 슈퍼클래스
Exception을 상속하지 않는 예외를 더 쉽게 잡을 수 있게 하고 새 계층 구조로의 전환을 쉽게 하기 위해 제안되었지만, 이 아이디어는 BDFL에 의해 거부되었습니다 [14]. 기존 코드에서 이 두 예외를 함께 잡는 사례가 충분히 나타나지 않으므로 내장 네임스페이스를 어지럽히는 것을 정당화할 수 없다는 인자가 제시되었습니다.
감사의 글
토론에 참여해 주신 Robert Brewer, Josiah Carlson, Alyssa Coghlan, Timothy Delaney, Jack Diedrich, Fred L. Drake, Jr., Philip J. Eby, Greg Ewing, James Y. Knight, MA Lemburg, Guido van Rossum, Stephen J. Turnbull, Raymond Hettinger, 그리고 미처 언급하지 못한 모든 분께 감사드립니다.
참고 문헌
Copyright
This document has been placed in the public domain.