Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding out an exception context

tlndr: how to tell in a function if it's called from an except block (directly/indirectly). python2.7/cpython.

I use python 2.7 and try to provide something similar to py3's __context__ for my custom exception class:

class MyErr(Exception):
    def __init__(self, *args):
        Exception.__init__(self, *args)
        self.context = sys.exc_info()[1]
    def __str__(self):
        return repr(self.args) + ' from ' + repr(self.context)

This seems to work fine:

try:
   1/0
except:
   raise MyErr('bang!')

#>__main__.MyErr: ('bang!',) from ZeroDivisionError('integer division or modulo by zero',)

Sometimes I need MyErr to be raised outside of an exception block. This is fine too:

raise MyErr('just so')

#>__main__.MyErr: ('just so',) from None

If, however, there has been a handled exception before this point, it's being incorrectly set as a context of MyErr:

try:
    print xxx
except Exception as e:
    pass

# ...1000 lines of code....
raise MyErr('look out')

#>__main__.MyErr: ('look out',) from NameError("name 'xxx' is not defined",) <-- BAD

I guess the reason is that sys.exc_info simply returns the "last" and not the "current" exception:

This function returns a tuple of three values that give information about the exception that is currently being handled. <...> Here, “handling an exception” is defined as “executing or having executed an except clause.”

So, my question is: how to tell if the interpreter is executing an except clause (and not has it executed in the past). In other words: is there a way to know in MyErr.__init__ if there is an except up on the stack?

My app is not portable, any Cpython specific hacks are welcome.

like image 969
georg Avatar asked Oct 07 '13 20:10

georg


People also ask

Where do you find exceptions in Python?

Catching Exceptions in Python In Python, exceptions can be handled using a try statement. The critical operation which can raise an exception is placed inside the try clause. The code that handles the exceptions is written in the except clause.

What is Ioerror in Python?

It is an error raised when an input/output operation fails, such as the print statement or the open() function when trying to open a file that does not exist. It is also raised for operating system-related errors.

What is exception in Python example?

Python Logical Errors (Exceptions) For instance, they occur when we try to open a file(for reading) that does not exist ( FileNotFoundError ), try to divide a number by zero ( ZeroDivisionError ), or try to import a module that does not exist ( ImportError ).


2 Answers

This is tested with CPython 2.7.3:

$ python myerr.py 
MyErr('bang!',) from ZeroDivisionError('integer division or modulo by zero',)
MyErr('nobang!',)

It works as long as the magic exception is directly created within the scope of an except clause. A little additional code can lift that restriction, though.

import sys
import opcode

SETUP_EXCEPT = opcode.opmap["SETUP_EXCEPT"]
SETUP_FINALLY = opcode.opmap["SETUP_FINALLY"]
END_FINALLY = opcode.opmap["END_FINALLY"]

def try_blocks(co):
    """Generate code positions for try/except/end-of-block."""
    stack = []
    code = co.co_code
    n = len(code)
    i = 0
    while i < n:
        op = ord(code[i])
        if op in (SETUP_EXCEPT, SETUP_FINALLY):
            stack.append((i, i + ord(code[i+1]) + ord(code[i+2])*256))
        elif op == END_FINALLY:
            yield stack.pop() + (i,)
        i += 3 if op >= opcode.HAVE_ARGUMENT else 1

class MyErr(Exception):
    """Magic exception."""

    def __init__(self, *args):
        callee = sys._getframe(1)
        try:
            in_except = any(i[1] <= callee.f_lasti < i[2] for i in try_blocks(callee.f_code))
        finally:
            callee = None

        Exception.__init__(self, *args)
        self.cause = sys.exc_info()[1] if in_except else None

    def __str__(self):
        return "%r from %r" % (self, self.cause) if self.cause else repr(self)

if __name__ == "__main__":
    try:
        try:
            1/0
        except:
            x = MyErr('bang!')
            raise x
    except Exception as exc:
        print exc

    try:
        raise MyErr('nobang!')
    except Exception as exc:
        print exc
    finally:
        pass

And remember, “Explicit is better than implicit,” so this would be way better if you ask me:

try:
    …
except Exception as exc:
    raise MyErr("msg", cause=exc)
like image 183
jhermann Avatar answered Oct 27 '22 18:10

jhermann


The following approach might work, although it's a bit long-winded.

  • Get the code of the current frame from import inspect; inspect.currentframe().f_code
  • Inspect the bytecode (f_code.co_code), perhaps using dis.dis, to figure out whether the frame is being executed in an except block.
  • Depending on what you want to do, you might want to go back a frame and see if it wasn't called from an except block.

Ex:

def infoo():
    raise MyErr("from foo in except")

try:
    nope
except:
    infoo()
  • If none of the frames are in an except block then the sys.exc_info() is outdated.
like image 21
Claudiu Avatar answered Oct 27 '22 17:10

Claudiu