Есть ли способ ссылаться на текущую функцию в python?

Я хочу, чтобы функция ссылалась на себя. например, быть рекурсивным.

Поэтому я делаю что-то вроде этого:

def fib(n): return n if n <= 1 else fib(n-1)+fib(n-2) 

Это нормально в большинстве случаев, но fib не на самом деле ссылается на себя; это относится к привязке fib в охватывающем блоке. Поэтому, если по какой-то причине fib переназначен, он сломается:

 >>> foo = fib >>> fib = foo(10) >>> x = foo(8) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fib TypeError: 'int' object is not callable 

Как я могу предотвратить это (изнутри fib ), если это вообще возможно? Насколько я знаю, имя fib не существует до полного выполнения функции-определения; Есть ли обходные пути?

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

4 Solutions collect form web for “Есть ли способ ссылаться на текущую функцию в python?”

Как сказал абамер, «… внутри проблемы нет».

Вот мой подход:

 def fib(n): def fib(n): return n if n <= 1 else fib(n-1)+fib(n-2) return fib(n) 

Я бы сделал декоратор для этого

 from functools import wraps def selfcaller(func): @wraps(func) def wrapper(*args, **kwargs): return func(wrapper, *args, **kwargs) return wrapper 

И используйте его как

 @selfcaller def fib(self, n): return n if n <= 1 else self(n-1)+self(n-2) 

Это на самом деле читаемый способ определения Комбинатора с фиксированной точкой (или Y Combinator):

 fix = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg))) 

Применение:

 fib = fix(lambda self: lambda n: n if n <= 1 else self(n-1)+self(n-2)) 

или:

 @fix def fib(self): return lambda n: n if n <= 1 else self(n-1)+self(n-2) 

Связывание здесь происходит в формальном параметре, поэтому проблема не возникает.

Невозможно делать то, что вы пытаетесь сделать. Вы правы в том, что fib не существует до того, как будет выполняться определение функции (или, что еще хуже, оно существует, но относится к чему-то совершенно другому …), что означает, что внутри не существует обходного пути, который может работать. *

Однако, если вы готовы отказаться от этого требования, есть обходные пути, которые действительно работают. Например:

 def _fibmaker(): def fib(n): return n if n <= 1 else fib(n-1)+fib(n-2) return fib fib = _fibmaker() del _fibmaker 

Теперь fib относится к привязке в замыкании из локальной среды вызова к _fibmaker . Конечно, даже это можно заменить, если вы действительно этого хотите, но это непросто (атрибут fib.__closure__ не доступен для записи, это кортеж, поэтому вы не можете заменить ни одну из его ячеек, а cell_contents каждой ячейки является атрибутом readonly, …), и вы не сможете сделать это случайно.

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


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

 def selfcaller(func): env = {} newfunc = types.FunctionType(func.__code__, globals=env) env['self'] = newfunc return newfunc @selfcaller def fib(n): return n if n <= 1 else self(n-1)+self(n-2) 

Конечно, это не будет работать на функцию, которая имеет любые свободные переменные, связанные с globals , но вы можете исправить это с некоторой интроспекцией. И, хотя мы и находимся в этом, мы также можем устранить необходимость использования функции self в определении функции:

 def selfcaller(func): env = dict(func.__globals__) newfunc = types.FunctionType(func.__code__, globals=env) env[func.__code__.co_name] = newfunc return newfunc 

Это Python 3.x-specific; некоторые имена атрибутов различаются в 2.x, но в остальном это одно и то же.

Это по-прежнему не полностью на 100%. Например, если вы хотите использовать его на методах, чтобы они все равно могли себя вызвать, даже если класс или объект переопределили их имя, вам нужны несколько разные трюки. И есть некоторые патологические случаи, которые могут потребовать создания нового CodeType из func.__code__.co_code . Но основная идея такая же.


* Что касается Python, то до тех пор, пока имя не будет связано, оно не существует … но, очевидно, под обложками интерпретатор должен знать имя функции, которую вы определяете. И, по крайней мере, некоторые переводчики предлагают непривлекательные способы получить эту информацию.

Например, в CPython 3.x вы можете очень легко получить имя текущей функции – это просто sys._getframe().f_code.co_name .

Конечно, это не принесет вам ничего хорошего, потому что ничто (или неправильное) не связано с этим именем. Но обратите внимание, что f_code там. Это объект кода текущего кадра. Конечно, вы не можете напрямую вызвать объект кода, но вы можете сделать это косвенно, либо создав из него новую функцию, либо используя bytecodehacks .

Например:

 def fib2(n): f = sys._getframe() fib2 = types.FunctionType(f.f_code, globals=globals()) return n if n<=1 else fib2(n-1)+fib2(n-2) 

Опять же, это не будет обрабатывать каждый патологический случай … но единственный способ, которым я могу это сделать, – фактически сохранить круговую ссылку на фрейм или, по крайней мере, на его глобальные переменные (например, путем передачи globals=f.f_globals ) которая кажется очень плохой идеей.

Смотрите Frame Hacks для более умных вещей, которые вы можете сделать.


Наконец, если вы готовы полностью выйти из Python, вы можете создать захват импорта, который предварительно обрабатывает или компилирует ваш код из расширенного Python с, скажем, defrec в чистый Python и / или байт-код.

И если вы думаете: «Но похоже, что это было бы намного лучше, чем макрос, чем как препроцессорный хак, если бы у Python были макросы» … тогда вы, вероятно, предпочтете использовать препроцессорный хак, который дает макросы Python, например MacroPy , а затем напишите ваши расширения как макросы.

Кто-то попросил меня для решения на основе макросов, поэтому вот оно:

 # macropy/my_macro.py from macropy.core.macros import * macros = Macros() @macros.decorator() def recursive(tree, **kw): tree.decorator_list = [] wrapper = FunctionDef( name=tree.name, args=tree.args, body=[], decorator_list=tree.decorator_list ) return_call = Return( Call( func = Name(id=tree.name), args = tree.args.args, keywords = [], starargs = tree.args.vararg, kwargs = tree.args.kwarg ) ) return_call = parse_stmt(unparse_ast(return_call))[0] wrapper.body = [tree, return_call] return wrapper 

Это можно использовать следующим образом:

 >>> import macropy.core.console 0=[]=====> MacroPy Enabled <=====[]=0 >>> from macropy.my_macro import macros, recursive >>> @recursive ... def fib(n): ... return n if n <= 1 else fib(n-1)+fib(n-2) ... >>> foo = fib >>> fib = foo(10) >>> x = foo(8) >>> x 21 

Это в основном делает именно обертывание, которое дал hus787:

  • Создайте новый оператор, который return fib(...) , который использует список аргументов исходной функции как ...
  • Создайте новый def , с тем же именем, с теми же аргументами, тот же самый decorator_list, что и старый
  • Поместите старую функцию вместе, а затем оператор return , в тело новой функцииdeff
  • Разделите оригинальную функцию его декораторов (я предполагаю, что вы захотите украсить обертку вместо этого)

Параметр parse_stmt(unparse_ast(return_call))[0] – это быстрый взлом, чтобы заставить работать (вы на самом деле не можете просто скопировать argument AST из списка параметров функции и использовать их в Call AST), но это просто подробно.

Чтобы показать, что это действительно так, вы можете добавить print unparse_ast чтобы увидеть, как выглядит преобразованная функция:

 @macros.decorator() def recursive(tree, **kw): ... print unparse_ast(wrapper) return wrapper 

которые при запуске, как указано выше, печатают

 def fib(n): def fib(n): return (n if (n <= 1) else (fib((n - 1)) + fib((n - 2)))) return fib(n) 

Похоже, что вы хотите! Он должен работать для любой функции с несколькими аргументами, kwargs, дефолтами и т. Д., Но я слишком ленив, чтобы проверить. Работа с АСТ немного сложна , и MacroPy по-прежнему супер-экспериментально, но я думаю, что это довольно аккуратно.

  • Замена некотируемых слов только в Python
  • Intermittent 403 «Эта услуга недоступна в вашей стране» Ошибка для выполнения API
  • Строка заменяет гласные в Python?
  • Сохраняется только последняя итерация цикла while
  • переменная длина% s с оператором% в python
  • Установка GOOGLE_APPLICATION_CREDENALAL для командной строки BigQuery Python
  • Как получить надежный счет символа Юникода в Python?
  • Скрытие данных в классе Python
  • Python - лучший язык программирования в мире.