2009-11-12 4 views
3

J'ai un décorateur memoize qui utilise le backend cache de Django pour se souvenir du résultat d'une fonction pendant un certain temps. Je l'applique spécifiquement à une méthode de classe.Décorateurs Python et méthodes de classe et évaluation - django memoize

Mon décorateur ressemble:

def memoize(prefix='mysite', timeout=300, keygenfunc=None): 
    # MUST SPECIFY A KEYGENFUNC(args, kwargs) WHICH MUST RETURN A STRING 
    def funcwrap(meth): 

     def keymaker(*args, **kwargs): 
     key = prefix + '___' + meth.func_name + '___' + keygenfunc(args, kwargs) 
     return key 

     def invalidate(*args, **kwargs): 
     key = keymaker(*args, **kwargs) 
     cache.set(key, None, 1) 

     def newfunc(*args, **kwargs): 
     # construct key 
     key = keymaker(*args, **kwargs) 

     # is in cache? 
     rv = cache.get(key) 

     if rv is None: 
      # cache miss 
      rv = meth(*args, **kwargs) 
      cache.set(key, rv, timeout) 

     return rv 

     newfunc.invalidate = invalidate 
     return newfunc 
    return funcwrap 

J'utilise ceci sur une méthode de classe, donc quelque chose comme:

class StorageUnit(models.Model): 
    @memoize(timeout=60*180, keygenfunc=lambda x,y: str(x[0].id)) 
    def someBigCalculation(self): 
    ... 
    return result 

Le processus de memoization réel fonctionne parfaitement! C'est-à-dire qu'un appel à

myStorageUnitInstance.someBigCalculation() 

utilise correctement le cache. OK cool!

Mon problème est lorsque je tente d'invalider manuellement l'entrée d'une instance spécifique, où je veux être en mesure d'exécuter

myStorageUnitInstance.someBigCalculation.invalidate() 

Cependant, cela ne fonctionne pas, parce que « soi » ne se faire passer et donc la clé ne se fait pas. Je reçois une erreur "IndexError: tuple index out of range" pointant vers ma fonction lambda comme indiqué plus haut.

Bien sûr, je peux appeler avec succès:

myStorageUnitInstance.someBigCalculation.invalidate(myStorageUnitInstance) 

et cela fonctionne parfaitement. Mais il "sent" redondant quand je référence déjà une instance spécifique. Comment puis-je faire en sorte que Python traite cette méthode comme une méthode liée à une instance et remplisse donc correctement la variable "self"?

Répondre

2

Les descripteurs doivent toujours être définis sur la classe, pas sur l'instance (voir the how-to guide pour tous les détails). Bien sûr, dans ce cas, vous ne le définissez pas sur l'instance, mais plutôt sur une autre fonction (et la récupérez comme un attribut d'une méthode liée). Je pense que la seule façon d'utiliser la syntaxe que vous voulez est de faire de funcwrap une instance d'une classe personnalisée (quelle classe doit être une classe descripteur, bien sûr, définir la méthode __get__ appropriée, tout comme les fonctions intrinsèquement). Alors invalidate peut être une méthode de cette classe (ou, peut-être mieux, l'autre classe personnalisée dont l'instance est la "substance liée à la méthode" produite par la méthode __get__ de la classe descripteur précédemment mentionnée), et atteindra finalement im_self (c'est comme ça il est nommé dans une méthode liée) que vous avez envie.

Un prix assez élevé (conceptuel et codage ;-) à payer pour la commodité mineure que vous recherchez - assez fort pour que je n'aie pas vraiment envie de passer une heure ou deux à le développer complètement et à le tester. Mais j'espère que je vous ai donné des indications claires pour que vous puissiez continuer si vous tenez encore à cela, et je serai ravi de clarifier et de vous aider si quelque chose n'est pas clair ou si quelque chose vous empêche de progresser.

-1

Bien que je suis d'accord avec AlexM, j'ai eu un peu de temps libre et pensé que ce serait intéressant:

# from django.whereever import cache 
class memoize(object): 
    def __init__(self,prefix='mysite', timeout=300, keygenfunc=None): 
     class memo_descriptor(object): 
      def __init__(self,func): 
       self.func = func 
      def __get__(self,obj,klass=None): 
       key = prefix + '___' + self.func.func_name + '___' + keygenfunc(obj) 
       class memo(object): 
        def __call__(s,*args,**kwargs): 
         rv = cache.get(key) 
         if rv is None: 
          rv = self.func(obj,*args, **kwargs) 
          cache.set(key, rv, timeout) 
         return rv 
        def invalidate(self): 
         cache.set(key, None, 1) 
       return memo() 
     self.descriptor = memo_descriptor 
    def __call__(self,func): 
     return self.descriptor(func) 

Remarque J'ai changé la signature keygenfunc(*args,**kwargs)-(instance), car c'est la façon dont vous utilisez Dans votre exemple (et il est impossible d'avoir une méthode someBigCalculation.invalidate effacer le cache de la manière que vous vouliez si vous générez une clé à partir des arguments de l'appel de méthode plutôt que de l'instance d'objet).

class StorageUnit(models.Model): 
    @memoize(timeout=60*180, keygenfunc=lambda x: str(x.id)) 
    def someBigCalculation(self): 
     return 'big calculation' 

Il y a beaucoup de choses qui se passent dans ce code, de sorte que ce soit réellement faire votre vie plus facile est quelque chose à considérer.

Questions connexes