Подтвердить что ты не робот

Как получить текущую функцию в переменную?

Как я могу получить переменную, которая содержит текущую исполняемую функцию в Python? Мне не нужно имя функции. Я знаю, что могу использовать inspect.stack для получения текущего имени функции. Я хочу фактический вызываемый объект. Можно ли это сделать, не используя inspect.stack для получения имени функции, а затем eval имя, чтобы получить вызываемый объект?

Изменить: У меня есть причина сделать это, но это даже не очень удачный вариант. Я использую plac для анализа аргументов командной строки. Вы используете его, выполнив plac.call(main), который генерирует объект ArgumentParser из сигнатуры функции "main" . Внутри "main" , если есть проблема с аргументами, я хочу выйти с сообщением об ошибке, которое включает текст справки из объекта ArgumentParser, что означает, что мне нужно напрямую обращаться к этому объекту, вызвав plac.parser_from(main).print_help(). Было бы неплохо сказать: plac.parser_from(get_current_function()).print_help(), так что я не полагаюсь на функцию, называемую "main" . Прямо сейчас, моя реализация "get_current_function" будет:

import inspect    
def get_current_function():
    return eval(inspect.stack()[1][3])

Но эта реализация зависит от функции, имеющей имя, которое, я полагаю, не слишком обременительно. Я никогда не буду делать plac.call(lambda ...).

В конечном итоге было бы более полезно попросить автора plac реализовать метод print_help для печати текста справки функции, которая была недавно вызвана с помощью plac или чего-то подобного.

4b9b3361

Ответ 1

Фрейм стека сообщает нам, в каком объекте кода мы находимся. Если мы сможем найти объект функции, который ссылается на этот объект кода в его __code__, мы нашли функцию.

К счастью, мы можем спросить у сборщика мусора, какие объекты содержат ссылку на наш объект кода, и просеивать их, вместо того, чтобы пересекать каждый активный объект в мире Python. Обычно есть только несколько ссылок на объект кода.

Теперь функции могут совместно использовать объекты кода и делать это в случае, когда вы возвращаете функцию из функции, т.е. Замыкания. Когда имеется более одной функции с использованием заданного объекта кода, мы не можем определить, какая функция она есть, поэтому мы возвращаем None.

import inspect, gc

def giveupthefunc():
    frame = inspect.currentframe(1)
    code  = frame.f_code
    globs = frame.f_globals
    functype = type(lambda: 0)
    funcs = []
    for func in gc.get_referrers(code):
        if type(func) is functype:
            if getattr(func, "__code__", None) is code:
                if getattr(func, "__globals__", None) is globs:
                    funcs.append(func)
                    if len(funcs) > 1:
                        return None
    return funcs[0] if funcs else None

Некоторые тестовые примеры:

def foo():
    return giveupthefunc()

zed = lambda: giveupthefunc()

bar, foo = foo, None

print bar()
print zed()

Я не уверен в характеристиках этого, но я думаю, что это должно быть хорошо для вашего случая использования.

Ответ 2

Это то, о чем вы просили, как можно ближе. Протестировано в версиях python 2.4, 2.6, 3.0.

#!/usr/bin/python
def getfunc():
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    caller = caller.f_back
    from pprint import pprint
    func = caller.f_locals.get(
            func_name, caller.f_globals.get(
                func_name
        )
    )

    return func

def main():
    def inner1():
        def inner2():
            print("Current function is %s" % getfunc())
        print("Current function is %s" % getfunc())
        inner2()
    print("Current function is %s" % getfunc())
    inner1()


#entry point: parse arguments and call main()
if __name__ == "__main__":
    main()

Вывод:

Current function is <function main at 0x2aec09fe2ed8>
Current function is <function inner1 at 0x2aec09fe2f50>
Current function is <function inner2 at 0x2aec0a0635f0>

Ответ 3

Недавно я потратил много времени, пытаясь сделать что-то подобное и в итоге ушел от него. Там много угловых случаев.

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

Например:

def recursive(*args, **kwargs):
    me = recursive

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

Чтобы получить функцию, выполняющуюся выше стека вызовов, я не мог придумать ничего, что можно было бы сделать надежно.

Ответ 4

Вот еще одна возможность: декоратор, который неявно передает ссылку на вызываемую функцию в качестве первого аргумента (аналогично self в методах связанных экземпляров). Вы должны украсить каждую функцию, которую хотите получить такую ​​ссылку, но "явное лучше, чем неявное", как говорится.

Конечно, у него есть все недостатки декораторов: другой вызов функции немного ухудшает производительность, а подпись завернутой функции больше не видна.

import functools

def gottahavethatfunc(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(func, *args, **kwargs)

    return wrapper

Тестовый пример иллюстрирует, что украшенная функция по-прежнему получает ссылку на себя, даже если вы меняете имя, к которому привязана функция. Это связано с тем, что вы только меняете привязку функции-обертки. Это также иллюстрирует его использование с помощью лямбда.

@gottahavethatfunc
def quux(me):
    return me

zoom = gottahavethatfunc(lambda me: me)

baz, quux = quux, None

print baz()
print zoom()

При использовании этого декоратора с помощью метода экземпляра или класса метод должен принимать ссылку на функцию как первый аргумент, а традиционный self - второй.

class Demo(object):

    @gottahavethatfunc
    def method(me, self):
        return me

print Demo().method()

Декоратор полагается на замыкание, чтобы удерживать ссылку на обернутую функцию в обертке. Создание закрытия напрямую может быть более чистым и не будет иметь накладных расходов на дополнительный вызов функции:

def my_func():
    def my_func():
        return my_func
    return my_func
my_func = my_func()

Внутри внутренней функции имя my_func всегда относится к этой функции; его значение не зависит от глобального имени, которое может быть изменено. Затем мы просто "поднимаем" эту функцию в глобальное пространство имен, заменяя ссылку на внешнюю функцию. Работает и в классе:

class K(object):
    def my_method():
        def my_method(self):
             return my_method
        return my_method
    my_method = my_method()

Ответ 5

Я просто определяю в начале каждой функции "ключевое слово", которое является просто ссылкой на фактическое имя функции. Я просто делаю это для любой функции, если это нужно или нет:

def test():
    this=test
    if not hasattr(this,'cnt'):
        this.cnt=0
    else:
        this.cnt+=1
    print this.cnt

Ответ 6

В стеке вызовов не содержится ссылка на саму функцию - хотя работающий кадр является ссылкой на объект кода, который является кодом, связанным с данной функцией.

(Функции - это объекты с кодом и некоторая информация об их среде, таких как закрытие, имя, словарь глобалов, строка документа, параметры по умолчанию и т.д.).

Поэтому, если вы используете обычную функцию, вам лучше использовать свое имя в словаре глобалов для вызова себя, как было указано.

Если вы используете какой-либо динамический или лямбда-код, в котором вы не можете использовать имя функции, единственным решением является перестройка другого функционального объекта, который повторно использует текущий текущий объект кода и вызывает эту новую функцию.

Вы потеряете пару вещей, например аргументы по умолчанию, и может быть трудно заставить их работать с закрытием (хотя это можно сделать).

Я написал сообщение в блоге о том, как это делать - вызывать анонимные функции внутри себя - я надеюсь, что код там может вам помочь:

http://metapython.blogspot.com/2010/11/recursive-lambda-functions.html

На стороне примечания: избегайте использования o inspect.stack - он слишком медленный, так как он перестраивает много информации каждый раз, когда он вызывается. предпочитают использовать проверочный кадр для обработки фреймов кода.

Это может показаться сложным, но сам код очень короткий - я вставляю его ниже. Сообщение выше содержит дополнительную информацию о том, как это работает.

from inspect import currentframe
from types import FunctionType

lambda_cache = {}

def myself (*args, **kw):
    caller_frame = currentframe(1)
    code = caller_frame.f_code
    if not code in lambda_cache:
        lambda_cache[code] = FunctionType(code, caller_frame.f_globals)
    return lambda_cache[code](*args, **kw)

if __name__ == "__main__":
    print "Factorial of 5", (lambda n: n * myself(n - 1) if n > 1 else 1)(5)

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

Ответ 7

sys._getframe (0).f_code возвращает именно то, что вам нужно: выполняемый программный объект. Имея объект кода, вы можете получить имя с именем codeobject.co_name

Ответ 8

ОК после прочтения вопроса и комментариев еще раз, я думаю, что это достойный тестовый пример:

def foo(n):
  """ print numbers from 0 to n """
  if n: foo(n-1)
  print n

g = foo    # assign name 'g' to function object
foo = None # clobber name 'foo' which refers to function object
g(10)      # dies with TypeError because function object tries to call NoneType

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

def selfbind(f):
   """ Ensures that f original function name is always defined as f when f is executed """
   oname = f.__name__
   def g(*args, **kwargs):

      # Clobber global namespace
      had_key = None
      if globals().has_key(oname):
         had_key = True
         key = globals()[oname]
      globals()[oname] = g

      # Run function in modified environment
      result = f(*args, **kwargs)

      # Restore global namespace
      if had_key: 
         globals()[oname] = key
      else:
         del globals()[oname]

      return result

   return g

@selfbind
def foo(n):
   if n: foo(n-1)
   print n

g = foo   # assign name 'g' to function object
foo = 2   # calling 'foo' now fails since foo is an int
g(10)     # print from 0..10, even though foo is now an int
print foo # prints 2 (the new value of Foo)

Я уверен, что я не продумал все варианты использования. Самая большая проблема, которую я вижу, - это объект функции, намеренно изменяющий то, на что указывает его собственное имя (операция, которая будет перезаписана декоратором), но это должно быть нормально, пока рекурсивная функция не переопределяет свое собственное имя в середине рекурсии.

Не уверен, что мне когда-нибудь понадобится это сделать, но подумать было интересно.

Ответ 9

Здесь вариант (Python 3.5.1) ответа get_referrers(), который пытается различать замыкания, которые используют один и тот же объект кода:

import functools
import gc
import inspect

def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        set(inspect.getclosurevars(referer).nonlocals.items()) <=
        set(frame.f_locals.items())][0]

def f1(x):
    def f2(y):
        print(get_func())
        return x + y
    return f2

f_var1 = f1(1)
f_var1(3)
# <function f1.<locals>.f2 at 0x0000017235CB2C80>
# 4

f_var2 = f1(2)
f_var2(3)
# <function f1.<locals>.f2 at 0x0000017235CB2BF8>
# 5

def f3():
    print(get_func())    

f3()
# <function f3 at 0x0000017235CB2B70>

def wrapper(func):
    functools.wraps(func)
    def wrapped(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped

@wrapper
def f4():
    print(get_func())    

f4()
# <function f4 at 0x0000017235CB2A60>

f5 = lambda: get_func()    

print(f5())
# <function <lambda> at 0x0000017235CB2950>

Ответ 10

Исправление моего предыдущего ответа, потому что проверка поддиапазона уже работает с "< =", вызываемой в dict_items, а вызовы дополнительного набора() приводят к проблемам, если есть значения dict-значений, которые сами определяют:

import gc
import inspect


def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        inspect.getclosurevars(referer).nonlocals.items() <=
        frame.f_locals.items()][0]