This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: sys.exc_clear() clears exception in other stack frames
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 2.7
process
Status: closed Resolution: out of date
Dependencies: Superseder:
Assigned To: Nosy List: Garrett Berg, iritkatriel
Priority: normal Keywords:

Created on 2017-12-13 23:27 by Garrett Berg, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (4)
msg308264 - (view) Author: Garrett Berg (Garrett Berg) * Date: 2017-12-13 23:27
# Summary
In python (2 or 3) it should always be valid to have a bare *raise* statement in an exception block.

    try:
        do_something()
    except SomeException:
        do_cleanup()
        raise # always reraises SomeException with original stack trace

You can see this in the python exception documentation:
https://docs.python.org/2.7/tutorial/errors.html#raising-exceptions


# Reproduction of Failure of invariants
Example python file where this doesn't work:

    from __future__ import print_function

    import sys

    def doclear():
        """This should basically be a noop"""
        sys.exc_clear()

    try:
        1/0
    except ZeroDivisionError:
        exc = sys.exc_info()[0]
        print("first exc:", exc)
        assert exc is ZeroDivisionError

    try:
        1/0
    except ZeroDivisionError:
        exc = sys.exc_info()[0]
        print("second exc (before doclear):", exc)
        doclear()
        exc = sys.exc_info()[0]
        print("second exc (after  doclear):", exc)
        assert sys.exc_info()[0] is ZeroDivisionError  # fails


Running in python2.7 gives:

    first exc: <type 'exceptions.ZeroDivisionError'>
    second exc (before doclear): <type 'exceptions.ZeroDivisionError'>
    second exc (after  doclear): None
    Traceback (most recent call last):
      File "doclear.py", line 23, in <module>
        assert sys.exc_info()[0] is ZeroDivisionError  # fails
    AssertionError


# Conclusion
There seems to be a bug in python 2.7 where it doesn't follow it's own spec.
[sys.exc_clear()](https://docs.python.org/2/library/sys.html#sys.exc_clear)
states:

> This function clears all information relating to the current or last
> exception that occurred in the current thread. After calling this function,
> exc_info() will return three None values until another exception is raised in
> the current thread or *the execution stack returns to a frame where another
> exception is being handled*.

The above code is clear example where sys.exc_clear() is changing the value of
sys.exc_info() *in a different stack frame*, which is against what is stated in
the above docs.

This bug makes it impossible to reraise with a bare raise statement when doing
cleanup that could use sys.exc_clear(). In other words, calling into functions
can *change your local state* with regards to what exception is being handled.
msg308265 - (view) Author: Garrett Berg (Garrett Berg) * Date: 2017-12-13 23:29
I forgot to post this:

    python --version                                                                                                                                                                                                                                                                                            
    Python 2.7.14
msg308269 - (view) Author: Garrett Berg (Garrett Berg) * Date: 2017-12-14 00:06
I found a workaround, and probably the reason this has not been detected before:

    import sys

    def doclear2()
        try:
            1/0
        except ZeroDivisionError:
            sys.exc_clear()

    try:
        1/0
    except ZeroDivisionError:
        exc = sys.exc_info()[0]
        print("third exc (before doclear):", exc)
        doclear2()
        exc = sys.exc_info()[0]
        print("third exc (after  doclear):", exc)
        assert sys.exc_info()[0] is ZeroDivisionError

The above passes. It seems that being inside a local exception block allows for the spec to hold, only the "local exception" is cleared.

I still think this is a pretty major bug, but it is good to know what the workaround/reason it's "been working" is.
msg381634 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2020-11-22 20:39
sys.exc_clear was removed in Python 3:
https://docs.python.org/3/whatsnew/3.0.html#index-22
History
Date User Action Args
2022-04-11 14:58:55adminsetgithub: 76498
2020-11-22 20:39:24iritkatrielsetstatus: open -> closed

nosy: + iritkatriel
messages: + msg381634

resolution: out of date
stage: resolved
2017-12-14 00:06:27Garrett Bergsetmessages: + msg308269
2017-12-13 23:29:50Garrett Bergsetmessages: + msg308265
2017-12-13 23:27:44Garrett Bergcreate