Как функция может быть устойчивой к себе?

[Код в исходной версии был сильно испорчен. Даже после того, как я исправил код, на этом посту остались несколько весьма запутанных опечаток. Я верю, что, наконец, все они исправлены. Профильные извинения.]

Два вызова alias ниже производят разные выходы, потому что объект, связанный с переменной my_own_id, изменяется между двумя вызовами:

>>> def my_own_id():
...     me = my_own_id
...     return id(me)
... 
>>> alias = my_own_id
>>> alias()
4301701560
>>> my_own_id = None
>>> alias()
4296513024

Что я могу назначить me в определении my_own_id, чтобы его вывод оставался неизменным после последующих переопределений переменной my_own_id? (IOW, так что внутренняя переменная me всегда относится к одному и тому же объекту функции?)

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

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

Ответ 1

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

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

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


>>> @save_id
def my_own_id(me): # me is passed implicitly by save_id
    return id(me)

>>> alias = my_own_id
>>> alias()
40775152
>>> my_own_id = 'foo'
>>> alias()
40775152

Ответ 2

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

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

from functools import wraps

def know_thyself(func):
   @wraps(func):
   def new_func(*args, **kwargs):
        my_own_id = func
        return func(*args, **kwargs)
   return new_func

И может использоваться как:

>>> @know_thyself
... def my_own_id():
...     me = my_own_id
...     return id(me)
... 

Существует еще один возможный подход, далекий от того, чтобы быть чистым, используя интроспекцию кадра и перестроить новую функцию, повторно используя один и тот же объектный код. Я использовал это на этом посту о самореферентном лямбда-выражении в Python: http://metapython.blogspot.com.br/2010/11/recursive-lambda-functions.html

Ответ 3

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

>>> def be_known():
...     global my_own_id
...     def _my_own_id():
...         return id(_my_own_id)
...     my_own_id = _my_own_id
... 
>>> be_known()
>>> my_own_id()
140685505972568
>>> alias, my_own_id = my_own_id, None
>>> alias()
140685505972568

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

Ответ 4

Подход декоратора, вероятно, самый лучший. Вот еще кое-что для удовольствия:

  • Возьмите один из аргументов функции для предоставления статической переменной.

    def fn(fnid=None):
        print "My id:", fnid
    fn.func_defaults = (id(fn),)
    
  • Есть несколько способов получить текущую функцию здесь: Код Python для получения текущей функции в переменной?; большинство из них предполагает поиск текущего кадра .f_code в различных местах. Они работают без каких-либо изменений в исходной функции.

Ответ 5

import inspect
def _this_fn():
    try:
        frame = inspect.currentframe().f_back
        code = frame.f_code
        return frame.f_globals[code.co_name]
    finally:
        del code
        del frame

def myfunc(*parms):
    print _this_fn()

>>> myfunc(1)
<function myfunc at 0x036265F0>
>>> myfunc
<function myfunc at 0x036265F0>

Ответ 6

Это из-за области

>>> def foo():
...     x = foo
...     print x
... 
>>> foo()
<function foo at 0x10836e938>
>>> alias = foo
>>> alias()
<function foo at 0x10836e938>
>>> foo = None
>>> alias()
None
>>> foo = []
>>> alias()
[]
>>> del foo
>>> alias()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
NameError: global name 'foo' is not defined
>>> 

Ответ 7

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

>>> def my_own_id(me=[None]):
    if not me[0]:
        me[0] = my_own_id
    return id(me[0])

>>> alias = my_own_id
>>> alias()
40330928
>>> my_own_id = None
>>> alias()
40330928

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