Запись сценария, который будет выполняться несколько раз в том же сеансе, - это странная вещь.
Я могу понять, почему вы хотите это сделать, но это все еще странно, и я не думаю, что для кода было бы необоснованным выставлять эту странность, выглядя немного странно и с комментарием, объясняющим это.
Однако вы сделали вещи более уродливыми, чем необходимо.
Во-первых, вы можете просто сделать это:
@functools32.lru_cache()
def _square(x):
print "Squaring", x
return x*x
try:
safe_square_2
except NameError:
safe_square_2 = _square
Там нет никакого вреда в придании кэш для нового _square
определения. Это не будет тратить время или больше, чем на несколько байт памяти, и, что самое важное, это не повлияет на кеш на предыдущем_square
. В этом весь смысл закрытия.
Там это потенциальная проблема здесь с рекурсивными функциями. Это уже связано с тем, как вы работаете, и кеш никак не добавляет к нему, но вы можете только заметить из-за кеша, поэтому я объясню его и покажу, как его исправить. Рассмотрим эту функцию:
@lru_cache()
def _fact(n):
if n < 2:
return 1
return _fact(n-1) * n
При повторном EXEC сценарий, даже если у вас есть ссылка на старый _fact
, это будет в конечном итоге вызова нового _fact
, потому что это доступ к _fact
как глобальное имя. Это не имеет никакого отношения к @lru_cache
; удалите это, и старая функция будет еще в конечном итоге вызывает новый _fact
.
Но если вы используете переименование трюк выше, вы можете просто позвонить переименованный вариант:
@lru_cache()
def _fact(n):
if n < 2:
return 1
return fact(n-1) * n
Теперь старый _fact
будет вызывать fact
, который до сих пор старый _fact
. Опять же, это работает одинаково с или без декодера кеша.
Помимо этого первоначального трюка, вы можете разложить весь этот узор в простой декоратор. Я объясню шаг за шагом ниже или посмотрю this blog post.
В любом случае, даже с менее уродливой версией он все еще немного уродлив и подробен. И если вы делаете это десятки раз, мое «хорошо, это должно смотреть бит уродливое оправдание будет очень тонким. Таким образом, вы захотите справиться с этим так же, как всегда, вы уклоняетесь от уродства: оберните его в функцию.
Вы не можете передавать имена как объекты в Python. И вы не хотите использовать отвратительный фрейм-хак, чтобы справиться с этим. Поэтому вам придется передавать имена вокруг как строки. икэ это:
globals().setdefault('fact', _fact)
globals
функция просто возвращает глобальный словарь текущего осциллографа. Который является dict
, что означает, что он имеет метод setdefault
, что означает, что он установит глобальное имя fact
на значение _fact
, если оно еще не имеет значения, но ничего не сделает, если это произойдет. Это именно то, что вы хотели. (Вы также можете использовать setattr
на текущем модуле, но я думаю, что этот способ подчеркивает, что сценарий должен быть (неоднократно) выполнен в чужой области, не используется в качестве модуля.)
Итак, вот что завернуто в функции:
def new_bind(name, value):
globals().setdefault(name, value)
... который вы можете превратить это в декоратора почти тривиальным:
def new_bind(name):
def wrap(func):
globals().setdefault(name, func)
return func
return wrap
, который можно использовать, как это:
@new_bind('foo')
def _foo():
print(1)
Но подождите, есть еще! func
, что new_bind
получает будет __name__
, не так ли? Если вы будете придерживаться именования, как и о том, что «частное» имя должно быть «общественное» имя с _
приставкой, мы можем сделать это:
def new_bind(func):
assert func.__name__[0] == '_'
globals().setdefault(func.__name__[1:], func)
return func
И вы можете увидеть, где это происходит:
@new_bind
@lru_cache()
def _square(x):
print "Squaring", x
return x*x
Существует одна незначительная проблема: если вы используете любые другие декораторы, которые не будут эффективно обертывать функцию, они нарушат ваше соглашение об именах. Так ... просто не делай этого. :)
И я думаю, что это работает именно так, как вы хотите в каждом случае края. В частности, если вы отредактировали источник и хотите заставить новое определение с новым кешем, вы просто del square
перед тем, как перезапустить файл, и он работает.
И, конечно, если вы хотите, чтобы объединить эти два декораторов в единое целое, это тривиально, чтобы сделать это, и назвать его non_resetting_lru_cache
.
Однако я бы сохранил их отдельно. Я думаю, что более очевидно, что они делают. И если вы когда-нибудь захотите обернуть еще один декоратор вокруг @lru_cache
, вы, вероятно, все еще захотите, чтобы @new_bind
был самым внешним декоратором, не так ли?
Что делать, если вы хотите поместить new_bind
в модуль, который можно импортировать? Тогда это не сработает, потому что оно будет ссылаться на глобалы этого модуля, а не на тот, который вы сейчас пишете.
Вы можете исправить это, явно передав свой globals
dict или ваш объект модуля или имя вашего модуля в качестве аргумента, например @new_bind(__name__)
, чтобы он мог найти ваши глобальные переменные вместо своего. Но это уродливо и повторяемо.
Вы также можете исправить это с помощью уродливого фрейма. По крайней мере, в CPython, sys._getframe()
может быть использован для получения кадра вызывающего абонента, и frame objects
есть ссылка на их глобал пространство имен, так:
def new_bind(func):
assert func.__name__[0] == '_'
g = sys._getframe(1).f_globals
g.setdefault(func.__name__[1:], func)
return func
Обратите внимание на большом ящике в документации, которая говорит вам это «деталь реализации ", который может применяться только к CPython и предназначен только для внутренних и специализированных целей. Возьмите это всерьез. Всякий раз, когда у кого-то есть классная идея для stdlib или встроенных функций, которые могут быть реализованы в чистом Python, но только с использованием _getframe
, он обычно обрабатывается почти так же, как идея, которая вообще не может быть реализована в чистом Python. Но если вы знаете, что делаете, и хотите использовать это, и вам будут нужны только текущие версии CPython, это сработает.
Ваши изменения на самом деле не работают, если только вы не перезапускаете сценарий в одном сеансе интерпретатора Python, например, 'execfile'-ing. – abarnert
См. Ниже; Я удалил слово «persistent», чтобы быть более четким. Повторное выполнение сценария в одном сеансе интерпретатора Python - это то, что я снимаю здесь. – kuzzooroo
Это очень странный способ запуска скриптов, но я понимаю, почему вы хотите это сделать. Позвольте мне написать еще один ответ с некоторыми комментариями. – abarnert