Как поместить переменные в стек / контекст в Python - PullRequest
2 голосов
/ 16 июня 2009

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

Тогда у нас есть класс вычисления:

 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 разыменовывается для привязки.

Ответы [ 4 ]

5 голосов
/ 16 июня 2009

Я пошел вперед и сделал что-то, что могло бы просто делать то, что вы хотите. Может использоваться как в качестве декоратора, так и в качестве менеджера контекста:

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))
2 голосов
/ 16 июня 2009

этот вопрос, кажется, два вопроса

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

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

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

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

и т. Д., И т. Д.

0 голосов
/ 16 июня 2009

"вы получаете запрос и открываете соединение с базой данных .... вы закрываете соединение с базой данных."

Это то, для чего предназначены объекты. Создайте объект подключения, передайте его другим объектам, а затем закройте его, когда закончите. Глобалы не подходят. Просто передайте значение как параметр другим объектам, выполняющим эту работу.

"Каждый отчет состоит из нескольких частей, каждая часть может опираться на разные вычисления, иногда разные части опираются частично на одни и те же вычисления .... Мне нужно их кэшировать"

Это то, для чего предназначены объекты. Создайте словарь с полезными результатами вычислений и передайте его из части отчета в часть отчета.

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


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

0 голосов
/ 16 июня 2009

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

def getConnection():
    global connection
    if connection:
        return connection
    connection=createConnection()
    return connection
...