Question

En substance, je veux mettre une variable sur la pile, qui sera accessible par tous les appels situés en dessous de cette partie de la pile jusqu'à la fermeture du bloc. En Java, je résoudrais cela à l’aide d’un thread local avec des méthodes de support, accessibles à partir de méthodes.

Exemple typique: vous recevez une demande et ouvrez une connexion à une base de données. Tant que la demande n'est pas terminée, vous souhaitez que tout le code utilise cette connexion à la base de données. Une fois la demande terminée et fermée, vous fermez la connexion à la base de données.

Ce dont j'ai besoin, c'est d'un générateur de rapport. Chaque rapport est composé de plusieurs parties, chaque partie pouvant s’appuyer sur des calculs différents, parfois des parties différentes reposant en partie sur le même calcul. Comme je ne veux pas répéter des calculs lourds, je dois les mettre en cache. Mon idée est de décorer les méthodes avec un décorateur de cache. Le cache crée un identifiant basé sur le nom de la méthode et le module, ainsi que ses arguments. Il vérifie s'il est déjà calculé dans une variable de pile et exécute la méthode si ce n'est pas le cas.

Je vais essayer de clarifier la situation en montrant ma mise en œuvre actuelle. Ce que je veux, c’est simplifier le code pour ceux qui implémentent des calculs.

Tout d'abord, j'ai l'objet d'accès au cache central, que j'appelle 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'argument fn est le nom du fichier dans lequel le contexte est créé, à partir duquel les données peuvent être lues pour être calculées.

Ensuite, nous avons la classe de calcul:

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

Et voici un exemple stupide de Fibonacci. Aucune des méthodes n'est réellement récursive, elles fonctionnent sur de grands ensembles de données, mais cela montre comment vous dépendez d'autres calculs:

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

Ce que je veux que Fibonacci soit à la place, n’est qu’une méthode décorée:

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

Avec l'exemple math_context, lorsque math_context sort de la portée, toutes les valeurs mises en cache le sont également. Je veux la même chose pour le décorateur. C'est à dire. au point X, tout ce qui est mis en cache par @cache est dereferrenced to gced.

Était-ce utile?

La solution

Je suis allé de l'avant et fait quelque chose qui pourrait bien faire ce que vous voulez. Il peut être utilisé à la fois comme décorateur et comme gestionnaire de contexte:

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

Autres conseils

cette question semble être deux questions

  • a) partage de connexion à la base de données
  • b) mise en cache / mémorisation

b) vous vous êtes répondu

a) Je ne semble pas comprendre pourquoi vous devez le mettre en pile. vous pouvez faire l'un de ces

  1. vous pouvez utiliser une classe et une connexion     pourrait être attribut de celui-ci
  2. vous pouvez décorer toute votre fonction afin qu'ils obtiennent une connexion de emplacement central
  3. chaque fonction peut explicitement utiliser un méthode de connexion globale
  4. vous pouvez créer une connexion et passer autour de lui ou créer un contexte objet et passe autour contexte, la connexion peut faire partie de contexte

etc, etc

Vous pouvez utiliser une variable globale encapsulée dans une fonction de lecture:

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

"Vous recevez une demande et ouvrez une connexion à la base de données .... vous fermez la connexion à la base de données."

C’est à quoi servent les objets. Créez l'objet de connexion, transmettez-le à d'autres objets, puis fermez-le lorsque vous avez terminé. Les globales ne sont pas appropriées. Il vous suffit de transmettre la valeur en tant que paramètre aux autres objets effectuant le travail.

"Chaque rapport est composé de plusieurs parties, chaque partie pouvant s’appuyer sur des calculs différents. Parfois, des parties différentes reposent en partie sur le même calcul .... Je dois les mettre en cache"

C’est à quoi servent les objets. Créez un dictionnaire avec des résultats de calcul utiles et transmettez-le d'une partie de rapport à l'autre.

Vous n'avez pas besoin de jouer avec les "variables de pile", "thread statique local". ou quelque chose comme ça. Il suffit de passer des arguments de variables ordinaires aux fonctions de méthodes ordinaires. Vous serez beaucoup plus heureux.

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

Vous pouvez l'utiliser comme ceci

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

Vous pouvez définir un nombre quelconque de calculs et les replier dans un même objet conteneur.

Si vous le souhaitez, vous pouvez transformer MathContext en un gestionnaire de contexte formel afin qu'il fonctionne avec l'instruction avec . Ajoutez ces deux méthodes à MathContext.

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

Ensuite, vous pouvez le faire.

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

À la fin de l'instruction avec , vous pouvez être sûr que la méthode __ exit __ a été appelée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top