Имитация "локальной статической" переменной в python

Рассмотрим следующий код:

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", { } )  

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

Есть ли более элегантный способ?

[EDIT]
Просто для выяснения этого вопроса речь идет не о локальных кешах функций, как может показаться вышеприведенный пример. Вот еще один короткий пример, где "статический локальный" может быть удобным:

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() 

Ответ 1

Поверните его в вызываемый объект (с тех пор как он есть на самом деле.)

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, как если бы это была функция. Но он остается аккуратным и самодостаточным.

Ответ 2

Поверните его в декоратор.

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):
    ...

Ответ 3

Рассмотрите возможность написания декоратора, который будет поддерживать кеш, и ваша функция не будет загрязнена кодом кеширования:

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() везде, где вам не нужны параметры ключевых слов. Можно создать аналогичный декоратор, который будет работать и для параметров ключевого слова, но на этот раз это не показалось нужным.

Ответ 4

Решение, предложенное С.Лоттом, - это решение, которое я бы предложил тоже.

Также есть полезные "memoize" декораторы, например:

Учитывая все это, я предоставляю альтернативу для вашей начальной попытки функции и "статического локального", который является автономным:

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

Ответ 5

Один из вариантов - использовать параметры по умолчанию. то есть:

def CalcSomething(a, _cache={}):
    if _cache.has_key(a):

Это имеет то преимущество, что вам не нужно квалифицировать имя и быстро получать локальный доступ к переменным, а не выполнять два медленных поиска. Однако у него все еще есть проблема, что он упоминается вне функции (на самом деле это хуже, поскольку теперь он находится в сигнатуре функции.)

Чтобы предотвратить это, лучшим решением было бы обернуть функцию в закрытии, содержащем вашу статику:

@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