tags:

views:

110

answers:

2

Hi!

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

+1  A: 

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
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
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
A: 

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
+1: I believe `frame.f_globals` is more correct than `globals()`. Did you mean `print foo is x` though?
Blixt
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
@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