Как поместить переменные в стек/контекст в Python
-
05-07-2019 - |
Вопрос
По сути, я хочу поместить в стек переменную, которая будет доступна всем вызовам ниже этой части стека до тех пор, пока блок не завершится.В Java я бы решил эту проблему, используя локальный статический поток с методами поддержки, к которым затем можно было бы получить доступ из методов.
Типичный пример:вы получаете запрос и открываете соединение с базой данных.Пока запрос не будет завершен, вы хотите, чтобы весь код использовал это соединение с базой данных.После завершения и закрытия запроса вы закрываете соединение с базой данных.
Мне это нужно для генератора отчетов.Каждый отчет состоит из нескольких частей, каждая часть может основываться на разных расчетах, иногда разные части частично основаны на одном и том же расчете.Поскольку я не хочу повторять тяжелые вычисления, мне нужно их кэшировать.Моя идея — украсить методы декоратором кеша.Кэш создает идентификатор на основе имени метода и модуля, а также его аргументов, проверяет, рассчитано ли оно уже в переменной стека, и выполняет метод, если нет.
Я постараюсь внести ясность, показав свою текущую реализацию.Я хочу упростить код для реализации вычислений.
Во-первых, у меня есть объект доступа к центральному кешу, который я называю MathContext:
class MathContext(object):
def __init__(self, fn):
self.fn = fn
self.cache = dict()
def get(self, calc_config):
id = create_id(calc_config)
if id not in self.cache:
self.cache[id] = calc_config.exec(self)
return self.cache[id]
Аргумент fn — это имя файла, для которого создается контекст, из которого можно считать данные для расчета.
Затем у нас есть класс Calculation:
class CalcBase(object):
def exec(self, math_context):
raise NotImplementedError
А вот глупый пример Фибоначчи.Ни один из методов на самом деле не является рекурсивным, вместо этого они работают с большими наборами данных, но он помогает продемонстрировать, как вы будете зависеть от других вычислений:
class Fibonacci(CalcBase):
def __init__(self, n): self.n = n
def exec(self, math_context):
if self.n < 2: return 1
a = math_context.get(Fibonacci(self.n-1))
b = math_context.get(Fibonacci(self.n-2))
return a+b
Вместо этого я хочу, чтобы Фибоначчи был просто декорированным методом:
@cache
def fib(n):
if n<2: return 1
return fib(n-1)+fib(n-2)
В примере math_context, когда math_context выходит за пределы области действия, то же самое происходит со всеми его кэшированными значениями.Я хочу того же для декоратора.Т.е.в точке X все, что кэшируется @cache, разыменовывается для gced.
Решение
Я сделал что-то, что могло бы делать то, что ты хочешь. Его можно использовать как в качестве декоратора, так и в качестве менеджера контекста:
from __future__ import with_statement
try:
import cPickle as pickle
except ImportError:
import pickle
class cached(object):
"""Decorator/context manager for caching function call results.
All results are cached in one dictionary that is shared by all cached
functions.
To use this as a decorator:
@cached
def function(...):
...
The results returned by a decorated function are not cleared from the
cache until decorated_function.clear_my_cache() or cached.clear_cache()
is called
To use this as a context manager:
with cached(function) as function:
...
function(...)
...
The function's return values will be cleared from the cache when the
with block ends
To clear all cached results, call the cached.clear_cache() class method
"""
_CACHE = {}
def __init__(self, fn):
self._fn = fn
def __call__(self, *args, **kwds):
key = self._cache_key(*args, **kwds)
function_cache = self._CACHE.setdefault(self._fn, {})
try:
return function_cache[key]
except KeyError:
function_cache[key] = result = self._fn(*args, **kwds)
return result
def clear_my_cache(self):
"""Clear the cache for a decorated function
"""
try:
del self._CACHE[self._fn]
except KeyError:
pass # no cached results
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.clear_my_cache()
def _cache_key(self, *args, **kwds):
"""Create a cache key for the given positional and keyword
arguments. pickle.dumps() is used because there could be
unhashable objects in the arguments, but passing them to
pickle.dumps() will result in a string, which is always hashable.
I used this to make the cached class as generic as possible. Depending
on your requirements, other key generating techniques may be more
efficient
"""
return pickle.dumps((args, sorted(kwds.items())), pickle.HIGHEST_PROTOCOL)
@classmethod
def clear_cache(cls):
"""Clear everything from all functions from the cache
"""
cls._CACHE = {}
if __name__ == '__main__':
# used as decorator
@cached
def fibonacci(n):
print "calculating fibonacci(%d)" % n
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
for n in xrange(10):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
def lucas(n):
print "calculating lucas(%d)" % n
if n == 0:
return 2
if n == 1:
return 1
return lucas(n - 1) + lucas(n - 2)
# used as context manager
with cached(lucas) as lucas:
for i in xrange(10):
print 'lucas(%d) = %d' % (i, lucas(i))
for n in xrange(9, -1, -1):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
cached.clear_cache()
for n in xrange(9, -1, -1):
print 'fibonacci(%d) = %d' % (n, fibonacci(n))
Другие советы
этот вопрос, кажется, состоит из двух вопросов
- а) совместное использование соединения с БД
- б) кэширование/мемоизация
б) вы ответили сами себе
а) Кажется, я не понимаю, зачем нужно помещать это в стек?ты можешь сделать одно из этого
- Вы можете использовать класс, и подключение может быть атрибутом этого
- Вы можете украсить всю свою функцию, чтобы они получали соединение из центрального местоположения
- Каждая функция может явно использовать метод глобального соединения
- Вы можете создать соединение и передать его или создать контекстный объект и передать контекст, соединение может быть частью контекста
и т. д. и т. д.
Вы можете использовать глобальную переменную, заключенную в функцию получения:
def getConnection():
global connection
if connection:
return connection
connection=createConnection()
return connection
" вы получаете запрос и открываете соединение с базой данных .... вы закрываете соединение с базой данных. "
Это то, для чего предназначены объекты. Создайте объект подключения, передайте его другим объектам, а затем закройте его, когда закончите. Глобалы не подходят. Просто передайте значение в качестве параметра другим объектам, которые выполняют работу.
" Каждый отчет состоит из нескольких частей, каждая часть может опираться на разные вычисления, иногда разные части опираются частично на одни и те же вычисления .... Мне нужно их кэшировать "
Это то, для чего предназначены объекты. Создайте словарь с полезными результатами вычислений и передайте его из части отчета в часть отчета. Р>
Вам не нужно связываться с " переменными стека " ;, " локальным статическим потоком " или что-нибудь в этом роде. Просто передайте аргументы обычной переменной обычным функциям метода. Вы будете намного счастливее.
<Ч>class MemoizedCalculation( object ):
pass
class Fibonacci( MemoizedCalculation ):
def __init__( self ):
self.cache= { 0: 1, 1: 1 }
def __call__( self, arg ):
if arg not in self.cache:
self.cache[arg]= self(arg-1) + self(arg-2)
return self.cache[arg]
class MathContext( object ):
def __init__( self ):
self.fibonacci = Fibonacci()
Вы можете использовать это так
>>> mc= MathContext()
>>> mc.fibonacci( 4 )
5
Вы можете определить любое количество вычислений и сложить их все в один контейнерный объект.
Если хотите, вы можете превратить MathContext в формальный менеджер контекста, чтобы он работал с оператором с . Добавьте эти два метода в MathContext.
def __enter__( self ):
print "Initialize"
return self
def __exit__( self, type_, value, traceback ):
print "Release"
Тогда вы можете сделать это.
with MathContext() as mc:
print mc.fibonacci( 4 )
В конце оператора с вы можете гарантировать, что был вызван метод __ exit __
.