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을 포착합니다. - 전환 계획
- 기각된 아이디어
- PendingDeprecationWarning을 상속하는 DeprecationWarning
- TypeError 또는 NameError를 상속하는 AttributeError
- EnvironmentError 제거
- MacError 및 UnixError 도입
- SystemExit를 상속하는 SystemError
- 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
“더 엄격한 상속”으로 표시된 예외는 더 이상 특정 클래스를 상속하지 않습니다. “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
모든 예외가 상속해야 하는 상위 클래스입니다. 이 이름은 예외 계층 구조의 기반에 있으면서 그 자체도 예외라는 점을 반영하도록 선택했습니다. “Raisable”도 이름으로 고려했지만, 그 이름은 자체가 예외라는 사실을 제대로 반영하지 못해 채택하지 않았습니다.
BaseException을 직접 상속하는 것은 예상되지 않으며, 일반적인 경우에는 권장하지 않습니다. 대부분의 사용자 정의 예외는 대신 Exception을 상속해야 합니다. 이렇게 하면 포착해야 하는 모든 예외를 포착하는 일반적인 경우에도 Exception을 포착하는 동작이 계속 작동합니다. BaseException을 직접 상속하는 것은 완전히 새로운 범주의 예외가 필요한 경우에만 수행해야 합니다.
그러나 모든 예외를 무조건 포착해야 하는 경우에는 except BaseException을 사용하면 작동합니다.
KeyboardInterrupt 및 SystemExit
이 두 예외는 더 이상 Exception 아래에 있지 않습니다. 이는 Exception을 상속하는 예외를 포착하여 단순한 except절이 더 적절한 기본 사례로 동작하도록 하기 위한 것입니다. KeyboardInterrupt와 SystemExit는 모두 인터프리터가 종료될 것으로 예상된다는 신호로 작동하므로, 일반적인 경우에 이들을 포착하는 것은 잘못된 의미론입니다.
NotImplementedError
RuntimeError 대신 Exception에서 상속합니다.
원래 RuntimeError에서 상속하던 NotImplementedError는 사용자 코드에서 임시방편으로 사용하기 위한 예외와 직접적인 관련이 없습니다. 따라서 이제 Exception에서 직접 상속합니다.
raise에 필요한 슈퍼클래스
raise 문에 전달되는 모든 객체가 특정 슈퍼클래스를 상속하도록 요구하면, 모든 예외에 특정 속성이 존재하도록 보장할 수 있습니다. PEP PEP 344이 채택되면, 해당 문서에 기술된 속성이 발생하는 모든 예외에 반드시 존재하도록 보장됩니다. 이는 예외에서 정보를 조회하는 일을 훨씬 쉽게 만들어 디버깅에 도움이 될 것입니다.
제안된 계층 구조에서는 BaseException이 필수 베이스 클래스입니다.
구현
강제 적용은 간단합니다. 예외를 발생시키기 전에 먼저 상속 검사를 수행하도록 RAISE_VARARGS를 수정하면 충분합니다. C API에서는 예외를 설정하는 모든 함수에 동일한 상속 검사를 적용합니다.
단순한 except절은 Exception을 포착합니다.
기존 Python 2.4 코드 대부분에서 단순한 except절은 포착하는 예외의 범위가 너무 넓습니다. 일반적으로는 오류를 알리는 예외만 포착하는 것이 바람직합니다. 이는 인터프리터가 종료되어야 함을 나타내는 예외는 일반적인 경우에 포착하지 않아야 한다는 의미입니다.
KeyboardInterrupt와 SystemExit가 Exception 대신 BaseException에서 상속하도록 변경되면, 단순한 except절이 except 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에 대한 문서를 모두 업데이트해야 합니다.
기각된 아이디어
PendingDeprecationWarning을 상속하는 DeprecationWarning
이는 DeprecationWarning을 다음 버전에서 제거될 PendingDeprecationWarning으로 볼 수 있기 때문에 처음 제안되었습니다. 그러나 상속이 논리적으로 반대 방향으로도 작동할 수 있다고 생각한 사람이 충분히 많았으므로 이 아이디어는 폐기되었습니다.
TypeError 또는 NameError를 상속하는 AttributeError
속성을 타입 인터페이스의 일부로 보는 관점 때문에 TypeError를 상속하는 방안이 제시되었습니다. 그러나 이는 덕 타이핑의 취지를 부분적으로 훼손하므로 이 아이디어는 폐기되었습니다.
객체는 속성이 존재하는 자체 네임스페이스를 가진 것으로 볼 수 있고, 속성을 찾지 못하면 네임스페이스 실패가 발생하므로 NameError를 상속하는 방안이 제안되었습니다. 그러나 모든 사람이 이 관점을 공유한 것은 아니므로 이 가능성 역시 폐기되었습니다.
EnvironmentError 제거
EnvironmentError가 불필요한 구분이라는 생각에 근거하여 처음 제안되었지만, BDFL이 이 아이디어를 기각했습니다 [10].
MacError 및 UnixError 도입
WindowsError와의 대칭성을 추가하기 위해 제안되었지만, BDFL은 충분히 많이 사용되지 않을 것이라고 말했습니다 [10]. 이후 WindowsError를 제거하자는 아이디어가 제안되었고 합리적인 것으로 받아들여졌으므로, 이러한 예외를 추가하자는 아이디어는 완전히 무효화되었습니다.
SystemExit를 상속하는 SystemError
SystemError는 시스템 종료로 이어지도록 의도되었기 때문에 제안되었지만, CriticalError가 이를 더 잘 나타내므로 이 아이디어는 제거되었습니다.
Exception 아래의 ControlFlowException
ControlFlowException이 Exception을 상속해야 한다는 제안이 있었습니다. 제어 흐름 예외는 일반적으로 하나의 except 절에서 모두 포착할 필요가 없다는 판단에 따라 이 아이디어는 거부되었습니다.
NameError를 NamespaceError로 이름 변경
NameError가 더 간결하다고 여겨지며, “네임스페이스”의 대문자 표기를 잘못 입력할 가능성도 전혀 남기지 않습니다 [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.