如何获取缺少属性的对象

7 投票
2 回答
1250 浏览
提问于 2025-04-29 11:25

假设我们试图访问一个不存在的属性:

>>> {'foo': 'bar'}.gte('foo')  # well, I meant “get”!

在Python中,如果你遇到AttributeError,它只会包含一个叫args的属性,里面有一个字符串,内容是错误信息,比如:'dict' object has no attribute 'gte',意思是字典对象没有这个属性。

使用inspect和/或traceback模块配合sys.last_traceback,有没有办法获取到实际的字典对象呢?

>>> offending_object = get_attributeerror_obj(sys.last_traceback)
>>> dir(offending_object)
[...
 'clear',
 'copy',
 'fromkeys',
 'get',       # ah, here it is!
 'items',
 ...]

补充:既然这个秘密已经被揭开,我就分享一下我的发现和代码(请不要解决这个问题并提交到PyPI哦;))

这个AttributeError是在这里创建的,明显没有附带原始对象的引用。

下面是带有相同占位符函数的代码:

import sys
import re
import difflib

AE_MSG_RE = re.compile(r"'(\w+)' object has no attribute '(\w+)'")

def get_attributeerror_obj(tb):
    ???

old_hook = sys.excepthook

def did_you_mean_hook(type, exc, tb):
    old_hook(type, exc, tb)

    if type is AttributeError:
        match = AE_MSG_RE.match(exc.args[0])
        sook = match.group(2)

        raising_obj = get_attributeerror_obj(tb)

        matches = difflib.get_close_matches(sook, dir(raising_obj))
        if matches:
            print('\n\nDid you mean?', matches[0], file=sys.stderr)

sys.excepthook = did_you_mean_hook
暂无标签

2 个回答

1

我想分享一下我的经验,因为我成功地尝试过类似的事情,针对的是DidYouMean-Python

这里的关键在于,这几乎是唯一一个错误信息包含足够信息来推测你真正想要的情况。实际上,重要的是你试图在一个dict对象上调用gte:你需要的是类型,而不是对象本身。

如果你写的是{'foo': 'bar'}.get('foob'),那么处理起来就会复杂得多,我很想知道有没有人有解决方案。

第一步

检查你是否在处理一个AttributeError(使用钩子的第一个参数)。

第二步

从错误信息中提取相关信息(使用第二个参数)。我用正则表达式来做这个。请注意,这个异常可能会根据Python的版本、你调用方法的对象等情况有多种形式。

到目前为止,我的正则表达式是:"^'?(\w+)'? (?:object|instance) has no attribute '(\w+)'$"

第三步

获取与类型对应的类型对象(在你的情况下是'dict'),这样你就可以在它上面调用dir()。一个简单的解决方案是直接使用eval(type),但你可以通过重用追踪信息(钩子的第三个参数)来做得更好、更干净:追踪的最后一个元素包含了异常发生时的帧,在那个帧中,类型是被正确定义的(可能是局部类型、全局类型或内置类型)。

一旦你得到了类型对象,你只需要在它上面调用dir(),然后提取你最喜欢的建议。

如果你需要更多关于我所做的事情的细节,请告诉我。

1

这不是你想要的答案,但我很确定你不能这样做……至少不能通过 sys.excepthook。这是因为在处理错误时,引用计数会减少,所以在调用 sys.excepthook 之前,对象可能已经被垃圾回收了。实际上,这在 CPython 中就是这样发生的:

import sys

class X:
    def __del__(self):
        print("deleting")

def error():
    X().wrong

old_hook = sys.excepthook
def did_you_mean_hook(type, exc, tb):
    print("Error!")
sys.excepthook = did_you_mean_hook

error()
#>>> deleting
#>>> Error!

不过,这并不总是如此。因为异常对象指向当前的执行框架,如果你的代码看起来像这样:

def error():
    x = X()
    x.wrong

x 还不能被回收。x 是由当前框架拥有的,而这个框架还在运行。但是,既然我已经证明没有对这个对象的明确引用,那么该怎么做就不太明显了。例如,

def error():
    foo().wrong

可能有一个对象还存活着,也可能没有,而唯一可行的方法就是运行 foo……但即便如此,你也会遇到副作用的问题。

所以不,这种情况是不可能的。如果你不介意采取任何极端措施,你可能最终需要在加载时重写抽象语法树(AST),这类似于 FuckIt.py。不过你可能不想这么做。


我的建议是尝试使用一个代码检查工具(linter),来获取所有已知类及其方法的名称。你可以利用这些信息来逆向工程错误追踪字符串,以找出类和错误的方法,然后进行模糊匹配来找到建议。

撰写回答