Domanda

In sostanza, voglio mettere una pila nello stack, che sarà raggiungibile da tutte le chiamate al di sotto di quella parte nello stack fino a quando il blocco non esce. In Java lo risolverei usando un thread statico locale con metodi di supporto, a cui è possibile accedere dai metodi.

Esempio tipico: ottieni una richiesta e apri una connessione al database. Fino al completamento della richiesta, si desidera che tutto il codice utilizzi questa connessione al database. Dopo aver terminato e chiuso la richiesta, chiudi la connessione al database.

Ciò di cui ho bisogno è un generatore di report. Ogni rapporto è composto da più parti, ogni parte può fare affidamento su calcoli diversi, a volte parti diverse si basano in parte sullo stesso calcolo. Poiché non voglio ripetere calcoli pesanti, devo memorizzarli nella cache. La mia idea è decorare i metodi con un decoratore di cache. La cache crea un ID in base al nome e al modulo del metodo, ed è argomento, controlla se lo ha già calcolato in una variabile dello stack ed esegue il metodo in caso contrario.

Cercherò di chiarire mostrando la mia attuale implementazione. Voglio che voglio fare è semplificare il codice per quei calcoli di implementazione.

Innanzitutto, ho l'oggetto di accesso alla cache centrale, che chiamo 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]

L'argomento fn è il nome file con cui viene creato il contesto, da cui è possibile leggere i dati per essere calcolati.

Quindi abbiamo la classe di calcolo:

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

Ed ecco uno stupido esempio di Fibonacci. Nessuno dei metodi è in realtà ricorsivo, funzionano invece su grandi set di dati, ma funziona per dimostrare come dipenderesti da altri calcoli:

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

Quello che voglio invece che sia Fibonacci, è solo un metodo decorato:

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

Con l'esempio math_context, quando math_context non rientra nell'ambito, lo stesso vale per tutti i valori memorizzati nella cache. Voglio la stessa cosa per il decoratore. Vale a dire. al punto X, tutto ciò che viene memorizzato nella cache da @cache è sottoposto a dereferrence per essere eliminato.

È stato utile?

Soluzione

Sono andato avanti e ho fatto qualcosa che potrebbe fare proprio quello che vuoi. Può essere usato sia come decoratore che come gestore del contesto:

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

Altri suggerimenti

questa domanda sembra essere due domande

  • a) condivisione della connessione db
  • b) memorizzazione nella cache / memorizzazione in memoria

b) vi siete risposto

a) Non sembra capire perché sia ??necessario metterlo in pila? puoi fare uno di questi

  1. puoi usare una classe e una connessione     potrebbe esserne un attributo
  2. puoi decorare tutte le tue funzioni in modo che ottengano una connessione da posizione centrale
  3. ogni funzione può usare esplicitamente a metodo di connessione globale
  4. è possibile creare una connessione e passare intorno ad esso, o creare un contesto obiettare e passare contesto, la connessione può far parte di contesto

etc, etc

È possibile utilizzare una variabile globale racchiusa in una funzione getter:

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

" ottieni una richiesta e apri una connessione al database .... chiudi la connessione al database. "

Ecco a cosa servono gli oggetti. Crea l'oggetto connessione, passalo ad altri oggetti e poi chiudilo quando hai finito. I globuli non sono appropriati. Passa semplicemente il valore come parametro agli altri oggetti che stanno facendo il lavoro.

" Ogni rapporto è composto da più parti, ogni parte può fare affidamento su calcoli diversi, a volte parti diverse si basano in parte sullo stesso calcolo .... Devo memorizzarle nella cache "

Ecco a cosa servono gli oggetti. Crea un dizionario con utili risultati di calcolo e passalo dalla parte del rapporto alla parte del rapporto.

Non è necessario pasticciare con " stack variabili " ;, " static thread local " o qualcosa del genere. Basta passare argomenti variabili ordinari alle normali funzioni del metodo. Sarai molto più felice.


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

Puoi usarlo in questo modo

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

È possibile definire un numero qualsiasi di calcoli e piegarli tutti in un singolo oggetto contenitore.

Se lo desideri, puoi trasformare MathContext in un gestore di contesto formale in modo che funzioni con l'istruzione con . Aggiungi questi due metodi a MathContext.

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

Quindi puoi farlo.

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

Alla fine dell'istruzione with , puoi essere certo che il metodo __exit__ è stato chiamato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top