Frage

Im Grunde mag ich auf dem Stapel eine Variable setzen, die von allen Anrufen unter diesem Teil auf dem Stapel, bis der Block beendet erreichbar sein werden. In Java würde ich lösen diese einen statischen lokalen Thread mit Unterstützung Methoden verwenden, die dann von Methoden zugegriffen werden kann.

Ein typisches Beispiel: Sie haben eine Anfrage erhalten, und eine Datenbank-Verbindung öffnen. Bis die Anforderung abgeschlossen ist, möchten Sie alle Code, um diese Datenbank-Verbindung zu verwenden. Nach Abschluss und Schließen der Anfrage, schließen Sie die Datenbankverbindung.

Was ich brauche dies für, ist ein Report-Generator. Jeder Bericht besteht aus mehreren Teilen, kann jeder Teil auf verschiedenen Berechnungen verlassen, manchmal verschiedene Teile teilweise auf der gleichen Berechnung beruht. Da will ich keine schweren Berechnungen wiederholen, muss ich sie zwischenzuspeichern. Meine Idee ist es, Methoden mit einem Cache-Dekorateur zu dekorieren. Der Cache schafft eine ID basiert auf dem Methodennamen und Modul, und es ist Argumente, sieht, wenn es diese allready in einem Stapel Variable berechnet hat, und führt das Verfahren, wenn nicht.

Ich werde versuchen, und clearify meine aktuelle Implementierung von zeigt. tun möchte, was ich will ist, den Code für die Implementierung Berechnungen zu vereinfachen.

Zuerst habe ich das zentrale Cache-Zugriff-Objekt, das ich MathContext nennen:

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]

Das fn Argument ist der Dateiname der Kontext in Beziehung erstellt wird, aus dem die Daten ausgelesen werden, berechnet werden.

Dann haben wir die Berechnung Klasse:

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

Und hier ist ein dummes Fibonacci Beispiel. Nicht der Methoden sind tatsächlich rekursiv, sie auf große Datenmengen arbeiten, anstatt, aber es funktioniert zu zeigen, wie Sie auf andere Berechnungen abhängen würde:

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

Was ich will Fibonacci stattdessen sein, ist nur eine dekorierter Methode:

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

Mit dem math_context Beispiel wenn math_context den Gültigkeitsbereich verlässt, nimmt auch alle es Werte zwischengespeichert. Ich möchte die gleiche Sache für den Dekorateur. D. h. am Punkt X, alles von @cache zwischengespeichert wird dereferrenced gced sein.

War es hilfreich?

Lösung

ging ich weiter und machte etwas, das nur tun könnte, was Sie wollen. Es kann sowohl als Dekorateur und ein Kontext-Manager verwendet werden:

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

Andere Tipps

Diese Frage scheint zwei Frage zu sein

  • a) Austausch DB-Verbindung
  • b) Caching / Memoizing

b) Sie beantworten euch haben

a) ich scheinen nicht zu verstehen, warum Sie es auf Stapel setzen müssen? Sie können eine von ihnen tun

  1. können Sie eine Klasse verwenden und Verbindung     könnte Attribut davon
  2. können Sie alle Ihre Funktion dekorieren so dass sie erhalten eine Verbindung von zentrale Lage
  3. jede Funktion kann explizit eine Verwendung globale Verbindungsmethode
  4. Sie können eine Verbindung erstellen und weitergeben um ihn herum, oder einen Kontext schaffen Objekt und laufen um Kontext kann Verbindung ein Teil sein Kontext

etc, etc

Sie können eine globale Variable in einer Getter-Funktion eingewickelt verwenden:

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

"Sie eine Anfrage erhalten, und eine Datenbank-Verbindung öffnen .... Sie die Datenbankverbindung schließen."

Dies ist, was Objekte sind. Erstellen Sie das Verbindungsobjekt, gibt es an anderen Gegenständen, und es dann schließen, wenn Sie fertig sind. Globals nicht geeignet sind. Übergeben Sie einfach den Wert um als Parameter an den anderen Objekten, die die Arbeit tun.

„Jeder Bericht besteht aus mehreren Teilen, wobei jeder Teil auf verschiedenen Berechnungen verlassen können, verlässt sich manchmal verschiedene Teile auf der gleichen Berechnung teil .... Ich brauche sie zwischenzuspeichern“

Dies ist, was Objekte sind. Erstellen Sie ein Wörterbuch mit nützlichen Berechnungsergebnisse und passieren, dass etwa aus Berichtsteil Teil zu melden.

Sie brauchen nicht zu Chaos mit „Stack-Variablen“, „static Thread local“ oder so etwas. Übergeben Sie einfach gewöhnliche variable Argumente gewöhnliche Methode funktioniert. Sie werden viel glücklicher sein.


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

Sie können es wie folgt verwenden

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

Sie können eine beliebige Anzahl von Berechnungen definieren und sie alle in einem einzigen Container-Objekt falten.

Wenn Sie möchten, können Sie die MathContext in einen formalen Kontext-Manager machen, so dass es mit der arbeiten mit Erklärung. Fügen Sie diese beiden Methoden zu MathContext.

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

Dann können Sie dies tun.

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

Am Ende der mit Erklärung, können Sie garantieren, dass die __exit__ Methode aufgerufen wurde.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top