8

Given the frame object (as returned by sys._getframe, for instance), can I get the underlying callable object?

Code explanation:

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

    # x is foo, now

Note that my problem is getting the object out of a frame, not the currently called object.

Hope that's possible.

Cheers,

MH

EDIT:

I've somewhat managed to work around this problem. It was heavily inspired by Andreas' and Alexander's replies. Thanks guys for the time invested!

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

(works in 2.6.2, for py3k replace func_code with __code__ and im_func with __func__)

Then, I can aggressively traverse globals() or gc.get_objects() and dir() everything in search for the callable with the given function object.

Feels a bit unpythonic for me, but works.

Thanks, again!

MH

Mike Hordecki
  • 92,113
  • 3
  • 26
  • 27
  • As you can see in the answers below, it's possible to do. It is in no way recommended though. What is it that you want to achieve? Most likely it can be done in some other way. – Blixt Jul 15 '09 at 17:30
  • The actual thing I'm trying to do is to log all function calls in the program (and process those called functions, later). For this, I'm tracing all stack frames using bdb module - which gives me frame objects. I would be deeply thankful for any ideas! – Mike Hordecki Jul 15 '09 at 17:59
  • By process, do you mean you want to call them or are you doing something else? – Blixt Jul 15 '09 at 18:24
  • Have a look at this: http://stackoverflow.com/questions/541329/is-it-possible-to-programmatically-construct-a-python-stack-frame-and-start-execu I don't envy you... – Blixt Jul 15 '09 at 18:45
  • I think everyone's forgetting about closures--you can't just make a function out of f_code and have it be the same as the original function. Closures are everywhere. What are you trying to do that needs a function, that you can't do with just the frame and code objects? – Glenn Maynard Jul 15 '09 at 19:31
  • Problem is that closure information belongs to the `FunctionType` object which you can't get from a `frame` object. If you *could* get the closure information, you could pretty easily write a hack that would get the current function being executed in the frame as well. But you're right that it won't behave the same when closures are taken into account. I just can't come up with another way to do it... – Blixt Jul 15 '09 at 19:35
  • Until we know what the poster means by 'process the called functions' it'll be hard to know what it would take exactly. To execute the function exactly according to original conditions with closures and all is hopefully not what Mike wants. :) – Alexander Ljungberg Jul 15 '09 at 20:24
  • I've updated my question - check it out. – Mike Hordecki Jul 15 '09 at 21:10
  • Haha, I'll be damned, that's a clever way of doing the impossible =) I'd use reference instead of equality check though: `o.func_code is fr.f_code` – Blixt Jul 15 '09 at 21:30
  • Ah, forgot about that ;) – Mike Hordecki Jul 15 '09 at 21:51
  • Pythonic or not I think it looks like a fun solution. :) – Alexander Ljungberg Jul 15 '09 at 22:42
  • 1
    @MikeHordecki, you can use `gc.get_referrers(fr.f_code)` instead of `gc.get_objects()`. – Gill Bates Mar 25 '20 at 19:13
  • 1
    @GillBates `gc.get_referrers(fr.f_code)` can give you more objects than you want(e.g. when a generator function is created for multiple times). Just use `gc.get_referrers(fr)` and it will return the correct callable. – laike9m Nov 09 '20 at 06:54

3 Answers3

1

To support all cases, including the function being part of a class or just a global function, there is no straight-forward way of doing this. You might be able to get the complete call stack and iterate your way down through globals(), but it wouldn't be nice...

The closest I can get you is this:

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!')

You'll be executing the exact same code, but it'll be in a new code wrapper (e.g., FunctionType.)

I suspect you want to be able to restore the state of your application based on a stack... Here's something that will at least call the functions as similarly as possible to the original calls (the closure is still left out, because if you could get closures from the frames, getting the function that was called would be pretty easy):

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)
Blixt
  • 49,547
  • 13
  • 120
  • 153
  • This solution is based on an assumption that the underlying callable object is a function defined in a global scope - object methods, for instance, are not handled properly, as co_name returns this method's name only. – Mike Hordecki Jul 15 '09 at 17:30
  • I see. I can't say I recommend you to do whatever it is that you're doing, but I've updated my answer now with the closest solution I can think of. – Blixt Jul 15 '09 at 18:02
1

A little ugly but here it is:

frame.f_globals[frame.f_code.co_name]

Full example:

#!/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()

Prints 'True'.

Alexander Ljungberg
  • 6,302
  • 4
  • 31
  • 37
  • +1: I believe `frame.f_globals` is more correct than `globals()`. Did you mean `print foo is x` though? – Blixt Jul 15 '09 at 17:29
  • 1
    Seconding my comment from Blixt's answer - what if the callable isn't present in f_globals - like object methods? Thanks for your answer, though. – Mike Hordecki Jul 15 '09 at 17:35
  • @Blixt Ah yes, I did mean 'print foo is x'. Edited that in there. @Mike yes, if it's not global this wouldn't work. – Alexander Ljungberg Jul 15 '09 at 18:02
0

Not really an answer, but a comment. I'd add it as a comment, but I don't have enough "reputation points".

For what it's worth, here's a reasonable (I think) use case for wanting to do this sort of thing.

My app uses gtk, and spins a lot of threads. As anyone whose done both of these at once knows, you can't touch the GUI outside the main thread. A typical workaround is to hand the callable that will touch the GUI to idle_add(), which will run it later, in the main thread, where it is safe. So I have a lot of occurrences of:

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

It would be just a bit shorter and easier (and more conducive to cut-n-paste) if I could just do

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

where idleIfNotMain() just returns False if we are in the main thread, but if not, it uses inspect (or whatever) to figure out the callable and args to hand off to idle_add(), then returns True. Getting the args I can figure out. Getting the callable appears not to be too easy. :-(

Phil
  • 101
  • 1
  • 5