Pregunta

En esencia, quiero poner una variable en la pila, que será accesible por todas las llamadas debajo de esa parte en la pila hasta que salga el bloque. En Java, resolvería esto usando un hilo estático local con métodos de soporte, al que luego se podría acceder desde métodos.

Ejemplo típico: recibe una solicitud y abre una conexión de base de datos. Hasta que se complete la solicitud, desea que todo el código use esta conexión de base de datos. Después de finalizar y cerrar la solicitud, cierra la conexión de la base de datos.

Para lo que necesito esto, es un generador de informes. Cada informe consta de varias partes, cada parte puede confiar en diferentes cálculos, a veces diferentes partes se basan en parte en el mismo cálculo. Como no quiero repetir cálculos pesados, necesito guardarlos en caché. Mi idea es decorar métodos con un decorador de caché. La memoria caché crea una identificación basada en el nombre del método y el módulo, y sus argumentos, parece que ya se ha calculado en una variable de la pila y, si no, ejecuta el método.

Intentaré aclarar mostrando mi implementación actual. Lo que quiero hacer es simplificar el código para aquellos que implementan cálculos.

Primero, tengo el objeto de acceso a la caché central, al que llamo 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]

El argumento fn es el nombre de archivo con el que se crea el contexto, desde donde se pueden leer los datos para calcularlos.

Luego tenemos la clase de cálculo:

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

Y aquí hay un estúpido ejemplo de Fibonacci. Ninguno de los métodos es realmente recursivo, sino que funciona en grandes conjuntos de datos, pero funciona para demostrar cómo dependería de otros 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

Lo que quiero que sea Fibonacci en su lugar, es solo un método decorado:

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

Con el ejemplo math_context, cuando math_context queda fuera de alcance, también lo hacen todos sus valores almacenados en caché. Quiero lo mismo para el decorador. Es decir. en el punto X, todo lo almacenado en caché por @cache se desreferencia para ser separado.

¿Fue útil?

Solución

Seguí adelante e hice algo que podría hacer lo que tú quieres. Se puede utilizar como decorador y como administrador 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))

Otros consejos

esta pregunta parece ser dos preguntas

  • a) compartiendo conexión db
  • b) almacenamiento en caché / memorización

b) os habéis respondido

a) No parece entender por qué necesita ponerlo en la pila. puedes hacer uno de estos

  1. puedes usar una clase y conexión     podría ser un atributo de ella
  2. puedes decorar todas tus funciones para que obtengan una conexión de ubicación central
  3. cada función puede usar explícitamente una método de conexión global
  4. puedes crear una conexión y pasar a su alrededor, o crear un contexto objetar y pasar contexto, la conexión puede ser parte de contexto

etc, etc

Podría usar una variable global envuelta en una función getter:

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

" recibe una solicitud y abre una conexión de base de datos ... cierra la conexión de la base de datos. "

Esto es para lo que son los objetos. Cree el objeto de conexión, páselo a otros objetos y luego ciérrelo cuando haya terminado. Los globales no son apropiados. Simplemente pase el valor como parámetro a los otros objetos que están haciendo el trabajo.

" Cada informe consta de varias partes, cada parte puede basarse en cálculos diferentes, a veces partes diferentes se basan en parte en el mismo cálculo ... Necesito almacenarlas en caché "

Esto es para lo que son los objetos. Cree un diccionario con resultados de cálculo útiles y transfiéralo de parte de informe a parte de informe.

No necesita meterse con " variables de pila " ;, " hilo estático local " o algo por el estilo. Simplemente pase argumentos variables ordinarios a funciones de método ordinarias. Serás mucho más 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()

Puedes usarlo de esta manera

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

Puede definir cualquier número de cálculos y doblarlos todos en un solo objeto contenedor.

Si lo desea, puede convertir el MathContext en un Administrador de contexto formal para que funcione con la instrucción con . Agrega estos dos métodos a MathContext.

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

Entonces puedes hacer esto.

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

Al final de la declaración con , puede garantizar que se invocó el método __exit__ .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top