Как сохранить состояние в 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)
Вам не следует отчаиваться от этого - предпочтительнее скрытая переменная при закрытии функции.