Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 490 – C 수준에서 예외 연결하기

Author:
Victor Stinner <vstinner at python.org>
Status:
Rejected
Type:
Standards Track
Created:
25-Mar-2015
Python-Version:
3.6

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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

  • PEP 3134 – 예외 연결과 내장 트레이스백 (Python 3.0): 예외에 새로운 __context__ 및 __cause__ 속성 추가
  • PEP 415 – 예외 속성을 이용한 컨텍스트 억제 구현 (Python 3.3): raise exc from None
  • PEP 409 – 예외 컨텍스트 억제 (PEP 415로 대체됨)

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 이슈

예외 연쇄:

예외 유실을 막기 위한 변경 사항:

거부

이 PEP는 2017년 9월 12일에 Victor Stinner에 의해 거부되었습니다. python-dev 논의에서 C 예외를 기본적으로 연쇄하지 않고, 대신 타당한 곳에서만 명시적으로 연쇄하기로 결정되었습니다.