Как функция может быть устойчивой к себе?
[Код в исходной версии был сильно испорчен. Даже после того, как я исправил код, на этом посту остались несколько весьма запутанных опечаток. Я верю, что, наконец, все они исправлены. Профильные извинения.]
Два вызова 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
Это требует осторожности с вашей стороны, чтобы никогда не вызывать функцию с параметром.