从框架中获取可调用对象

8 投票
3 回答
2615 浏览
提问于 2025-04-15 12:55

给定一个帧对象(比如通过sys._getframe返回的),我能否获取到底层的可调用对象?

代码说明:

def foo():
    frame = sys._getframe()
    x = some_magic(frame)

    # x is foo, now

请注意,我的问题是如何从一个帧中获取对象,而不是获取当前被调用的对象。

希望这是可能的。

谢谢,

MH

编辑:

我在某种程度上找到了解决这个问题的方法。这主要受到Andreas和Alexander回复的启发。感谢你们的时间投入!

def magic():
    fr = sys._getframe(1)
    for o in gc.get_objects():
        if inspect.isfunction(o) and o.func_code is fr.f_code:
            return o 

class Foo(object):
    def bar(self):
        return magic()

x = Foo().bar()

assert x is Foo.bar.im_func

(在2.6.2版本中有效,对于py3k,将func_code替换为__code__,将im_func替换为__func__

然后,我可以积极地遍历globals()或gc.get_objects(),并使用dir()查找与给定函数对象对应的可调用对象。

这让我觉得有点不太符合Python的风格,但确实有效。

再次感谢!

MH

3 个回答

0

这不是一个真正的答案,而是一个评论。我本想把它作为评论发出去,但我的“声望积分”不够。

不过,我觉得这里有一个合理的(我认为)使用场景,说明为什么想要这样做。

我的应用程序使用gtk,并且创建了很多线程。任何同时做这两件事的人都知道,除了主线程之外,你不能直接操作图形界面(GUI)。一个常见的解决办法是把需要操作GUI的函数交给 idle_add(),这样它就会在主线程中安全地运行。于是,我有很多这样的代码:

def threaded_gui_func(self, arg1, arg2):
    if threading.currentThread().name != 'MainThread':
        gobject.idle_add(self.threaded_gui_func, arg1, arg2)
        return
    # code that touches the GUI

如果我能简单地写成这样就好了,这样会更短、更简单(也更方便复制粘贴):

def thread_gui_func(self, arg1, arg2):
    if idleIfNotMain(): return
    # code that touches the GUI

其中,idleIfNotMain() 如果在主线程中就返回 False,如果不在主线程中,它会使用 inspect(或者其他方法)来找出需要调用的函数和参数,然后交给 idle_add(),最后返回 True。获取参数我能搞定,但获取函数似乎不是那么简单。 :-(

1

有点丑,不过就是这样:

frame.f_globals[frame.f_code.co_name]

完整的例子:

#!/usr/bin/env python

import sys

def foo():
  frame = sys._getframe()
  x = frame.f_globals[frame.f_code.co_name]

  print foo is x

foo()

输出'真'。

2

为了支持所有情况,包括函数是类的一部分还是单纯的全局函数,其实没有简单的方法可以做到这一点。你可能可以获取完整的调用栈,然后通过 globals() 一层层地遍历,但这样做并不优雅...

我能给你提供的最接近的东西是这个:

import sys, types

def magic():
    # Get the frame before the current one (i.e. frame of caller)
    frame = sys._getframe(1)
    # Default values and closure is lost here (because they belong to the
    # function object.)
    return types.FunctionType(frame.f_code, frame.f_globals)

class MyClass(object):
    def foo(self, bar='Hello World!'):
        print bar
        return magic()

test = MyClass()

new_foo = test.foo()
new_foo(test, 'Good Bye World!')

你将执行完全相同的代码,但它会在一个新的代码包装器中运行(比如说 FunctionType)。

我猜你是想根据调用栈恢复应用程序的状态... 这里有一些东西,至少能尽可能地像原始调用那样调用函数(闭包的部分仍然没有包含,因为如果你能从栈帧中获取闭包,那么获取被调用的函数就会变得相对简单):

import sys, types

class MyClass(object):
    def __init__(self, temp):
        self.temp = temp

    def foo(self, bar):
        print self.temp, bar
        return sys._getframe()

def test(hello):
    print hello, 'World!'
    return sys._getframe()

def recall(frame):
    code = frame.f_code
    fn = types.FunctionType(
        code, frame.f_globals, code.co_name,
        # This is one BIG assumption that arguments are always last.
        tuple(frame.f_locals.values()[-code.co_argcount:]))
    return fn()


test1 = MyClass('test1')
frame1 = test1.foo('Hello World!')

test2 = MyClass('test2')
frame2 = test2.foo('Good Bye World!')
frame3 = test2.foo('Sayonara!')

frame4 = test('HI')

print '-'

recall(frame4)
recall(frame3)
recall(frame2)
recall(frame1)

撰写回答