PEP 317 – 암시적 예외 인스턴스화 제거
- Author:
- Steven Taschuk <staschuk at telusplanet.net>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 06-May-2003
- Python-Version:
- 2.4
- Post-History:
- 09-Jun-2003
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
“명확성을 위해 새 코드에서는raise class(argument, ...)형식이 권장됩니다(즉, 생성자를 명시적으로 호출하라는 뜻입니다).”—Guido van Rossum, 1997년 [1]
이 PEP는 예외를 암시적으로 인스턴스화하는 형태의 raise 문을 공식적으로 지원 중단하고 궁극적으로 제거할 것을 제안합니다. 예를 들어, 다음과 같은 문장은
raise HullBreachError
raise KitchenError, 'all out of baked beans'
이 제안에 따라 다음과 같은 동의어로 대체되어야 합니다
raise HullBreachError()
raise KitchenError('all out of baked beans')
후자의 문장들은 이미 합법적이며, 이 PEP가 그 의미를 변경하지 않는다는 점에 유의하십시오.
이러한 형태의 raise를 제거하면 문자열 예외를 사용할 수 없게 됩니다. 따라서 이 PEP는 문자열 예외의 공식적인 지원 중단과 궁극적인 제거도 제안합니다.
이 제안을 채택하면 하위 호환성이 깨집니다. 제안된 구현 일정에 따르면, Python 2.4에서는 궁극적으로 잘못된 것이 될 raise의 사용에 대한 경고를 도입할 것이며, Python 3.0에서는 이를 완전히 제거할 것입니다. (PEP 5의 지침을 준수하기 위해 이 전환 기간—2.4에서 3.0까지—은 최소 1년 이상일 것으로 가정합니다.)
동기
문자열 예외
문자열 예외를 제거하는 것은 표준 예외 타입이 클래스로 변경된 Python 1.5 시절부터 적어도 의도되어 왔으므로, 논란의 여지가 없을 것으로 가정합니다 [1].
참고로 말하면, 두 종류의 예외가 존재하는 것은 아무런 보상 없이 언어를 복잡하게 만들기 때문에 문자열 예외는 제거되어야 합니다. 인스턴스 예외가 더 우수한데, 예를 들어
- 클래스-인스턴스 관계가 예외 타입과 값 사이의 관계를 더 자연스럽게 표현합니다.
- 상위 클래스-하위 클래스 관계를 이용하여 자연스럽게 조직할 수 있으며,
- 오류 보고 동작을 캡슐화할 수 있습니다(예를 들어).
암묵적 인스턴스화
표준 예외를 클래스로 변경하는 것에 관한 Guido의 1997년 에세이 [1] 는 raise가 왜 암묵적으로 인스턴스화할 수 있는지를 분명히 밝히고 있습니다:
“raise 문은 명시적인 인스턴스화 없이 클래스 예외를 발생시킬 수 있도록 확장되었습니다. raise 문의 “호환성 형식”이라고 불리는 다음 형식들[…] 호환성 형식을 도입한 동기는 표준 예외를 발생시키던 예전 코드와의 하위 호환성을 허용하기 위함이었습니다.”
예를 들어, 문자열 예외 구문을 사용한 1.5 이전 버전의 코드가
raise TypeError, 'not an int'
TypeError가 문자열이었던 버전의 파이썬과 클래스였던 버전의 파이썬 양쪽 모두에서 동작하기를 바랐습니다.
그러한 고려 사항이 존재하지 않는 경우 – 즉, 코드가 지원해야 하는 소프트웨어의 어떤 버전에서도 원하는 예외 타입이 문자열이 아닌 경우 – 암묵적으로 인스턴스화할 뚜렷한 이유가 없으며, 그렇게 하지 않는 편이 더 명확합니다. 예를 들면:
- 다음 코드에서
try: raise MyError, raised except MyError, caught: pass
raise문과except문 사이의 구문적 유사성은raised와caught가 같은 객체를 가리킨다는 것을 강하게 암시합니다. 문자열 예외의 경우 실제로 그러하지만, 인스턴스 예외의 경우는 그렇지 않습니다. - 인스턴스화가 암묵적으로 이루어지면, 예를 들어 그것이 예외가 발생할 때 일어나는지 아니면 잡힐 때 일어나는지 등, 언제 발생하는지가 명확하지 않습니다. 실제로는
raise시점에 일어나므로, 코드는 그것을 명시적으로 나타내야 합니다.C API 수준에서는 예외가 인스턴스화되지 않고도 “발생”하고 “처리”될 수 있음에 유의하십시오. 이는 예를 들어
PyIter_Next에서 최적화로 사용됩니다. 하지만 파이썬에서는 그러한 최적화가 가능하지 않으며 가능해서도 안 됩니다.) - 다음과 같이 인자 없이 암묵적으로 인스턴스화하는
raise문은raise MyError
말 그대로의 동작을 하지 않습니다: 명명된 객체를 발생시키지 않습니다.
- 다음의 동등성은
raise MyError raise MyError()
클래스와 인스턴스를 혼동시켜 초보자에게 혼란의 원인이 될 수 있습니다. (더욱이 인터프리터가 새 스타일 클래스와 그러한 클래스의 인스턴스를 구별할 수 있는지도 분명하지 않으므로, 암묵적 인스턴스화는 향후 예외를 새 스타일 객체로 만들려는 어떤 계획에도 장애물이 될 수 있습니다.)
요컨대 암묵적 인스턴스화는 하위 호환성 외에는 이점이 없으므로, 그것이 호환성을 보장하기 위해 존재하는 대상인 문자열 예외와 함께 단계적으로 폐지되어야 합니다.
명세
raise_stmt [3] 의 문법은 다음에서
raise_stmt ::= "raise" [expression ["," expression ["," expression]]]
다음으로 변경됩니다
raise_stmt ::= "raise" [expression ["," expression]]
표현식이 없으면 raise 문은 현재와 마찬가지로 동작합니다: 현재 스코프에서 활성 상태였던 마지막 예외를 다시 발생시키며, 현재 스코프에서 활성 상태인 예외가 없으면 이를 알리는 TypeError가 발생합니다.
그렇지 않으면 첫 번째 표현식이 평가되어 발생된 객체를 생성합니다. 그런 다음 두 번째 표현식이 있으면 평가되어 대체된 트레이스백을 생성합니다. 두 번째 표현식이 없으면 대체된 트레이스백은 None입니다.
발생시킨 객체는 인스턴스여야 합니다. 그 인스턴스의 클래스가 예외 타입이 되고, 인스턴스 자체가 예외 값이 됩니다. 발생시킨 객체가 인스턴스가 아닌 경우 – 예를 들어 클래스나 문자열인 경우 – TypeError가 발생합니다.
대체된 트레이스백이 None이 아니면, 그것은 트레이스백 객체여야 하며, 예외가 발생한 위치로서 현재 위치 대신 대체됩니다. 그것이 트레이스백 객체도 None도 아니면, TypeError가 발생합니다.
하위 호환성
이행 계획
Future 문
미래 문인 PEP 236에 따라:
from __future__ import raise_with_two_args
raise 문의 구문과 의미는 위에서 설명한 바와 같습니다. 이 future 기능은 Python 2.4에서 등장할 예정이며, 그 효과는 Python 3.0에서 표준이 되는 것입니다.
아래 예제들이 보여주듯이, 이 future 문은 raise에 대체된 트레이스백 인자를 사용하는 코드에만 필요하며, 단순한 예외 발생에는 필요하지 않습니다.
경고
제안된 변경 사항에 따라 부정확해질 raise의 사용을 지적하기 위해, 모두 DeprecationWarning 범주에 속하는 세 가지 새로운 경고가 발행될 예정입니다.
첫 번째 경고는 첫 번째 표현식이 문자열로 평가되는 raise 문이 실행될 때 발행됩니다. 이 경고의 메시지는 다음과 같습니다:
raising strings will be impossible in the future
두 번째 경고는 첫 번째 표현식이 클래스로 평가되는 raise 문이 실행될 때 발생합니다. 이 경고의 메시지는 다음과 같습니다.:
raising classes will be impossible in the future
세 번째 경고는 표현식 세 개를 가진 raise 문이 컴파일될 때 발생합니다. (실행될 때가 아니라는 점에 유의하십시오. 이 경고가 예고하는 SyntaxError가 컴파일 시점에 발생하기 때문에 이 점이 중요합니다.) 이 경고의 메시지는 다음과 같습니다.:
raising with three arguments will be impossible in the future
이 경고들은 Python 2.4에서 등장하며, 이를 유발하는 조건이 단순한 오류가 되는 Python 3.0에서는 사라질 예정입니다.
예제
암묵적 인스턴스화를 사용하는 코드
다음과 같은 코드는
class MyError(Exception):
pass
raise MyError, 'spam'
raise 문이 실행될 때 경고를 발생시킵니다. raise 문은 명시적으로 인스턴스화하도록 변경해야 합니다.:
raise MyError('spam')
문자열 예외를 사용하는 코드
다음과 같은 코드는
MyError = 'spam'
raise MyError, 'eggs'
raise 문이 실행될 때 경고를 발생시킵니다. 예외 타입은 클래스로 변경해야 합니다.:
class MyError(Exception):
pass
그리고 앞의 예제에서와 마찬가지로, raise 문은 명시적으로 인스턴스화하도록 변경해야 합니다.
raise MyError('eggs')
트레이스백 객체를 제공하는 코드
다음과 같은 코드는
raise MyError, 'spam', mytraceback
컴파일 시 경고를 발생시킵니다. 해당 문은 다음과 같이 변경되어야 합니다
raise MyError('spam'), mytraceback
그리고 다음 future 문이
from __future__ import raise_with_two_args
모듈 상단에 추가되어야 합니다. 이 future 문을 추가하면 나머지 두 경고도 오류로 바뀌므로, 앞선 예제들에서 설명한 변경 사항도 함께 적용해야 함에 유의하십시오.
다음과 같은 특수한 경우
raise sys.exc_type, sys.exc_info, sys.exc_traceback
(이전 예외를 다시 발생시키기 위한 용도)는 단순히 다음과 같이 변경되어야 합니다
raise
계획의 실패
문자열을 발생시키거나 암묵적으로 인스턴스화하는 raise 문이 이 PEP의 단계적 도입 기간 동안 프로덕션이나 테스트 중에 실행되지 않을 수 있습니다. 이 경우 어떠한 경고도 발생시키지 않다가, 어느 날 Python 3.0 또는 그 이후 버전에서 갑자기 실패하게 됩니다. (여기서 실패란 의도한 예외가 아니라, raise의 인자에 불평하는 TypeError라는 잘못된 예외가 발생하는 것을 말합니다.)
단계적 도입 기간을 늘림으로써 이러한 경우를 더 드물게 만들 수는 있지만, 모든 raise 문에 대해 컴파일 시점에 경고를 발생시키지 않는 한 이를 완전히 불가능하게 만들 수는 없습니다.
거부
이 PEP가 수용된다면 기존 Python 코드의 거의 전부를 검토하고 아마도 수정해야 할 것이며, 명시적 인스턴스화를 지지하는 위의 모든 논거를 받아들인다 하더라도, 명료성의 개선은 그러한 수정 작업의 비용과 그로 인해 새로운 버그가 도입될 위험을 정당화하기에는 너무 미미합니다.
따라서 이 제안은 거부되었습니다 [6].
문자열 예외는 이 제안과 무관하게 제거될 예정임에 유의하십시오. 거부된 것은 암묵적 예외 인스턴스화의 제거입니다.
논의 요약
응답자 중 소수만이 이 제안에 찬성했으며, 대다수의 응답은 그러한 마이그레이션이 예상되는 이익에 비해 지나치게 큰 비용을 초래할 것이라는 의견이었습니다. 위에서 언급했듯이, 이 점만으로도 이 PEP를 거부하기에 충분합니다.
새 스타일 예외
암묵적 인스턴스화는 새 스타일 클래스의 인스턴스를 예외로 사용할 수 있게 하려는 향후 계획과 충돌할 수 있습니다. 암묵적으로 인스턴스화할지 결정하려면 raise 메커니즘이 첫 번째 인자가 클래스인지 인스턴스인지를 판별해야 하지만, 새 스타일 클래스에서는 명확하고 확고한 구분이 존재하지 않습니다.
이 제안 하에서는 예외가 이미 인스턴스화된 상태이기 때문에 이 문제가 발생하지 않습니다. 그러나 그럴듯한 대안이 두 가지 있습니다:
- 예외 타입이
Exception의 서브클래스여야 한다고 요구하고, 다음 조건을 만족할 때에만 암묵적으로 인스턴스화합니다issubclass(firstarg, Exception)
- 다음 조건을 만족할 때에만 암묵적으로 인스턴스화합니다
isinstance(firstarg, type)
따라서 암묵적 인스턴스화를 완전히 없애는 것이 이 문제를 해결하는 데 필수적이지는 않습니다.
명시적 인스턴스화의 볼썽사나움
일부 응답자는 명시적으로 인스턴스화하는 구문이 더 볼썽사납다고 느꼈으며, 특히 예외 생성자에 인자를 전달하지 않는 경우 그러하다고 밝혔습니다:
raise TypeError()
이 문제는 예외 인스턴스 자체는 관심 대상이 아니고, 즉 예외 타입만이 유일한 관심사일 때 특히 두드러집니다:
try:
# ... deeply nested search loop ...
raise Found
except Found:
# ...
이러한 경우 raise와 except사이의 대칭성이 코드의 의도를 더 잘 표현할 수 있습니다.
Guido는 암묵적으로 인스턴스화하는 구문이 단일 인자를 사용하는 경우에도 문장 부호가 더 적기 때문에 “약간 더 예쁘다”고 의견을 밝혔습니다.
경고의 성능 저하
apply()를 폐지하면서 얻은 경험에 따르면, 경고 프레임워크의 사용은 상당한 성능 저하를 초래할 수 있습니다.
명시적으로 인스턴스를 생성하는 코드는 영향을 받지 않는데, 경고를 발생시킬지 여부를 판단하는 데 필요한 런타임 검사가 애초에 암묵적으로 인스턴스를 생성할지 여부를 판단하는 데 필요한 검사와 정확히 동일하기 때문입니다. 즉, 그러한 문장들은 이미 이러한 검사의 비용을 감수하고 있습니다.
암묵적으로 인스턴스를 생성하는 코드는 큰 비용을 초래하는데, 시간 측정 실험에 따르면 경고를 발생시키는 것(억제되든 그렇지 않든)은 단순히 인스턴스를 생성하고, 예외를 발생시키고, 이를 잡는 것보다 약 다섯 배 더 많은 시간이 소요됩니다.
이러한 손실은 raise문이 성능이 중요한 실행 경로에 있는 경우가 드물다는 사실에 의해 완화됩니다.
트레이스백 인자
이 제안이 현재 상태로는, 모든 2.x 버전의 Python에서 raise에 트레이스백 인자를 편리하게 사용하는 것이 불가능할 것입니다.
2.4 미만 버전과의 호환성을 위해서는 세 개의 인자를 갖는 형식을 사용해야 하지만, 이 형식은 2.4 이상 버전에서 경고를 발생시키게 됩니다. 이러한 경고는 억제할 수 있지만, 해당 경고의 유형이 컴파일 시점에 발생하는 것이기 때문에 억제하기가 까다롭습니다.
이 PEP가 여전히 검토 중이었다면, 단계적 도입 기간을 연장함으로써 이 반론에 대응했을 것입니다. 예를 들어, 경고는 먼저 3.0에서 발생시키고, 이후 어느 릴리스에서 오류로 전환할 수 있었을 것입니다.
참고 자료
Copyright
This document has been placed in the public domain.