This commit is contained in:
Victor Stinner 2015-03-26 13:14:31 +01:00
parent d1d9f7a914
commit 32ad4c5b64
1 changed files with 186 additions and 148 deletions

View File

@ -19,8 +19,8 @@ Chain exceptions at C level, as already done at Python level.
Rationale
=========
Python 3 introduced a new killer feature: exceptions are chained by default,
PEP 3134.
Python 3 introduced a new killer feature: exceptions are chained by
default, PEP 3134.
Example::
@ -43,9 +43,27 @@ Output::
raise ValueError("err2")
ValueError: err2
Exceptions are chained by default in Python code, but not in extensions written
in C. This PEP proposes to also chain exceptions automatically at C level to
stay consistent.
Exceptions are chained by default in Python code, but not in
extensions written in C.
A new private ``_PyErr_ChainExceptions()`` function was introduced in
Python 3.4.3 and 3.5 to chain exceptions. Currently, it must be called
explicitly to chain exceptions and its usage is not trivial.
Example of ``_PyErr_ChainExceptions()`` usage from the ``zipimport``
module to chain the previous ``OSError`` to a new ``ZipImportError``
exception::
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
_PyErr_ChainExceptions(exc, val, tb);
This PEP proposes to also chain exceptions automatically at C level to
stay consistent and give more information on failures to help
debugging. The previous example becomes simply::
PyErr_Format(ZipImportError, "can't open Zip file: %R", archive);
Proposal
@ -54,141 +72,110 @@ Proposal
Modify PyErr_*() functions to chain exceptions
----------------------------------------------
Modify C functions raising exceptions of the Python C API to automatically
chain exceptions.
Modify C functions raising exceptions of the Python C API to
automatically chain exceptions: modify ``PyErr_SetString()``,
``PyErr_Format()``, ``PyErr_SetNone()``, etc.
Modify functions to not chain exceptions
----------------------------------------
To remove the previous exception, PyErr_Clear() can be called before
PyErr_SetString() (or other functions raising exceptions).
Keeping the previous exception is not always interesting when the new
exception contains information of the previous exception or even more
information, especially when the two exceptions have the same type.
Keeping the original exception is not always interesting.
Example of an useless exception chain with ``int(str)``::
Example of Python code (``os._Environ.__getitem__``) suppressing the context::
try:
value = self._data[self.encodekey(key)]
except KeyError:
# raise KeyError with the original key value
raise KeyError(key) from None
Example of an exception chain raised by the struct module with exceptions
chained by default::
$ python3.5 -c 'import struct; struct.pack("b", 2**100)'
OverflowError: Python int too large to convert to C long
TypeError: a bytes-like object is required, not 'type'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<string>", line 1, in <module>
struct.error: argument out of range
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a number, not 'type'
The ``OverflowError`` exception is not very useful, it can be hidden.
The new ``TypeError`` exception contains more information than the
previous exception. The previous exception should be hidden.
The ``PyErr_Clear()`` function can be called to clear the current
exception before raising a new exception, to not chain the current
exception with a new exception.
Modify functions to chain exceptions
------------------------------------
Some functions save and then restore the current exception. If a new exception
is raised, the exception is displayed or ignored depending on the function.
Maybe some of these functions should be modified to chain exceptions instead?
Examples of function displaying the new exception:
* atexit_callfuncs(): display exceptions with PyErr_Display() and return the
latest exception
* sock_dealloc(): log warning exception with PyErr_WriteUnraisable()
* slot_tp_del(): display exception with PyErr_WriteUnraisable()
* _PyGen_Finalize(): display gen_close() exception with PyErr_WriteUnraisable()
* slot_tp_finalize(): display exception raised by __del__() with
PyErr_WriteUnraisable()
* PyErr_GivenExceptionMatches(): display exception raised by PyType_IsSubtype()
with PyErr_WriteUnraisable()
Some functions save and then restore the current exception. If a new
exception is raised, the exception is currently displayed into
sys.stderr or ignored depending on the function. Some of these
functions should be modified to chain exceptions instead.
Examples of function ignoring the new exception(s):
* ptrace_enter_call(): ignore exception
* subprocess_fork_exec(): ignore exception raised by enable_gc()
* t_bootstrap() of the _thread module: ignore raised exception when trying to
display the bootstrap function to sys.stderr
* PyDict_GetItem(), _PyDict_GetItem_KnownHash(): ignore exception raiesd when
looking for a key
* _PyErr_TrySetFromCause(): ignore ecxeption
* PyFrame_LocalsToFast(): ignore exception raised by dict_to_map()
* _PyObject_Dump(): ignore exception. _PyObject_Dump() is used to debug, to
inspect a running process, it should not modify the Python state.
* Py_ReprLeave(): ignore exception "because there is no way to report them"
* type_dealloc(): ignore exception raised by remove_all_subclasses()
* PyObject_ClearWeakRefs(): ignore exceptions?
* call_exc_trace(), call_trace_protected(): ignore exception
* remove_importlib_frames(): ignore exception
* do_mktuple(), helped used by Py_BuildValue() for example: ignore exception?
* flush_io(): ignore exception
* sys_write(), sys_format(): ignore exception
* _PyTraceback_Add(): ignore exception
* PyTraceBack_Print(): ignore exception
* ``ptrace_enter_call()``: ignore exception
* ``subprocess_fork_exec()``: ignore exception raised by enable_gc()
* ``t_bootstrap()`` of the ``_thread`` module: ignore exception raised
by trying to display the bootstrap function to ``sys.stderr``
* ``PyDict_GetItem()``, ``_PyDict_GetItem_KnownHash()``: ignore
exception raised by looking for a key in the dictionary
* ``_PyErr_TrySetFromCause()``: ignore exception
* ``PyFrame_LocalsToFast()``: ignore exception raised by
``dict_to_map()``
* ``_PyObject_Dump()``: ignore exception. ``_PyObject_Dump()`` is used
to debug, to inspect a running process, it should not modify the
Python state.
* ``Py_ReprLeave()``: ignore exception "because there is no way to
report them"
* ``type_dealloc()``: ignore exception raised by
``remove_all_subclasses()``
* ``PyObject_ClearWeakRefs()``: ignore exception?
* ``call_exc_trace()``, ``call_trace_protected()``: ignore exception
* ``remove_importlib_frames()``: ignore exception
* ``do_mktuple()``, helper used by ``Py_BuildValue()`` for example:
ignore exception?
* ``flush_io()``: ignore exception
* ``sys_write()``, ``sys_format()``: ignore exception
* ``_PyTraceback_Add()``: ignore exception
* ``PyTraceBack_Print()``: ignore exception
Examples of function displaying the new exception to ``sys.stderr``:
Python C API
------------
Include/pyerror.h declares functions related to exceptions.
Functions raising exceptions:
* PyErr_SetNone(exc_type)
* PyErr_SetObject(exc_type, exc_value)
* PyErr_SetString(exc_type, message)
* PyErr_Format(exc, format, ...)
Helpers to raise specific exceptions:
* PyErr_BadArgument()
* PyErr_BadInternalCall()
* PyErr_NoMemory()
* PyErr_SetFromErrno(exc)
* PyErr_SetFromWindowsErr(err)
* PyErr_SetImportError(message, name, path)
* _PyErr_SetKeyError(key)
* _PyErr_TrySetFromCause(prefix_format, ...)
Manage the current exception:
* PyErr_Clear(): clear the current exception, like "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)
Others function to handle exceptions:
* PyErr_ExceptionMatches(exc): check to implement "except exc: ..."
* PyErr_GivenExceptionMatches(exc1, exc2)
* PyErr_NormalizeException(exc_type, exc_value, exc_tb)
* _PyErr_ChainExceptions(exc_type, exc_value, exc_tb)
* ``atexit_callfuncs()``: display exceptions with
``PyErr_Display()`` and return the latest exception, the function
calls multiple callbacks and only returns the latest exception
* ``sock_dealloc()``: log the ``ResourceWarning`` exception with
``PyErr_WriteUnraisable()``
* ``slot_tp_del()``: display exception with
``PyErr_WriteUnraisable()``
* ``_PyGen_Finalize()``: display ``gen_close()`` exception with
``PyErr_WriteUnraisable()``
* ``slot_tp_finalize()``: display exception raised by the
``__del__()`` method with ``PyErr_WriteUnraisable()``
* ``PyErr_GivenExceptionMatches()``: display exception raised by
``PyType_IsSubtype()`` with ``PyErr_WriteUnraisable()``
Backward compatibility
======================
A side effect of chaining exceptions is that exceptions store traceback
objects. Traceback objects store frame objects which store local variables.
Local variables are kept alive by exceptions. A common issue is a reference
cycle between local variables and exceptions. The cycle only impacts
applications storing exceptions.
A side effect of chaining exceptions is that exceptions store
traceback objects which store frame objects which store local
variables. Local variables are kept alive by exceptions. A common
issue is a reference cycle between local variables and exceptions: an
exception is stored in a local variable and the frame indirectly
stored in the exception. The cycle only impacts applications storing
exceptions.
The reference cycle can now be fixed with the new traceback.TracebackException
object introduced in Python 3.5 which store informations required to format a
full textual traceback without storing local variables.
The reference cycle can now be fixed with the new
``traceback.TracebackException`` object introduced in Python 3.5. It
stores informations required to format a full textual traceback without
storing local variables.
The asyncio is impacted by the reference cycle issue, but this module is also
maintained outside Python standard library to release a version for Python 3.3.
traceback.TracebackException will probably be backward in a private asyncio
module to fix the issue.
The ``asyncio`` is impacted by the reference cycle issue. This module
is also maintained outside Python standard library to release a
version for Python 3.3. ``traceback.TracebackException`` will maybe
be backported in a private ``asyncio`` module to fix reference cycle
issues.
Alternatives
@ -197,54 +184,34 @@ Alternatives
No change
---------
Python 3.5 introduces a new private ``_PyErr_ChainExceptions()`` function which
is enough to chain manually exceptions.
A new private ``_PyErr_ChainExceptions()`` function is enough to chain
manually exceptions.
Exceptions will only be chained explicitly where it makes sense.
New helpers to chain exceptions
-------------------------------
Functions like ``PyErr_SetString()`` don't chain automatically exceptions. To
make usage of ``_PyErr_ChainExceptions()`` easier, new functions are added:
Functions like ``PyErr_SetString()`` don't chain automatically
exceptions. To make the usage of ``_PyErr_ChainExceptions()`` easier,
new private functions are added:
* PyErr_SetStringChain(exc_type, message)
* PyErr_FormatChaine(exc_type, format, ...)
* PyErr_SetNoneChain(exc_type)
* PyErr_SetObjectChain(exc_type, exc_value)
* ``_PyErr_SetStringChain(exc_type, message)``
* ``_PyErr_FormatChain(exc_type, format, ...)``
* ``_PyErr_SetNoneChain(exc_type)``
* ``_PyErr_SetObjectChain(exc_type, exc_value)``
Helper functions like _PyErr_SetKeyError(key) or PyErr_SetImportError(message,
name, path) don't chain exceptions. The generic
_PyErr_ChainExceptions(exc_type, exc_value, exc_tb) should be used.
Helper functions to raise specific exceptions like
``_PyErr_SetKeyError(key)`` or ``PyErr_SetImportError(message, name,
path)`` don't chain exceptions. The generic
``_PyErr_ChainExceptions(exc_type, exc_value, exc_tb)`` should be used
to chain exceptions with these helper functions.
Appendix
========
Python Issues
-------------
Chain exceptions:
* `Issue #23763: Chain exceptions in C
<http://bugs.python.org/issue23763>`_
* `Issue #23696: zipimport: chain ImportError to OSError
<http://bugs.python.org/issue23696>`_
* `Issue #21715: Chaining exceptions at C level
<http://bugs.python.org/issue21715>`_: added _PyErr_ChainExceptions()
* `Issue #18488: sqlite: finalize() method of user function may be called with
an exception set if a call to step() method failed
<http://bugs.python.org/issue18488>`_
* `Issue #23781: Add private _PyErr_ReplaceException() in 2.7
<http://bugs.python.org/issue23781>`_
Changes preventing loosing exceptions:
* `Issue #23571: Raise SystemError if a function returns a result with an
exception set <http://bugs.python.org/issue23571>`_
* `Issue #18408: Fixes crashes found by pyfailmalloc
<http://bugs.python.org/issue18408>`_
PEPs
----
@ -255,7 +222,78 @@ PEPs
<https://www.python.org/dev/peps/pep-0415/>`_ (Python 3.3):
``raise exc from None``
* `PEP 409 - Suppressing exception context
<https://www.python.org/dev/peps/pep-0409/>`_ (superseded by the PEP 415)
<https://www.python.org/dev/peps/pep-0409/>`_
(superseded by the PEP 415)
Python C API
------------
The header file ``Include/pyerror.h`` declares functions related to
exceptions.
Functions raising exceptions:
* ``PyErr_SetNone(exc_type)``
* ``PyErr_SetObject(exc_type, exc_value)``
* ``PyErr_SetString(exc_type, message)``
* ``PyErr_Format(exc, format, ...)``
Helpers to raise specific exceptions:
* ``PyErr_BadArgument()``
* ``PyErr_BadInternalCall()``
* ``PyErr_NoMemory()``
* ``PyErr_SetFromErrno(exc)``
* ``PyErr_SetFromWindowsErr(err)``
* ``PyErr_SetImportError(message, name, path)``
* ``_PyErr_SetKeyError(key)``
* ``_PyErr_TrySetFromCause(prefix_format, ...)``
Manage the current exception:
* ``PyErr_Clear()``: clear the current exception,
like ``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)``
Others function to handle exceptions:
* ``PyErr_ExceptionMatches(exc)``: check to implement
``except exc: ...``
* ``PyErr_GivenExceptionMatches(exc1, exc2)``
* ``PyErr_NormalizeException(exc_type, exc_value, exc_tb)``
* ``_PyErr_ChainExceptions(exc_type, exc_value, exc_tb)``
Python Issues
-------------
Chain exceptions:
* `Issue #23763: Chain exceptions in C
<http://bugs.python.org/issue23763>`_
* `Issue #23696: zipimport: chain ImportError to OSError
<http://bugs.python.org/issue23696>`_
* `Issue #21715: Chaining exceptions at C level
<http://bugs.python.org/issue21715>`_: added
``_PyErr_ChainExceptions()``
* `Issue #18488: sqlite: finalize() method of user function may be
called with an exception set if a call to step() method failed
<http://bugs.python.org/issue18488>`_
* `Issue #23781: Add private _PyErr_ReplaceException() in 2.7
<http://bugs.python.org/issue23781>`_
* `Issue #23782: Leak in _PyTraceback_Add
<http://bugs.python.org/issue23782>`_
Changes preventing to loose exceptions:
* `Issue #23571: Raise SystemError if a function returns a result with an
exception set <http://bugs.python.org/issue23571>`_
* `Issue #18408: Fixes crashes found by pyfailmalloc
<http://bugs.python.org/issue18408>`_
Copyright