Имитация «локальной статической» переменной в Python - PullRequest
25 голосов
/ 20 января 2009

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

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

Ответы [ 5 ]

50 голосов
/ 20 января 2009

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

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

17 голосов
/ 20 января 2009

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

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):
    ...
11 голосов
/ 20 января 2009

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

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 голосов
/ 20 января 2009

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

Вокруг также есть полезные "памятные" декораторы, такие как:

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

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
3 голосов
/ 20 января 2009

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

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...