views:

241

answers:

4

Given the function

def f():
    x, y = 1, 2 
    def get():
        print 'get'
    def post():
        print 'post'

is there a way for me to access its local get() and post() functions in a way that I can call them? I'm looking for a function that will work like so with the function f() defined above:

>>> get, post = get_local_functions(f)
>>> get()
'get'

I can access the code objects for those local functions like so

import inspect
for c in f.func_code.co_consts:
    if inspect.iscode(c):
        print c.co_name, c

which results in

get <code object get at 0x26e78 ...>
post <code object post at 0x269f8 ...>

but I can't figure out how to get the actual callable function objects. Is that even possible?

Thanks for your help,

Will.

+2  A: 

You can return functions just like any other object in Python:

def f():
    x, y = 1, 2 
    def get():
        print 'get'
    def post():
        print 'post'
    return (get, post)


get, post = f()

Hope this helps!

Note, though, that if you want to use your 'x' and 'y' variables in get() or post(), you should make them a list.

If you do something like this:

def f():
    x = [1]
    def get():
        print 'get', x[0]
        x[0] -= 1
    def post():
        print 'post', x[0]
        x[0] += 1
    return (get, post)

get1, post1 = f()
get2, post2 = f()

get1 and post1 will reference a different 'x' list than get2 and post2.

rledley
Yeah, that's actually what I'm doing at the moment. I was hoping to find a way to avoid returning the functions explicitly, if possible.
Will McCutchen
Will, to quote the **Zen of Python**: *"Explicit is better than implicit."* --and-- *"Special cases aren't special enough to break the rules."*
Evan Fosmark
Good point, Evan. The solution that I've got right now, which is essentially the same as this one except that it requires returning a dict instead of a tuple, works fine. I was just hoping to avoid the repetition of putting return dict(get=get, post=post, put=put, etc) at the bottom of a bunch of Django views. Thanks for the advice, though.
Will McCutchen
+2  A: 

You could use exec to run the code objects. For example, if you had f defined as above, then

exec(f.func_code.co_consts[3])

would give

get

as output.

Paul Wicks
Ah, so that's how you execute a code object! Unfortunately, for my actual use case I need to be able to pass arguments into the local functions. Is there a way for me to do that when calling exec()?
Will McCutchen
+1  A: 

The inner function objects don't exist before the function f() is executed. If you want to get them you'll have to construct them yourself. That is definitely non-trivial because they might be closures that capture variables from the scope of the function and will anyway require poking around in objects that should probably be regarded as implementation details of the interpreter.

If you want to collect the functions with less repetition I recommend one of the following approaches:

a) Just put the functions in a class definition and return a reference to that class. A collection of related functions that are accessed by name smells awfully like a class.

b) Create a dict subclass that has a method for registering functions and use that as a decorator.

The code for this would look something like this:

class FunctionCollector(dict):
    def register(self, func):
        self[func.__name__] = func

def f():
    funcs = FunctionCollector()
    @funcs.register
    def get():
        return 'get'
    @funcs.register
    def put():
        return 'put'
    return funcs

c) Poke around in locals() and filter out the function with inspect.isfunction. (usually not a good idea)

Ants Aasma
Re: "a) Just put the functions in a class definition and return a reference to that class. A collection of related functions that are accessed by name smells awfully like a class.", you're definitely right. See my comment on gavoja's answer below for an explanation for why I'm not using a class in this instance. Thanks for your explanations, though!
Will McCutchen
+1  A: 

You are pretty close of doing that - just missing new module:

import inspect
import new

def f():
    x, y = 1, 2
    def get():
        print 'get'
    def post():
        print 'post'

for c in f.func_code.co_consts:
    if inspect.iscode(c):
        f = new.function(c, globals())
        print f # Here you have your function :].

But why the heck bother? Isn't it easier to use class? Instantiation looks like a function call anyway.

gavoja
That does the trick, as long as the nested functions aren't closures. Thanks!You and Ants Aasma are right, though, that I should just be using a class for this. The reason that I'm not is that this is Django project that already has a lot of view decorators used for custom authentication, etc., and I didn't want to adapt all of those decorators to work with methods. So I'm sort of half-assed simulating a class-based approach to RESTful views (like http://github.com/jpwatts/django-restviews/tree/master).
Will McCutchen