Pergunta

Em essência, eu quero colocar uma variável na pilha, que será acessível por todas as chamadas abaixo da parte na pilha até que as saídas de bloco. Em Java eu ??iria resolver isso usando um fio estática local com métodos de apoio, que, em seguida, pode ser acessado a partir de métodos.

Exemplo típico: você recebe um pedido, e abrir uma conexão com o banco. Até que o pedido está completo, você quer que todo o código para usar esta conexão com o banco. Depois de terminar e fechar o pedido, você fechar a conexão de banco de dados.

O que eu preciso disso para, é um gerador de relatório. Cada relatório consistem em várias partes, cada parte pode confiar em cálculos diferentes, às vezes diferentes partes depende, em parte, o mesmo cálculo. Como eu não quero repetir cálculos pesados, eu preciso cache-los. Minha idéia é métodos de decorar com um decorador de cache. O cache cria um ID baseado no nome do método e do módulo, e de argumentos, olhares se ele tem esse allready calculadas em uma variável de pilha, e executa o método se não.

Vou tentar clearify mostrando minha implementação atual. Quer que eu quero fazer é simplificar o código para esses cálculos de execução.

Em primeiro lugar, tenho o objeto de acesso de cache central, que eu chamo de 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]

O argumento fn é o nome do contexto é criado em relação a, a partir de onde os dados podem ser lidos a ser calculado.

Em seguida, temos a classe Cálculo:

 class CalcBase(object):
     def exec(self, math_context):
         raise NotImplementedError

E aqui está um exemplo estúpido Fibonacci. Non dos métodos são realmente recursiva, eles trabalham em grandes conjuntos de dados em vez, mas funciona para demonstrar como você vai depender de outros cálculos:

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

O que eu quero Fibonacci para ser em vez disso, é apenas um método decorado:

@cache
def fib(n):
    if n<2: return 1
    return fib(n-1)+fib(n-2)

Com o exemplo math_context, quando math_context sai do escopo, o mesmo acontece com todos os seus valores em cache. Eu quero a mesma coisa para o decorador. Ie. no ponto X, tudo em cache por @cache é dereferrenced a ser GCed.

Foi útil?

Solução

eu fui em frente e fez algo que só poderia fazer o que quiser. Ele pode ser usado tanto como um decorador e um gerente de contexto:

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))

Outras dicas

Esta questão parece ser duas perguntas

  • a) ligação partilha db
  • b) caching / Memoizing

b) ter respondido vós

a) I parecem não entender por que você precisa colocá-lo na pilha? você pode fazer um destes

  1. você pode usar uma classe e conexão poderia ser atributo do it
  2. você pode decorar toda a sua função para que eles obter uma conexão de localização central
  3. cada função pode usar explicitamente um método de conexão global
  4. você pode criar uma conexão e passar em torno dele, ou criar um contexto objeto e passar ao redor contexto, a ligação pode ser uma parte de contexto

etc, etc

Você pode usar uma variável global envolto em uma função getter:

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

"você recebe um pedido, e abrir uma conexão com o banco .... você fechar a conexão com o banco."

Isto é o que objetos são para. Criar o objeto de conexão, passá-lo para outros objetos, e depois fechá-lo quando estiver pronto. Globals não são adequadas. Basta passar o valor em torno de como um parâmetro para os outros objetos que estão fazendo o trabalho.

"Cada relatório consistem em várias partes, cada parte pode confiar em cálculos diferentes, às vezes diferentes partes depende, em parte, o mesmo cálculo .... eu preciso para armazenar em cache-los"

Isto é o que objetos são para. Criar um dicionário com os resultados dos cálculos úteis e passar que cerca de relatório parte de relatório parte.

Você não precisa mexer com "variáveis ??de pilha", "rosca estática local" ou qualquer coisa assim. Basta passar argumentos variáveis ??comuns para funções de método comuns. Você será muito mais feliz.


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()

Você pode usá-lo como este

>>> mc= MathContext()
>>> mc.fibonacci( 4 )
5

É possível definir qualquer número de cálculos e dobrá-los todos em um único objecto recipiente.

Se você quiser, você pode fazer o MathContext em um contexto formal gerente para que ele funcione com o com comunicado. Adicione estes dois métodos para MathContext.

def __enter__( self ):
    print "Initialize"
    return self
def __exit__( self, type_, value, traceback ):
    print "Release"

Em seguida, você pode fazer isso.

with  MathContext() as mc:
    print mc.fibonacci( 4 )

No final da com declaração, você pode garantir que o método __exit__ foi chamado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top