How in Python find where exception was raised - python

How to determine in what function exception was raised. For example exist two functions: 'foo' and 'bar'. In 'foo' exception will raised randomly.
import random
def foo():
if random.randint(1, 10) % 2:
raise Exception
bar()
def bar():
raise Exception
try:
foo()
except Exception as e:
print "Exception raised in %s" % ???

import inspect
try:
foo()
except Exception as e:
print "Exception raised in %s" % inspect.trace()[-1][3]

I use the traceback module, like so:
import traceback
try:
1 / 0
except Exception:
print traceback.format_exc()
This gives the following output:
Traceback (most recent call last):
File "<ipython-input-3-6b05b5b621cb>", line 2, in <module>
1 / 0
ZeroDivisionError: integer division or modulo by zero
If the code runs from a file, the traceback will tell the line and character number of where the error occured :)
EDIT:
To accomodate the comment from Habibutsu: Yes, it's useful for printing, but when needed to get more info (for example function name) - not suitable
The doc-pages tell you how to extract the trace programmatically: http://docs.python.org/2/library/traceback.html
From the page linked above:
>>> import traceback
>>> def another_function():
... lumberstack()
...
>>> def lumberstack():
... traceback.print_stack()
... print repr(traceback.extract_stack())
... print repr(traceback.format_stack())
...
>>> another_function()
File "<doctest>", line 10, in <module>
another_function()
File "<doctest>", line 3, in another_function
lumberstack()
File "<doctest>", line 6, in lumberstack
traceback.print_stack()
[('<doctest>', 10, '<module>', 'another_function()'),
('<doctest>', 3, 'another_function', 'lumberstack()'),
('<doctest>', 7, 'lumberstack', 'print repr(traceback.extract_stack())')]
[' File "<doctest>", line 10, in <module>\n another_function()\n',
' File "<doctest>", line 3, in another_function\n lumberstack()\n',
' File "<doctest>", line 8, in lumberstack\n print repr(traceback.format_stack())\n']
The doc-string for traceback.extract_stack is the same as for traceback.extract_tb
traceback.extract_tb(traceback[, limit])
Return a list of up to limit “pre-processed” stack trace entries
extracted from the traceback object traceback. It is useful for
alternate formatting of stack traces. If limit is omitted or None, all
entries are extracted. A “pre-processed” stack trace entry is a
quadruple (filename, line number, function name, text) representing
the information that is usually printed for a stack trace. The text is
a string with leading and trailing whitespace stripped; if the source
is not available it is None.

What is your goal? If you are worried about bar and foo throwing the same exception type and the caller not being able to differentiate between them, just derive a new exception class:
import random
class FooException(Exception):
"""An exception thrown only by foo."""
def foo():
if random.randint(1,10) % 2:
raise FooException
bar()
def bar():
raise Exception
try:
foo()
except FooException:
print "Exception raised in foo..."
except:
print "Exception raised in bar (probably)..."

Related

Gathering results from an asyncio task list with exceptions

The code...
import asyncio
import random
from time import perf_counter
from typing import Iterable
from pprint import pprint
async def coro(n, i, threshold=0.4):
await asyncio.sleep(i)
if i > threshold:
# For illustration's sake - some coroutines may raise,
# and we want to accomodate that and just test for exception
# instances in the results of asyncio.gather(return_exceptions=True)
raise Exception(f"{i} of Task-{n} is too high")
return i
async def main(it: Iterable, timeout: float) -> tuple:
tasks = [asyncio.create_task(coro(i+1, d), name=f"Task-{i+1}") for i, d in enumerate(it)]
await asyncio.wait(tasks, timeout=timeout)
return tasks # *not* (done, pending)
timeout = 0.5
random.seed(444)
n = 10
it = [random.random() for _ in range(n)]
start = perf_counter()
tasks = asyncio.run(main(it=it, timeout=timeout))
elapsed = perf_counter() - start
print(f"Done main({n}) in {elapsed:0.2f} seconds\n")
pprint(tasks)
print('----')
# does not work from here on....
res = []
for t in tasks:
try:
r = t.result() # gives an error!!!
except Exception as e:
res.append(e)
else:
res.append(r)
pprint(res)
...does not work for collection of the task results. It fails with ...
Traceback (most recent call last):
File "c:\Users\user\Documents\user\projects\learn\asyncio\wrap_gather_in_timeout.py", line 8, in coro
await asyncio.sleep(i)
File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\asyncio\tasks.py", line 654, in sleep
return await future
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\user\Documents\user\projects\learn\asyncio\wrap_gather_in_timeout.py", line 35, in <module>
r = t.result()
asyncio.exceptions.CancelledError
Task exception was never retrieved
future: <Task finished name='Task-7' coro=<coro() done, defined at c:\Users\user\Documents\user\projects\learn\asyncio\wrap_gather_in_timeout.py:7> exception=Exception('i too high')>
Traceback (most recent call last):
File "c:\Users\user\Documents\user\projects\learn\asyncio\wrap_gather_in_timeout.py", line 13, in coro
raise Exception("i too high")
Exception: i too high
The code was run in python 3.9.
Any idea where I am going wrong and why?
Is it because the tasks need to be cancelled after it throws an exception? I could not successfully implement it.
Inspired by: Solution to wrapping asyncio.gather SO
Your code works, the issue why you are not able to create res successfully is because the code does not raise just the normal Exception class. Since the task fails it ends up calling asyncio.exceptions.CancelledError which if we take a look in the documentation inherits from BaseException not Exception. This change is new as of Python 3.8 and since you are using Python 3.9 that change is live. Changing your code slightly to the following yields:
res = []
for t in tasks:
try:
r = t.result() # gives an error!!!
except BaseException as e:
res.append(e)
continue
res.append(r)
print(res)
[0.3088946587429545,
0.01323751590501987,
Exception('0.4844375347808497 of Task-3 is too high'),
asyncio.exceptions.CancelledError(),
asyncio.exceptions.CancelledError(),
asyncio.exceptions.CancelledError(),
Exception('0.4419557492849159 of Task-7 is too high'),
0.3113884366691503,
0.07422124156714727,
asyncio.exceptions.CancelledError()]

Having issues with formatters: "unsupported operand type(s) for %: 'NoneType' and 'tuple'"

I'm getting this error and I don't understand why.
I'm learning to use Gmail's API and copy pasted their example from: https://developers.google.com/gmail/api/v1/reference/users/threads/get#examples
Here is the code:
def GetThread(service, user_id, thread_id):
"""Get a Thread.
Args:
service: Authorized Gmail API service instance.
user_id: User's email address. The special value "me"
can be used to indicate the authenticated user.
thread_id: The ID of the Thread required.
Returns:
Thread with matching ID.
"""
try:
thread = service.users().threads().get(userId=user_id, id=thread_id).execute()
messages = thread['messages']
print ('thread id: %s - number of messages '
'in this thread: %d') % (thread['id'], len(messages))
return thread
except errors.HttpError, error:
print 'An error occurred: %s' % error
I'm currently getting this error:
thread id: %s - number of messages in this thread: %d
Traceback (most recent call last):
File "quickstart1.py", line 176, in <module>
main()
File "quickstart1.py", line 152, in main
GetThread(service, EMAIL_LOGIN, thread_id)
File "quickstart1.py", line 121, in GetThread
'in this thread: %d') % (thread['id'], len(messages))
TypeError: unsupported operand type(s) for %: 'NoneType' and 'tuple'
Even when I change
print ('thread id: %s - number of messages in this thread: %d') % (thread['id'], len(messages))
to: print ('thread id: %s - number of messages in this thread: %d') % ('test', 1)
thread id: %s - number of messages in this thread: %d
Traceback (most recent call last):
File "quickstart1.py", line 175, in <module>
main()
File "quickstart1.py", line 151, in main
GetThread(service, EMAIL_LOGIN, thread_id)
File "quickstart1.py", line 120, in GetThread
print ('thread id: %s - number of messages in this thread: %d') % ('test', 1)
TypeError: unsupported operand type(s) for %: 'NoneType' and 'tuple'
I still get the same error. Any idea reason why?
This example from Google was written for Python 2, but you're using it on Python 3 or with from __future__ import print_function. It is very unfortunate that Google is using an obsolete version of Python in their documentation examples, though at least it is obvious from the last print statement and the except Exception, e, which was changed to except errors.HttpError, error:
Specifically:
print ('thread id: %s - number of messages '
'in this thread: %d') % (thread['id'], len(messages))
was written for Python 2, and should be changed to
print(('thread id: %s - number of messages '
'in this thread: %d') % (thread['id'], len(messages)))
In Python 3, print is a function that returns None, so that kind of statement is parsed as
(print('something')) % (something, something)
i.e. the operator % is applied to the return value of print and the tuple (thread['id'], len(messages)).
However in Python 2, where print was a statement, everything after the print keyword would have been evaluated first and printed; so in Python 2, it is parsed as
print (('something') % (something, something))
Adding the extra parentheses around the whole thing makes it work correctly on Python 3.
If the statement didn't have those extra parentheses, you would have got SyntaxError: Missing parentheses in call to 'print'” mean in Python?" which would have made the error immediately obvious.
Your parentheses are in the wrong place.
print ('thread id: %s - number of messages in this thread: %d') % ('test', 1)
You're calling print, which returns None. Then you're trying to
apply % between that None and the tuple. It should be:
print ('thread id: %s - number of messages in this thread: %d' % ('test', 1))
Edit: Martijn is right. This is Python 2. I was thinking in terms of
Python 3, that being the only one I use.
If this were in Python 3, print would be a function, and parentheses
would have to go around everything. Running this in Python 3 will get
that error.
However, in Python 2, print is a statement and you don't have to use
parentheses. Looking at the code you link to, we see this:
print ('thread id: %s - number of messages '
'in this thread: %d') % (thread['id'], len(messages))
Here, the parentheses serve a different purpose. The string is split
across two lines, and parentheses are needed to make the parser treat it
as one object; 'strings' 'like' 'this' are concatenated into one
string. So, in Python 2, this is correct syntax.
% operates on a string, not a print statement/function.
# wrong
print ('thread id: %s - number of messages in this thread: %d') % (thread['id'], len(messages))
# right
print ('thread id: %s - number of messages in this thread: %d' % (thread['id'], len(messages)))
It's actually a little more complicated than that. In Python 2, print was a statement that took 1 or more expressions and printed them to standard output. A statement like print ("hi") was parsed as the print keyword followed by a parenthesized expression. That means print ("%s") % ("hi",) would be parsed as print followed by the expression ("%s") % ("hi,), which would evaluated to "hi", then be printed.
In Python 3, print became a function, meaning the parser now recognizes print ("%s") % ("hi", ) as one expression, involving the % operator and its two arguments, the function call print("%s") and the tuple ("hi",). Since print returns None, Python complains that % doesn't support the first argument being of type None.

inspect.getargvalues() throws exception "AttributeError: 'tuple' object has no attribute 'f_code'"

I'm trying to use Python's inspect module (in Python 2) to show information about the function that called the current function, including its arguments.
Here's a simple test program:
import inspect
def caller_args():
frame = inspect.currentframe()
outer_frames = inspect.getouterframes(frame)
caller_frame = outer_frames[1]
return inspect.getargvalues(caller_frame)
def fun_a(arg1):
print caller_args()
def fun_b():
fun_a('foo')
if __name__ == '__main__':
fun_b()
And this happens when I run it:
$ python getargvalues_test.py
Traceback (most recent call last):
File "getargvalues_test.py", line 16, in <module>
fun_b()
File "getargvalues_test.py", line 13, in fun_b
fun_a('foo')
File "getargvalues_test.py", line 10, in fun_a
print caller_args()
File "getargvalues_test.py", line 7, in caller_args
return inspect.getargvalues(caller_frame)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 829, in getargvalues
args, varargs, varkw = getargs(frame.f_code)
AttributeError: 'tuple' object has no attribute 'f_code'
I've googled that AttributeError exception, but with no luck. What am I doing wrong?
(I've since found the problem, so I'm asking-and-answering this here so anyone who has this problem in future will find the answer here.)
This similar question helped me discover the problem.
The Python documentation for the inspect module mentions both "frame records" and "frame objects", and explains the difference.
inspect.currentframe() returns a frame object, but
inspect.getouterframes() returns a list of frame records.
The mistake in the code above is not extracting the frame object from the frame record of the calling function, and passing inspect.getouterframes() the frame record instead of the frame object. (Note that inspect.getouterframes() doesn't check that its argument is a frame object.)
Here's the fixed definition of caller_args() (with the change to the assignment to caller_frame):
def caller_args():
frame = inspect.currentframe()
outer_frames = inspect.getouterframes(frame)
caller_frame = outer_frames[1][0]
return inspect.getargvalues(caller_frame)
Which runs as desired:
$ python getargvalues_test_fixed.py
ArgInfo(args=['arg1'], varargs=None, keywords=None, locals={'arg1': 'foo'})
cause of error
AttributeError: 'tuple' object has no attribute 'f_code'
in your function
def caller_args()
is that caller_frame is an array of which you need item [1][0] as argument for
inspect.getargvalues(...)
this works :
currframe = inspect.currentframe()
callerframe = inspect.getouterframes(currframe, 2)
inspect.getargvalues(callerframe[1][0])
Also, the getargvalues function returns 4 values. First three are unimportant in this case, fourth contains JSON like format key/value list of callerframe arguments
_,_,_,values = inspect.getargvalues(callerframe[1][0])
for i in values:
argsstring += str(i) + ' : ' + str(values[i])
My test looks like this :
import inspect
def log(text):
currframe = inspect.currentframe()
callerframe = inspect.getouterframes(currframe, 2)
_,_,_,values = inspect.getargvalues(callerframe[1][0])
argsstring = ''
for i in values:
argsstring += str(i) + ' : ' + str(values[i])
print('name of file : ' + callerframe[1][1])
print('name of function : ' + callerframe[1][3])
print('line number : ' + str(callerframe[1][2]))
print('caller function arguments : ' + argsstring)
def doTest(text):
log(text)
doTest('this is a test')

Why Python doesn't catch exceptions raised in C++?

I am trying to make a Python iterator in boost::python. So I have a function
PyObject *my_iterator_next(MyIterator *iter) {
if (!iter->is_end()) {
return *(*iter)++;
}
else {
PyErr_SetNone(PyExc_StopIteration);
// this doesn't work either
// PyErr_SetString(PyExc_StopIteration, "end of collection");
return NULL;
}
}
In Python:
// x is MyContainer([1, 2, 3])
for x in my_container:
print(x)
And I get:
1
2
3
NoneTraceback (most recent call last):
File "main.py", line 6, in <module>
print(x)
StopIteration: end of collection
Also
it = my_collection.__iter__()
try:
it.__next__();
it.__next__();
it.__next__();
it.__next__();
except:
print("caught exception")
This code doesn't print anything, so no type of exception is caught.
Why?
After setting the Python exception, you must notify Boost.Python like this:
throw_error_already_set();
See http://www.boost.org/doc/libs/1_35_0/libs/python/doc/v2/errors.html

traceback.format_exc/print_exc returns None when expecting traceback

I can't figure out why traceback.format_exc() is returning "None" in the following example:
#!/usr/bin/env python
import sys
import traceback
def my_excepthook(type, value, tb):
print type.__name__
print value
# the problem: why does this return "None"?
print traceback.format_exc(tb) # see http://docs.python.org/library/traceback.html#traceback.format_exc
sys.excepthook = my_excepthook # see http://docs.python.org/library/sys.html#sys.excepthook
# some code to generate a naturalistic exception
a = "text"
b = 5
error = a + b
Using Python 2.7.1, I get the following output:
TypeError
cannot concatenate 'str' and 'int' objects
None
Instead of "None" on the 3rd line, I'd expect to get what happens when I comment out the sys.excepthook line:
Traceback (most recent call last):
File "log-test.py", line 17, in <module>
error = a+b
Try to change like this in my_excepthook:
print "".join(traceback.format_exception(type, value, tb))

Categories