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.
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.
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.
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 ).
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)
The following approach might work, although it's a bit long-winded.
import inspect; inspect.currentframe().f_code
f_code.co_code
), perhaps using dis.dis
, to figure out whether the frame is being executed in an except
block.Ex:
def infoo():
raise MyErr("from foo in except")
try:
nope
except:
infoo()
except
block then the sys.exc_info()
is outdated.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With