Как сохранить состояние в Python без классов?

Существуют ли pythonic способы поддержания состояния (например, для оптимизации) без полного объектно-ориентированного?

Чтобы лучше проиллюстрировать мой вопрос, вот пример шаблона, который я часто использую в JavaScript:

var someFunc = (function () {
    var foo = some_expensive_initialization_operation();
    return someFunc (bar) {
        // do something with foo and bar
    }
}());

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

Примером этого в Python является оптимизация регулярных выражений - полезно использовать re.compile и хранить скомпилированную версию для операций match и search.

Единственными способами, которые я знаю для этого в Python, является установка переменной в области видимости модуля:

compiled_regex = compile_my_regex()

def try_match(m): # In reality I wouldn't wrap it as pointlessly as this
    return compiled_regex.match(m)

Или создав класс:

class MatcherContainer(object):
    def __init__(self):
        self.compiled_regex = compile_my_regex()
    def try_match(self, m):
        self.compiled_regex.match(m)

my_matcher = MatcherContainer()

Первый подход является ad-hoc, и не совсем ясно, что указанная выше функция и переменная связаны друг с другом. Он также сильно загрязняет пространство имен модулей, что я не слишком доволен.

Последний подход кажется многословным и немного тяжелым на шаблоне.

Единственный способ, с которым я могу придумать, - это включить любые функции, подобные этому, в отдельные файлы (модули) и просто импортировать функции, чтобы все было чисто.

Любые советы от более опытных Pythoners о том, как с этим бороться? Или вы просто не беспокоитесь об этом и продолжаете решать проблему?

Ответ 1

Вы также можете выполнить это с помощью аргументов по умолчанию:

def try_match(m, re_match=re.compile(r'sldkjlsdjf').match):
    return re_match(m)

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

Или даже проще:

try_match = lambda m, re_match=re.compile(r'sldkjlsdjf').match: re_match(m)

Или простейший:

try_match = re.compile(r'sldkjlsdjf').match

Это сохраняет не только время повторной компиляции (которое в любом случае кэшируется внутри модуля re), но и поиск метода ".match". В занятой функции или в узкой петле эти "." разрешения могут складываться.

Ответ 2

Вы можете определить замыкание в Python так же, как вы определяете закрытие в JavaScript.

def get_matcher():
    compiled_regex = compile_my_regex()

    def try_match(m)
        return compiled_regex.match(m)

    return try_match

Однако в Python 2.x закрытие доступно только для чтения (вы не можете повторно назначить вызов функции compiled_regex внутри приведенного выше примера). Если переменная закрытия является изменяемой структурой данных (например, list, dict, set), вы можете изменить ее внутри своего вызова функции.

def get_matcher():
    compiled_regex = compile_my_regex()
    match_cache = {}

    def try_match(m):
        if m not in match_cache:
           match_cache[m] = compiled_regex.match(m)

        return match_cache[m]

    return try_match

В Python 3.x вы можете использовать ключевое слово nonlocal, чтобы повторно назначить переменной закрытия в вызове функции. (PEP-3104)

Также см. следующие вопросы о закрытии в Python:

Ответ 3

Что насчет

def create_matcher(re):
    compiled_regex = compile_my_regex()
    def try_match(m):
        return compiled_regex.match(m)
    return try_match

matcher = create_matcher(r'(.*)-(.*)')
print matcher("1-2")

?

Но в большинстве случаев классы лучше и чисты.

Ответ 4

Вы можете занести атрибут в любую функцию. Поскольку имя функции глобально, вы можете получить ее в других функциях. Например:

def memorize(t):
    memorize.value = t

def get():
    return memorize.value

memorize(5)
print get()

Вывод:

5

Вы можете использовать его для хранения состояния в одной функции:

def memory(t = None):
    if t:
        memory.value = t
    return memory.value

print memory(5)
print memory()
print memory()
print memory(7)
print memory()
print memory()

Вывод:

5
5
5
7
7
7

Предполагает, что его полезность ограничена. Я использовал его только в SO этот вопрос.

Ответ 5

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

# mymodule.py

_MATCHER = compile_my_regex()

def try_match(m):
    return _MATCHER.match(m)

Вам не следует отчаиваться от этого - предпочтительнее скрытая переменная при закрытии функции.