Рассмотрим следующий код:
def CalcSomething(a):
if CalcSomething._cache.has_key(a):
return CalcSomething._cache[a]
CalcSomething._cache[a] = ReallyCalc(a)
return CalcSomething._cache[a]
CalcSomething._cache = { }
Это самый простой способ, который я могу придумать для имитации локальной статической переменной в python. Меня беспокоит то, что CalcSomething._cache упоминается вне определения функции, но альтернативой может быть что-то вроде этого:
if not hasattr(CalcSomething, "_cache"):
setattr(CalcSomething, "_cache", { } )
внутри определения функции, что действительно громоздко.
Есть способ более элегантный?
[РЕДАКТИРОВАТЬ]
Чтобы прояснить, этот вопрос не о локальных кэшах функций, как можно предположить из приведенного выше примера. Вот еще один короткий пример, когда может пригодиться «статический локальный»:
def ParseString(s):
return ParseString._parser.parse(s)
# Create a Parser object once, which will be used for all parsings.
# Assuming a Parser object is heave on resources, for the sake of this example.
ParseString._parser = Parser()
ваш второй пример хуже первого. Вы можете вернуть datetime.datetime.strptime (dts, "% m-% d-% Y% H:% M:% S"), поскольку strptime - это метод класса, который создает новый объект datetime. На самом деле нет необходимости создавать объект datetime.
Насчет второго примера вы правы. Изменил это.






Превратите его в декоратора.
def static_var(var_name, initial_value):
def _set_var(obj):
setattr(obj, var_name, initial_value)
return obj
return _set_var
@static_var("_cache", {})
def CalcSomething(a):
...
Вы действительно считаете это «более элегантным способом»? Нет, серьезно ;-)
Я бы не стал считать статические переменные элегантным шаблоном для использования в Python, но декоратор, по крайней мере, инкапсулирует технические детали настройки атрибута.
+1 за классное использование атрибутов Python. @Pau Oyster: понятия не имею, почему вы не считаете это элегантным. Если вам нужен нехакерский способ сделать это, просто используйте класс Calculator с переменной экземпляра. Вы запросили локальную статику, что по своей сути некрасиво.
Что ж, будучи прагматиком, а не пуристом (плюс пишущий код на C++ с 1991 ..), я не считаю локальную статику уродливой.
Вам нужно указать return obj в конце _set_var. И хотя я знаю, что вы пытаетесь указать, что _set_var не является общедоступным, нет необходимости искажать имя (с добавленным подчеркиванием), поскольку оно актуально только внутри static_var.
Подумайте о написании декоратора, который будет поддерживать кеш, и ваша функция не будет испорчена кеширующим кодом:
def cacheResults(aFunc):
'''This decorator funcion binds a map between the tuple of arguments
and results computed by aFunc for those arguments'''
def cachedFunc(*args):
if not hasattr(aFunc, '_cache'):
aFunc._cache = {}
if args in aFunc._cache:
return aFunc._cache[args]
newVal = aFunc(*args)
aFunc._cache[args] = newVal
return newVal
return cachedFunc
@cacheResults
def ReallyCalc(a):
'''This function does only actual computation'''
return pow(a, 42)
Возможно, сначала это выглядит не очень хорошо, но вы можете использовать cacheResults() везде, где вам не нужны параметры ключевого слова. Можно создать аналогичный декоратор, который работал бы также для параметров ключевого слова, но на этот раз это не казалось необходимым.
Хотя это не является целью моего вопроса (см. Пояснение там), это прекрасная схема для реализации локальных кешей. Спасибо за это.
Вы можете избавиться от условия if not hasattr ..., сделав _cache локальной переменной cacheResults.
Превратите его в вызываемый объект (так как он есть на самом деле).
class CalcSomething(object):
def __init__(self):
self._cache = {}
def __call__(self, a):
if a not in self._cache:
self._cache[a] = self.reallyCalc(a)
return self._cache[a]
def reallyCalc(self, a):
return # a real answer
calcSomething = CalcSomething()
Теперь вы можете использовать calcSomething как функцию. Но он остается аккуратным и автономным.
+1 - еще одно решение, скрывающее реализацию кеширования. Этот подход не приходил мне в голову, но он выглядит более мощным, чем мой простой декоратор.
Для тех, кто хочет использовать настраиваемые вызываемые объекты в качестве методов, вам необходимо правильно реализовать получать (подробнее об этом можно узнать в Google для "протокола дескриптора python").
Красивый. Эта комбинация использования специализированного класса плюс возможность вызова фактически позволяет создать своего рода «фабрику функций» для разных случаев, управляемую параметрами ctor.
+1 для вызов, я понятия не имел, что вы можете сделать это с объектами python
+1: функциональность + состояние -> функтор (класс AKA с определенным вызов).
Один из вариантов - злоупотребить параметрами по умолчанию. то есть:
def CalcSomething(a, _cache = {}):
if _cache.has_key(a):
Это имеет то преимущество, что вам не нужно уточнять имя и вы получите быстрый локальный доступ к переменным, вместо того, чтобы выполнять два медленных поиска dict. Однако у него все еще есть проблема, что он упоминается вне функции (на самом деле, это еще хуже, так как теперь оно находится в сигнатуре функции).
Чтобы предотвратить это, лучшим решением было бы заключить функцию в замыкание, содержащее вашу статику:
@apply
def CalcSomething():
cache = {} # statics go here
def CalcSomething(a):
if cache.has_key(a):
return cache[a]
cache[a] = ReallyCalc(a)
return cache[a]
return CalcSomething
К сожалению, приложение apply было удалено из Python 3.0. Я тоже нашел несколько подобных случаев, когда это было коротко, просто и полезно.
Я не думаю, что это хорошее применение. Гораздо очевиднее просто вызвать функцию.
Решение, предложенное С.Лоттом, - это решение, которое я бы тоже предложил.
Также есть полезные "мемоизирующие" декораторы, например:
Учитывая все это, я предлагаю альтернативу для вашей первоначальной попытки использования функции и «статический локальный», который является автономным:
def calc_something(a):
try:
return calc_something._cache[a]
except AttributeError: # _cache is not there
calc_something._cache= {}
except KeyError: # the result is not there
pass
# compute result here
calc_something._cache[a]= result
return result
Я думаю, это не сработает. Первый проход запустит блок except AttributeError, инициализирует кеш как пустой dict, а затем завершится ошибкой при ошибке ключа в calc_something._cache[a]= result.
@smido Готов поспорить, что вероятность того, что dict.__setitem__ бросит MemoryError, намного выше, чем вероятность того, что он бросит KeyError ?
Кстати, вы можете сформулировать свой вопрос по-другому. Вы ищете не локальную статическую переменную, а способ ввести мемоизацию в вашу функцию.