PEP 352 – 예외에 필요한 슈퍼클래스
- Author:
- Brett Cannon, Guido van Rossum
- Status:
- Final
- Type:
- Standards Track
- Created:
- 27-Oct-2005
- Python-Version:
- 2.5
- Post-History:
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python 2.4 및 그 이전 버전에서는 어떤 (구식) 클래스든 예외로 발생시킬 수 있습니다. 2.5의 계획은 신식 클래스를 허용하는 것이었지만, 이는 문제를 악화시킵니다. 즉, 모든 클래스(또는 인스턴스)를 발생시킬 수 있게 됩니다! 이는 예외의 인터페이스에 관해 어떠한 보장도 할 수 없게 만든다는 문제가 있습니다. 이 PEP는 발생되는 모든 객체가 상속해야 하는 새로운 슈퍼클래스를 도입할 것을 제안합니다. 이러한 제한을 적용하면 신뢰할 수 있는 표준 예외 인터페이스가 존재할 수 있습니다. 또한 모든 예외가 따를 수 있는 알려진 계층 구조가 마련됩니다.
특정 인터페이스에 특정 베이스 클래스를 요구하는 것은 비파이썬적이라고 반론할 수도 있습니다. 그러나 예외의 특정한 경우에는 타당한 이유가 있습니다(이는 일반적으로 python-dev에서 동의된 사항입니다). 계층 구조를 요구하면 예외를 잡으려는 코드에 도움이 되며, except *: 대신 except BaseException:이라고 작성하여 모든 예외를 명시적으로 잡을 수 있게 됩니다. [1]
예외에 새로운 슈퍼클래스를 도입하면 예외 계층 구조를 조금 더 나은 방향으로 재배치할 기회도 얻을 수 있습니다. 현재 상태에서는 내장 네임스페이스의 모든 예외가 Exception을 상속합니다. 이는 애플리케이션의 예외 처리에서 자주 제외해야 하는 두 예외(KeyboardInterrupt 및 SystemExit)도 포함한다는 문제가 있습니다. 트레이스백 없이 인터프리터를 종료하는 기본 동작이 일반적으로 애플리케이션이 수행할 수 있는 동작보다 더 바람직합니다(>>> 프롬프트를 사용하는 Python의 대화형 명령 루프를 에뮬레이트하는 애플리케이션은 예외일 수 있습니다). 이 두 예외가 Exception 대신 공통 슈퍼클래스를 상속하도록 변경하면, 범위를 지나치게 넓히지 않고 전파되어야 하는 예외를 잡지 않는 except 절을 쉽게 작성할 수 있습니다.
이 PEP는 PEP 348을 위해 수행된 이전 작업을 기반으로 합니다.
공통 슈퍼클래스 요구
이 PEP는 BaseException이라는 새로운 예외를 도입할 것을 제안합니다. 이는 신식 클래스이며 단일 속성인 args를 가집니다. 아래에는 이 예외가 Python 3.0에서 작동할 방식의 코드가 나와 있습니다(Python 2.x에서 작동할 방식은 Transition Plan 섹션에서 다룹니다).:
class BaseException(object):
"""Superclass representing the base of the exception hierarchy.
Provides an 'args' attribute that contains all arguments passed
to the constructor. Suggested practice, though, is that only a
single string argument be passed to the constructor.
"""
def __init__(self, *args):
self.args = args
def __str__(self):
if len(self.args) == 1:
return str(self.args[0])
else:
return str(self.args)
def __repr__(self):
return "%s(*%s)" % (self.__class__.__name__, repr(self.args))
하위 호환성을 위해 args에 전달할 수 있는 값에는 어떠한 제한도 두지 않습니다. 그러나 실제로는 단일 문자열 인자만 사용해야 합니다. 이렇게 하면 예외의 문자열 표현이 사람이 읽을 수 있는 유용한 예외 메시지가 됩니다. 이것이 __str__ 메서드가 길이가 1인 args 값에 대해 특별히 처리하는 이유입니다. 프로그래밍 방식의 정보(예: 오류 코드 번호)를 포함하려면 서브클래스의 별도 속성으로 저장해야 합니다.
raise 문은 전달되는 모든 객체가 BaseException을 상속해야 하도록 변경됩니다. 이를 통해 모든 예외가 BaseException을 기반으로 하는 단일 계층 구조에 속하게 됩니다 [1]. 또한 BaseException에서 상속되는 기본 인터페이스가 보장됩니다. raise의 변경 사항은 Python 3.0부터 적용됩니다(아래의 Transition Plan을 참조하십시오).
BaseException이 예외 계층 구조의 루트가 되므로, 이제 Exception은 BaseException을 상속합니다.
예외 계층 구조 변경
이제 예외 계층 구조에 기본 루트가 있어 그 중요성이 더욱 커졌으므로, 기존 계층 구조를 변경해야 합니다. 현재 상태로는 오류를 나타내며 그리고 인터프리터를 종료하도록 허용해야 한다는 의미는 아닌 모든 예외를 포착하려면, 두 예외를 제외한 모든 예외를 except 절에서 명시적으로 지정하거나, 두 예외를 별도로 포착한 다음 다시 발생시키고 다른 모든 예외가 인자 없는 except 절로 흘러가도록 해야 합니다.:
except (KeyboardInterrupt, SystemExit):
raise
except:
...
이는 불필요하게 명시적입니다. 이 PEP는 KeyboardInterrupt와 SystemExit가 BaseException을 직접 상속하도록 이동할 것을 제안합니다.
- BaseException
|- KeyboardInterrupt
|- SystemExit
|- Exception
|- (all other current built-in exceptions)
이렇게 하면 Exception을 잡는 것이 더 합리적입니다. 오류를 나타내는 예외만 잡게 됩니다. 인터프리터가 종료되어야 함을 나타내는 예외는 잡히지 않으므로 상위로 전파되어 인터프리터가 종료되도록 허용됩니다.
사용자는 일반적으로 인터럽트 키(대개 Ctrl-C)를 누르면 애플리케이션이 종료되기를 기대하므로 KeyboardInterrupt가 이동되었습니다. 지나치게 포괄적인 except절을 사용하면 예상한 동작이 발생하지 않습니다.
SystemExit도 비슷한 이유로 이동되었습니다. sys.exit()이 호출될 때 예외가 발생하므로 인터프리터가 정상적으로 종료되도록 허용해야 합니다. 안타깝게도 지나치게 포괄적인 except절은 명시적으로 요청된 종료가 발생하지 못하도록 막을 수 있습니다.
대부분의 경우 사람들이 Exception을 잡도록 하려면, 프로그래머가 사용하려는 것이 Exception이어야 한다고 강력히 권고하도록 문서와 튜토리얼의 여러 부분을 업데이트해야 합니다. KeyboardInterrupt와 SystemExit는 거의 항상 상위로 전파되도록 허용해야 하므로, 단독 except절이나 BaseException을 직접 잡는 것은 권장하지 않아야 합니다.
전환 계획
Python에 대한 의미론적 변경이 제안되고 있으므로 전환 계획이 필요합니다. 목표는 2.x 코드에 원활한 전환을 제공하면서 Python 3.0에서 새로운 의미론을 사용하도록 하는 것입니다. 계획에 언급된 모든 사용 중단은 최초 사용 중단이 적용된 버전의 다음 버전부터 해당 의미론의 제거로 이어집니다.
다음은 2.x 계열에서 구현된 BaseException입니다.:
class BaseException(object):
"""Superclass representing the base of the exception hierarchy.
The __getitem__ method is provided for backwards-compatibility
and will be deprecated at some point. The 'message' attribute
is also deprecated.
"""
def __init__(self, *args):
self.args = args
def __str__(self):
return str(self.args[0]
if len(self.args) <= 1
else self.args)
def __repr__(self):
func_args = repr(self.args) if self.args else "()"
return self.__class__.__name__ + func_args
def __getitem__(self, index):
"""Index into arguments passed in during instantiation.
Provided for backwards-compatibility and will be
deprecated.
"""
return self.args[index]
def _get_message(self):
"""Method for 'message' property."""
warnings.warn("the 'message' attribute has been deprecated "
"since Python 2.6")
return self.args[0] if len(args) == 1 else ''
message = property(_get_message,
doc="access the 'message' attribute; "
"deprecated and provided only for "
"backwards-compatibility")
Python 2.9에서 기능을 사용 중단하는 것은 선택 사항입니다. 이는 2.x 계열의 마지막 버전이 될 예정인 Python 2.9가 3.0에 포함되지 않을 기능을 적극적으로 사용 중단할지는 현재로서는 알려져 있지 않기 때문입니다. 2.9와 3.0 사이에 큰 차이가 생겨 경고 측면에서 2.9가 지나치게 “시끄러워질” 수 있으므로, 2.9에서는 사용 중단 경고를 전혀 사용하지 않을 가능성도 있습니다. 따라서 Python 2.9에 대해 제안된 사용 중단 경고는 해당 버전의 개발이 시작될 때 다시 검토하여 여전히 필요한지 결정합니다.
- Python 2.5 [완료]
- 모든 표준 예외가 뉴 스타일 클래스가 됩니다 [완료]
- BaseException을 도입합니다 [완료]
- Exception, KeyboardInterrupt 및 SystemExit가 BaseException을 상속합니다 [완료]
- 문자열 예외 발생을 사용 중단합니다 [완료]
- Python 2.6 [완료]
- 문자열 예외 잡기를 사용 중단합니다 [완료]
message속성을 사용 중단합니다 (Retracted Ideas 참조) [완료]
- Python 2.7 [완료]
- BaseException을 상속하지 않는 예외 발생을 사용 중단합니다
- Python 3.0 [완료]
- 위에서 사용 중단된 모든 것을 제거합니다:
- 문자열 예외(발생과 처리 모두) [완료]
- 모든 예외는 BaseException을 상속해야 합니다 [완료]
__getitem__,message제거 [완료]
- 위에서 사용 중단된 모든 것을 제거합니다:
철회된 아이디어
Python 2.5에서 구현되었던 이 PEP의 이전 버전은 BaseException에 ‘message’ 속성을 포함했습니다. 그 목적은 BaseException이 단일 인자만 받도록 전환을 시작하는 것이었습니다. 이는 인터페이스를 강화하고, 임의의 정보를 예외와 함께 전달할 때 모든 것을 args에 욱여넣는 대신 서브클래스의 속성을 사용하도록 강제하기 위한 것이었습니다.
안타깝게도 PyCon 2007 스프린트 [2]에서 Python 3.0의 args 속성 제거를 구현하던 중, 이 전환이 특히 C 확장 모듈에 매우 고통스럽다는 것이 발견되었습니다. Python 2.6에서 message 속성을 사용 중단 예고하고(Python 2.7과 Python 3.0에서는 제거하고), Python 3.0에서는 BaseException의 다중 인자 지원을 제거하고 단일 인자만 받도록 하는 더 장기적인 전환 전략을 고려하는 편이 낫다고 결정되었습니다. 따라서 message의 도입과 args의 기존 사용 중단 예고는 철회되었습니다.
참고 자료
- 새 스타일 예외에 대한 이슈: python/cpython#41459
Copyright
This document has been placed in the public domain.