How to get current function into a variable?

Как я могу получить переменную, которая содержит текущую выполняемую функцию в 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, или чего-то подобного.

10 ответов

Решение

Кадр стека сообщает нам, в каком объекте кода мы находимся. Если мы сможем найти объект функции, который ссылается на этот объект кода в своем __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()

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

Это то, что вы просили, как можно ближе. Протестировано в 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>

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

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

Например:

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

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

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

Вот еще одна возможность: декоратор, который неявно передает ссылку на вызываемую функцию в качестве первого аргумента (аналогично 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()

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

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

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

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

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

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

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

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

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

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

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

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)

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

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

Хорошо, после прочтения вопроса и комментариев снова, я думаю, что это достойный тестовый пример:

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

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

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

Вот вариант (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>

Исправление моего предыдущего ответа, потому что проверка subdict уже работает с "<=", вызванным для dict_items, и дополнительные вызовы set() приводят к проблемам, если есть 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]
Другие вопросы по тегам