PEP 490 – C 수준에서 예외 연결하기
- Author:
- Victor Stinner <vstinner at python.org>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 25-Mar-2015
- Python-Version:
- 3.6
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python 수준에서 이미 하고 있는 것처럼 C 수준에서도 예외를 연결합니다.
근거
Python 3는 새로운 핵심 기능을 도입했습니다. 예외가 기본적으로 연결된다는 것입니다(PEP 3134).
예시:
try:
raise TypeError("err1")
except TypeError:
raise ValueError("err2")
출력:
Traceback (most recent call last):
File "test.py", line 2, in <module>
raise TypeError("err1")
TypeError: err1
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 4, in <module>
raise ValueError("err2")
ValueError: err2
Python 코드에서는 예외가 기본적으로 연결되지만, C로 작성된 확장에서는 그렇지 않습니다.
예외를 연결하기 위해 Python 3.4.3과 3.5에 새로운 비공개 함수 _PyErr_ChainExceptions()가 도입되었습니다. 현재는 예외를 연결하려면 이 함수를 명시적으로 호출해야 하며, 사용법도 간단하지 않습니다.
zipimport 모듈에서 이전 OSError를 새 ZipImportError 예외에 연결하기 위해 _PyErr_ChainExceptions()를 사용하는 예:
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
_PyErr_ChainExceptions(exc, val, tb);
이 PEP는 일관성을 유지하고 실패에 관한 더 많은 정보를 제공하여 디버깅에 도움이 되도록 C 수준에서도 예외를 자동으로 연결할 것을 제안합니다. 앞의 예는 다음과 같이 간단해집니다:
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
제안
예외를 연결하도록 PyErr_*() 함수 수정하기
예외를 발생시키는 Python C API의 C 함수가 예외를 자동으로 연결하도록 수정합니다. PyErr_SetString(), PyErr_Format(), PyErr_SetNone() 등을 수정합니다.
예외를 연결하지 않도록 함수 수정하기
새 예외가 이전 예외의 정보나 그보다 더 많은 정보를 담고 있을 때, 특히 두 예외의 타입이 같을 때는 이전 예외를 유지하는 것이 항상 유용하지는 않습니다.
int(str)에서 발생하는 쓸모없는 예외 연결의 예:
TypeError: a bytes-like object is required, not 'type'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a number, not 'type'
새 TypeError 예외는 이전 예외보다 더 많은 정보를 담고 있습니다. 이전 예외는 숨겨야 합니다.
현재 예외를 새 예외와 연결하지 않으려면, 새 예외를 발생시키기 전에 PyErr_Clear() 함수를 호출하여 현재 예외를 지울 수 있습니다.
예외를 연결하도록 함수 수정하기
일부 함수는 현재 예외를 저장했다가 복원합니다. 새 예외가 발생하면, 현재는 함수에 따라 그 예외를 sys.stderr에 표시하거나 무시합니다. 이러한 함수 중 일부는 대신 예외를 연결하도록 수정해야 합니다.
새 예외를 무시하는 함수의 예:
ptrace_enter_call(): 예외를 무시합니다subprocess_fork_exec(): enable_gc()가 발생시킨 예외를 무시합니다_thread모듈의t_bootstrap(): 부트스트랩 함수를sys.stderr에 표시하려다 발생한 예외를 무시합니다PyDict_GetItem(),_PyDict_GetItem_KnownHash(): 딕셔너리에서 키를 찾다가 발생한 예외를 무시합니다_PyErr_TrySetFromCause(): 예외를 무시합니다PyFrame_LocalsToFast():dict_to_map()에서 발생한 예외를 무시합니다_PyObject_Dump(): 예외를 무시합니다._PyObject_Dump()는 디버깅 용도, 즉 실행 중인 프로세스를 검사하는 데 사용되므로 Python 상태를 수정해서는 안 됩니다.Py_ReprLeave(): “예외를 보고할 방법이 없기 때문에” 예외를 무시합니다type_dealloc():remove_all_subclasses()에서 발생한 예외를 무시합니다PyObject_ClearWeakRefs(): 예외를 무시합니까?call_exc_trace(),call_trace_protected(): 예외를 무시합니다remove_importlib_frames(): 예외를 무시합니다do_mktuple(), 예를 들어Py_BuildValue()가 사용하는 도우미 함수: 예외를 무시합니까?flush_io(): 예외를 무시합니다sys_write(),sys_format(): 예외를 무시합니다_PyTraceback_Add(): 예외를 무시합니다PyTraceBack_Print(): 예외를 무시합니다
새 예외를 sys.stderr에 표시하는 함수의 예:
atexit_callfuncs():PyErr_Display()로 예외를 표시하고 마지막 예외를 반환합니다. 이 함수는 여러 콜백에 대해 함수 호출을 수행하며 마지막 예외만 반환합니다sock_dealloc():PyErr_WriteUnraisable()로ResourceWarning예외를 기록합니다slot_tp_del():PyErr_WriteUnraisable()로 예외를 표시합니다_PyGen_Finalize():PyErr_WriteUnraisable()로gen_close()예외를 표시합니다slot_tp_finalize():__del__()메서드에서 발생한 예외를PyErr_WriteUnraisable()로 표시합니다PyErr_GivenExceptionMatches():PyType_IsSubtype()에서 발생한 예외를PyErr_WriteUnraisable()로 표시합니다
하위 호환성
예외 연결의 부작용은 예외가 트레이스백 객체를 저장하고, 트레이스백 객체는 프레임 객체를 저장하며, 프레임 객체는 지역 변수를 저장한다는 것입니다. 지역 변수는 예외에 의해 계속 살아 있게 됩니다. 흔한 문제는 지역 변수와 예외 사이의 참조 순환입니다. 예외가 지역 변수에 저장되고, 그 프레임이 간접적으로 예외에 저장됩니다. 이 순환은 예외를 저장하는 애플리케이션에만 영향을 줍니다.
이제 Python 3.5에 도입된 새로운 traceback.TracebackException 객체로 참조 순환을 해결할 수 있습니다. 이 객체는 지역 변수를 저장하지 않고도 전체 텍스트 트레이스백을 포맷하는 데 필요한 정보를 저장합니다.
asyncio는 참조 순환 문제의 영향을 받습니다. 이 모듈은 Python 3.3용 버전을 릴리스하기 위해 Python 표준 라이브러리 밖에서도 유지 관리됩니다. 참조 순환 문제를 해결하기 위해 traceback.TracebackException이 비공개 asyncio 모듈에 백포트될 수도 있습니다.
대안
변경하지 않음
예외를 수동으로 연결하는 데는 새로운 비공개 _PyErr_ChainExceptions() 함수로 충분합니다.
예외는 타당한 경우에만 명시적으로 연결될 것입니다.
예외 연결을 위한 새 헬퍼
PyErr_SetString() 같은 함수는 예외를 자동으로 연결하지 않습니다. _PyErr_ChainExceptions()를 더 쉽게 사용할 수 있도록 새 비공개 함수를 추가합니다:
_PyErr_SetStringChain(exc_type, message)_PyErr_FormatChain(exc_type, format, ...)_PyErr_SetNoneChain(exc_type)_PyErr_SetObjectChain(exc_type, exc_value)
_PyErr_SetKeyError(key)나 PyErr_SetImportError(message, name, path) 같이 특정 예외를 발생시키는 헬퍼 함수는 예외를 연결하지 않습니다. 이러한 헬퍼 함수로 예외를 연결하려면 범용 함수인 _PyErr_ChainExceptions(exc_type, exc_value, exc_tb)를 사용해야 합니다.
부록
PEP
Python C API
헤더 파일 Include/pyerror.h는 예외 관련 함수를 선언합니다.
예외를 발생시키는 함수:
PyErr_SetNone(exc_type)PyErr_SetObject(exc_type, exc_value)PyErr_SetString(exc_type, message)PyErr_Format(exc, format, ...)
특정 예외를 발생시키는 헬퍼:
PyErr_BadArgument()PyErr_BadInternalCall()PyErr_NoMemory()PyErr_SetFromErrno(exc)PyErr_SetFromWindowsErr(err)PyErr_SetImportError(message, name, path)_PyErr_SetKeyError(key)_PyErr_TrySetFromCause(prefix_format, ...)
현재 예외 관리:
PyErr_Clear():except: pass처럼 현재 예외를 지웁니다PyErr_Fetch(exc_type, exc_value, exc_tb)PyErr_Restore(exc_type, exc_value, exc_tb)PyErr_GetExcInfo(exc_type, exc_value, exc_tb)PyErr_SetExcInfo(exc_type, exc_value, exc_tb)
예외를 처리하는 그 밖의 함수:
PyErr_ExceptionMatches(exc):except exc: ...를 구현하기 위한 검사PyErr_GivenExceptionMatches(exc1, exc2)PyErr_NormalizeException(exc_type, exc_value, exc_tb)_PyErr_ChainExceptions(exc_type, exc_value, exc_tb)
Python 이슈
예외 연쇄:
- 이슈 #23763: C에서 예외 연쇄
- 이슈 #23696: zipimport: ImportError를 OSError에 연쇄
- 이슈 #21715: C 수준에서 예외 연쇄:
_PyErr_ChainExceptions()추가 - 이슈 #18488: sqlite: step() 메서드 호출이 실패하면 사용자 함수의 finalize() 메서드가 예외가 설정된 상태로 호출될 수 있음
- 이슈 #23781: 2.7에 비공개 _PyErr_ReplaceException() 추가
- 이슈 #23782: _PyTraceback_Add의 누수
예외 유실을 막기 위한 변경 사항:
거부
이 PEP는 2017년 9월 12일에 Victor Stinner에 의해 거부되었습니다. python-dev 논의에서 C 예외를 기본적으로 연쇄하지 않고, 대신 타당한 곳에서만 명시적으로 연쇄하기로 결정되었습니다.
Copyright
This document has been placed in the public domain.