Reputation: 338
Let's consider the following code example which I will use to raise an AttributeError as an example.
def test(first, second):
print("My age is " + first.age + " and my neighbour is " + second.age)
Say I have the following class.
class Dummy(object):
def __init__(self):
pass
If I call the function with
d = Dummy()
d.__setattr__("age", "25")
test(d, Dummy())
I will get an AttributeError because the second Dummy has no Attribute age
. This is caused by second.age
.
My question now is if there is a way that I can find out what the name of the variable is that causes the error. Looking at the source code it is obvious that it is second
, but how can I find this out in an try except block?
Upvotes: 3
Views: 2581
Reputation: 338
Ok, so I found a solution which should also work in case a class is not under my control. This solution only targets the AttributeError
but should be extendable in case other Errors need to be caught.
We still have the same test function and the same Dummy class
def test(first, second):
print("My name is " + first.age + " and I am here with " + second.age)
class Dummy(object):
def __init__(self):
pass
We can use a Proxy object to wrap each value we pass to the test function.
This proxy object records if it sees an AttributeError
by setting the _had_exception
flag.
class Proxy(object):
def __init__(self, object_a):
self._object_a = object_a
self._had_exception: bool = False
def __getattribute__(self, name):
if name == "_had_exception":
return object.__getattribute__(self, name)
obj = object.__getattribute__(self, '_object_a')
try:
return getattr(obj, name)
except AttributeError as e:
# Flag this object as a cause for an exception
self._had_exception = True
raise e
And the call to the function looks as follows
d = Dummy()
d.__setattr__("age", "25")
p1 = Proxy(d)
p2 = Proxy(Dummy())
try:
test(p1, p2)
except AttributeError as e:
# Get the local variables from when the Error happened
locals = e.__traceback__.tb_next.tb_frame.f_locals
offender_names = []
# Check if one of the local items is the same
# as one of our inputs that caused an Error
for key, val in locals.items():
if p1._had_exception:
if p1 is val:
offender_names.append(key)
if p2._had_exception:
if p2 is val:
offender_names.append(key)
print(offender_names) # ['second']
The end result is a list with all local variable names -- used in the called function -- which correspond to our wrapped inputs, that caused an exception.
Upvotes: 0
Reputation: 22324
For debug purpose, note that the error message explains what happened.
obj = object()
print(obj.does_not_exist)
AttributeError: 'object' object has no attribute 'does_not_exist'
It is thus clear which attribute raised the exception. You can also recover that information though the sys.exc_info
function if you think you might need that information at runtime.
try-except
If that does not satisfy you, be aware that the purpose of a try-except
statement is to catch exceptions you expect to happen. Thus if two different exceptions might arise in the same block, you might as well split it into two try-except
statements.
def test(first, second):
try:
first_age = first.age
except AttributeError:
# Do something if first doest not have attribute age
try:
second_age = second.age
except AttributeError:
# Do something if second does not have attribute age
print("My age is " + first.age + " and my neighbour is " + second.age)
hasattr
Another option might be to use hasattr
to check if the attribute exist.
def test(first, second):
if not hasattr(first, 'age'):
# Do something
if not hasattr(second, 'age'):
# Do something else
print("My age is " + first.age + " and my neighbour is " + second.age)
Upvotes: 2
Reputation: 5470
You could change your test
definition to split up the accessing of the attributes:
def test(first, second):
f_age = first.age
s_age = second.age
print(f"My age is {f_age} and my neighbour is {s_age}")
Then when you invoke test
, you'll be able to trace it to the particular line.
Upvotes: 0