访问正在创建的对象所赋予的名称

6 投票
4 回答
508 浏览
提问于 2025-04-16 04:19

我正在写一些代码,用来确定一个对象被赋予的名称。这是为了进行一般的调试工作,同时也让我更熟悉Python的内部机制。

我把它做成了一个类装饰器,这样这个类的所有实例在可能的情况下都会记录它们的名称。代码比较长,所以我不会在这里贴出来,除非有人要求。大致的步骤如下:

  1. 用我想要的代码装饰类的 __init__ 方法。

  2. 设置 caller = inspect.currentframe().f_back,然后打开 inspect.getframeinfo(caller).filename,把它传给 ast.parse。我在这里不做错误检查,因为(1)这只是为了调试/分析/黑客行为,(2)这个过程刚刚完成,否则代码根本不会运行。这样做有问题吗?

  3. 找到导致当前执行的 __init__ 方法运行的 ast.Assignment 实例。

  4. 如果 len(assignment.targets) == 1,那么左边只有一个项目,我可以从 targets[0].id 中获取名称。在像 a = Foo() 这样的简单形式中,assignment.value 是一个 ast.Call 的实例。如果它是一个字面量(例如列表),那么 value 就是那个列表,我就不再继续,因为我感兴趣的对象并没有被赋予一个名称。

确认 assignment.value.func 确实是我感兴趣的对象的 type(obj).__call__ 的最佳方法是什么?我很确定它“某处”是存在的,否则代码根本不会运行。我只需要它在顶层。显而易见的做法是遍历它,确保里面没有其他调用。这样我就能确认我有这个名称。(我的推理是正确的,但我不确定它的假设是否正确。)不过这并不理想,因为如果我对 Foo 感兴趣,这可能会让我忽略 a = Foo(Bar()),因为我不知道它是否是 a = Bar(Foo())

当然,我可以直接检查 assignment.value.func.id,但这样的话,有人可能会做 Foobar = Foo 之类的事情,所以我不想过于依赖这个。

任何帮助都将不胜感激。和往常一样,我也对任何其他建议或我可能忽略的问题感兴趣。

另外,我真的很惊讶我居然需要自己发明“python-internals”这个标签。

4 个回答

0

我这里没有做任何错误检查,因为(1)这只是为了调试/性能分析/黑客实验(2)这个过程刚刚完成,否则代码就不会运行。这样做有问题吗?

有:

  1. 启动一个程序

  2. 等到它导入一个特定的模块 foo.py

  3. 编辑 foo.py

现在,加载到 Python 进程中的代码和磁盘上的代码不一致了。

这也是为什么反汇编字节码可能是更好的一种方法。

0

我不知道这对你有多大帮助,但你有没有考虑过使用 locals() 这个函数?它会返回一个 dict(字典),里面包含了所有局部变量的名字和它们的值。

举个例子:

s = ''
locals()
>>> {'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 's': '', '__name__': '__main__', '__doc__': None}
t = s  # I think this is what is of most importance to you
locals()
>>> {'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 's': '', 't': '', '__name__': '__main__', '__doc__': None}

这样你就可以遍历这个字典,检查哪些变量的值是你想要的那种类型的对象。

就像我说的,我不知道这个回答能帮上你多少,但如果你对任何内容有疑问,欢迎留言,我会尽量回复你。

2

抽象语法树(AST)无法给你这个答案。你可以试试使用 frame.f_lasti,然后查看一下字节码。如果下一行不是 STORE_FAST,那说明在你想要的简单赋值之外,还有其他的内部调用或者其他事情发生。

def f():
  f = sys._getframe()
  i = f.f_lasti + 3   # capture current point of execution, advance to expected store
  print dis.disco(f.f_code, i)

撰写回答