Вопрос

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

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

этот вопрос, кажется, состоит из двух вопросов

  • а) совместное использование соединения с БД
  • б) кэширование/мемоизация

б) вы ответили сами себе

а) Кажется, я не понимаю, зачем нужно помещать это в стек?ты можешь сделать одно из этого

  1. Вы можете использовать класс, и подключение может быть атрибутом этого
  2. Вы можете украсить всю свою функцию, чтобы они получали соединение из центрального местоположения
  3. Каждая функция может явно использовать метод глобального соединения
  4. Вы можете создать соединение и передать его или создать контекстный объект и передать контекст, соединение может быть частью контекста

и т. д. и т. д.

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

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 __ .

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top