Анализ вложенных (локальных) функций данной функции в Python

Учитывая функцию

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

есть ли способ получить доступ к его локальным функциям get() и post() таким образом, чтобы я мог их вызывать? Я ищу функцию, которая будет работать так с функцией f(), определенной выше:

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

Я могу получить доступ к объектам кода для этих локальных функций, например так

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

что приводит к

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

но я не могу понять, как получить фактические вызываемые объекты функций. Это вообще возможно?

Спасибо за вашу помощь,

Буду.


person Will McCutchen    schedule 05.08.2009    source источник
comment
Я должен отметить, что для моего фактического варианта использования мне нужно иметь возможность вызывать локальные/вложенные функции следующим образом: get(request, *args, **kwargs)   -  person Will McCutchen    schedule 05.08.2009


Ответы (4)


Вы очень близки к этому - просто отсутствует new модуль:

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 :].

Но на кой черт заморачиваться? Не проще ли использовать класс? В любом случае создание экземпляра выглядит как вызов функции.

person gavoja    schedule 05.08.2009
comment
Это помогает, если вложенные функции не являются замыканиями. Спасибо! Однако вы и Антс Аасма правы в том, что я должен просто использовать для этого класс. Причина, по которой я этого не делаю, заключается в том, что это проект Django, в котором уже есть множество декораторов представлений, используемых для пользовательской аутентификации и т. д., и я не хотел адаптировать все эти декораторы для работы с методами. Так что я как бы наполовину имитирую основанный на классах подход к представлениям RESTful (например, github.com/jpwatts/django-restviews/tree/master). - person Will McCutchen; 06.08.2009
comment
Я считаю, что это работает только потому, что у вас есть f в текущем модуле. - person Quant; 14.03.2014

Вы можете возвращать функции, как и любой другой объект в Python:

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


get, post = f()

Надеюсь это поможет!

Обратите внимание, однако, что если вы хотите использовать свои переменные «x» и «y» в get() или post(), вы должны сделать их списком.

Если вы сделаете что-то вроде этого:

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 и post1 будут ссылаться на другой список «x», чем get2 и post2.

person rledley    schedule 05.08.2009
comment
Да, собственно этим я и занимаюсь в данный момент. Я надеялся найти способ избежать явного возврата функций, если это возможно. - person Will McCutchen; 05.08.2009
comment
Уилл, цитируя Дзен Python: Явное лучше, чем неявное. --и-- Особые случаи не настолько особенные, чтобы нарушать правила.< /я> - person Evan Fosmark; 05.08.2009
comment
Хороший вопрос, Эван. Решение, которое у меня есть прямо сейчас, которое по сути такое же, как и это, за исключением того, что оно требует возврата словаря вместо кортежа, отлично работает. Я просто надеялся избежать повторения размещения return dict(get=get, post=post, put=put и т. д.) внизу кучи представлений Django. Но спасибо за совет. - person Will McCutchen; 06.08.2009

Вы можете использовать exec для запуска объектов кода. Например, если вы определили f, как указано выше, то

exec(f.func_code.co_consts[3])

даст

get

как вывод.

person Paul Wicks    schedule 05.08.2009
comment
Ах, так вот как вы выполняете объект кода! К сожалению, для моего фактического варианта использования мне нужно иметь возможность передавать аргументы в локальные функции. Есть ли способ сделать это при вызове exec()? - person Will McCutchen; 05.08.2009

Объекты внутренней функции не существуют до выполнения функции f(). Если вы хотите получить их, вам придется построить их самостоятельно. Это определенно нетривиально, потому что они могут быть замыканиями, которые захватывают переменные из области действия функции и в любом случае потребуют копания в объектах, которые, вероятно, следует рассматривать как детали реализации интерпретатора.

Если вы хотите собирать функции с меньшим количеством повторений, я рекомендую один из следующих подходов:

а) Просто поместите функции в определение класса и верните ссылку на этот класс. Набор связанных функций, доступ к которым осуществляется по имени, ужасно пахнет классом.

б) Создайте подкласс dict, у которого есть метод для регистрации функций, и используйте его в качестве декоратора.

Код для этого будет выглядеть примерно так:

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) Покопайтесь в locals() и отфильтруйте функцию с помощью inspect.isfunction. (обычно не лучшая идея)

person Ants Aasma    schedule 05.08.2009
comment
Re: а) Просто поместите функции в определение класса и верните ссылку на этот класс. Набор связанных функций, доступ к которым осуществляется по имени, ужасно пахнет классом. Вы определенно правы. См. мой комментарий к ответу gavoja ниже для объяснения того, почему я не использую класс в этом случае. Тем не менее, спасибо за ваши объяснения! - person Will McCutchen; 06.08.2009