找出导致异常的对象的变量名



让我们考虑下面的代码示例,我将使用它来引发 AttributeError 作为示例。

def test(first, second):
print("My age is " + first.age + " and my neighbour is " + second.age)

假设我有以下课程。

class Dummy(object):
def __init__(self):
pass

如果我调用函数

d = Dummy()
d.__setattr__("age", "25")
test(d, Dummy())

我会得到一个属性错误,因为第二个假人没有属性age。这是由second.age引起的。

我现在的问题是,是否有一种方法可以找出导致错误的变量的名称。查看源代码很明显它是second,但是我如何在尝试中找出它除了块?

出于调试目的,请注意错误消息解释了发生的情况。

obj = object()
print(obj.does_not_exist)

错误信息

AttributeError: 'object' object has no attribute 'does_not_exist'

因此,很明显哪个属性引发了异常。如果您认为在运行时可能需要该信息,也可以通过sys.exc_info函数恢复该信息。

缩小try-except

范围如果这不能让您满意,请注意,try-except语句的目的是捕获您期望发生的异常。因此,如果同一块中可能出现两个不同的异常,则不妨将其拆分为两个try-except语句。

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

另一种选择可能是使用hasattr来检查该属性是否存在。

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)

您可以更改test定义以拆分属性的访问:

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}")

然后,当您调用test时,您将能够将其跟踪到特定行。

好的,所以我找到了一个解决方案,如果类不受我的控制,它也应该有效。此解决方案仅针对AttributeError但应可扩展,以防需要捕获其他错误。

我们仍然具有相同的测试函数和相同的虚拟类

def test(first, second):
print("My name is " + first.age + " and I am here with " + second.age)
class Dummy(object):
def __init__(self):
pass

我们可以使用 Proxy 对象来包装我们传递给测试函数的每个值。 此代理对象通过设置_had_exception标志来记录它是否看到AttributeError

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

对函数的调用如下所示

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']

最终结果是一个列表,其中包含所有局部变量名称 - 在调用的函数中使用 - 对应于我们包装的输入,这导致了异常。