Имитация локальной статической переменной в 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", { } )  

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

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

[РЕДАКТИРОВАТЬ]
Чтобы прояснить, этот вопрос не о локальных кэшах функций, как можно предположить из приведенного выше примера. Вот еще один короткий пример, когда может пригодиться «статический локальный»:

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

Кстати, вы можете сформулировать свой вопрос по-другому. Вы ищете не локальную статическую переменную, а способ ввести мемоизацию в вашу функцию.

Torsten Marek 20.01.2009 13:16

ваш второй пример хуже первого. Вы можете вернуть datetime.datetime.strptime (dts, "% m-% d-% Y% H:% M:% S"), поскольку strptime - это метод класса, который создает новый объект datetime. На самом деле нет необходимости создавать объект datetime.

nosklo 20.01.2009 14:22

Насчет второго примера вы правы. Изменил это.

Paul Oyster 20.01.2009 14:43
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
27
3
15 196
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

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

Вы действительно считаете это «более элегантным способом»? Нет, серьезно ;-)

Paul Oyster 20.01.2009 12:44

Я бы не стал считать статические переменные элегантным шаблоном для использования в Python, но декоратор, по крайней мере, инкапсулирует технические детали настройки атрибута.

Torsten Marek 20.01.2009 13:05

+1 за классное использование атрибутов Python. @Pau Oyster: понятия не имею, почему вы не считаете это элегантным. Если вам нужен нехакерский способ сделать это, просто используйте класс Calculator с переменной экземпляра. Вы запросили локальную статику, что по своей сути некрасиво.

Wim Coenen 20.01.2009 13:43

Что ж, будучи прагматиком, а не пуристом (плюс пишущий код на C++ с 1991 ..), я не считаю локальную статику уродливой.

Paul Oyster 20.01.2009 13:56

Вам нужно указать return obj в конце _set_var. И хотя я знаю, что вы пытаетесь указать, что _set_var не является общедоступным, нет необходимости искажать имя (с добавленным подчеркиванием), поскольку оно актуально только внутри static_var.

Roger Pate 20.01.2009 16:29

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

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

Хотя это не является целью моего вопроса (см. Пояснение там), это прекрасная схема для реализации локальных кешей. Спасибо за это.

Paul Oyster 20.01.2009 13:53

Вы можете избавиться от условия if not hasattr ..., сделав _cache локальной переменной cacheResults.

GingerPlusPlus 13.07.2015 20:37
Ответ принят как подходящий

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

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 - еще одно решение, скрывающее реализацию кеширования. Этот подход не приходил мне в голову, но он выглядит более мощным, чем мой простой декоратор.

Abgan 20.01.2009 14:00

Для тех, кто хочет использовать настраиваемые вызываемые объекты в качестве методов, вам необходимо правильно реализовать получать (подробнее об этом можно узнать в Google для "протокола дескриптора python").

bruno desthuilliers 20.01.2009 15:33

Красивый. Эта комбинация использования специализированного класса плюс возможность вызова фактически позволяет создать своего рода «фабрику функций» для разных случаев, управляемую параметрами ctor.

Paul Oyster 20.01.2009 15:39

+1 для вызов, я понятия не имел, что вы можете сделать это с объектами python

Wim Coenen 20.01.2009 15:53

+1: функциональность + состояние -> функтор (класс AKA с определенным вызов).

cdleary 27.02.2009 07:40

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

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. Я тоже нашел несколько подобных случаев, когда это было коротко, просто и полезно.

Roger Pate 20.01.2009 16:32

Я не думаю, что это хорошее применение. Гораздо очевиднее просто вызвать функцию.

Benjamin Peterson 20.01.2009 18:08

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

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

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

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 23.04.2020 12:48

@smido Готов поспорить, что вероятность того, что dict.__setitem__ бросит MemoryError, намного выше, чем вероятность того, что он бросит KeyError ?

tzot 30.04.2020 20:32

Другие вопросы по теме