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]