Je tente de construire un décorateur pour une méthode d'instance d'une classe qui tiendra le résultat. (Cela a été fait un million de fois auparavant) Cependant, j'aimerais pouvoir réinitialiser le cache mémo à tout moment (par exemple, si quelque chose dans l'état d'instance change, ce qui pourrait changer le résultat de la méthode n'ayant rien faire avec ses arguments). Ainsi, j'ai tenté de construire un décorateur comme une classe au lieu d'une fonction, de sorte que je pourrais avoir accès au cache comme membre de classe. Cela m'a conduit sur le chemin d'apprentissage sur les descripteurs, en particulier le __ obtenez __ code> méthode, ce qui est en fait bloqué. Mon code ressemble à:
import time
class memoized(object):
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args, **kwargs):
key = (self.func, args, frozenset(kwargs.iteritems()))
try:
return self.cache[key]
except KeyError:
self.cache[key] = self.func(*args, **kwargs)
return self.cache[key]
except TypeError:
# uncacheable, so just return calculated value without caching
return self.func(*args, **kwargs)
# self == instance of memoized
# obj == instance of my_class
# objtype == class object of __main__.my_class
def __get__(self, obj, objtype=None):
"""Support instance methods"""
if obj is None:
return self
# new_func is the bound method my_func of my_class instance
new_func = self.func.__get__(obj, objtype)
# instantiates a brand new class...this is not helping us, because it's a
# new class each time, which starts with a fresh cache
return self.__class__(new_func)
# new method that will allow me to reset the memoized cache
def reset(self):
print "IN RESET"
self.cache = {}
class my_class:
@memoized
def my_func(self, val):
print "in my_func"
time.sleep(2)
return val
c = my_class()
print "should take time"
print c.my_func(55)
print
print "should be instant"
print c.my_func(55)
print
c.my_func.reset()
print "should take time"
print c.my_func(55)
3 Réponses :
Plutôt que d'essayer de résoudre la mécanique de votre implémentation, j'ai pris la classe de décorateur code> code> de PythondecoratoratorLibrary et l'ont modifié pour ajouter réinitialiser code>. Ci-dessous est le résultat; L'astuce que j'ai utilisée consiste à ajouter un attribut appelable
réinitialiser code> à la fonction décorée elle-même.
class memoized2(object):
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncachable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __repr__(self):
"""Return the function's docstring."""
return self.func.__doc__
def __get__(self, obj, objtype):
"""Support instance methods."""
fn = functools.partial(self.__call__, obj)
fn.reset = self._reset
return fn
def _reset(self):
self.cache = {}
class my_class:
@memoized2
def my_func(self, val):
print "in my_func"
time.sleep(2)
return val
c = my_class()
print "should take time"
print c.my_func(55)
print
print "should be instant"
print c.my_func(55)
print
c.my_func.reset()
print "should take time"
print c.my_func(55)
Merci! J'avais essayé l'approche FuncTools, mais n'avait pas l'expérience de réaliser ce que cela faisait réellement. Je réalise que toute l'idée est un peu maladroite, alors merci pour votre aide!
Cela peut fonctionner, mais il a l'air très inefficace pour moi donné ce que __ obtenez __ () code> fait sur chaque référence à la méthode commémorée - qui est quelque peu ironique compte tenu de l'utilisation prévue ...
@Martineau - Dans l'intérêt de l'éducation, pourquoi diriez-vous que le __ obtenir __ code> est inefficace? (Véritable curiosité, comme débutant)
@Hoopes: ok sûr. J'ai dit que parce que sur chaque i> Access de la méthode mémozaine avant toute mise en cache ait eu la chance de se dérouler, il appelle FuncTools.Partial () code> qui crée une fonction qui sera appelée Et puis, juste au cas où, il crée et attribue également
self._reset code> à un attribut de cette fonction nouvellement créée afin que ce soit là s'il s'appelle. Je pense que cela peut être une limitation inhérente à essayer de cacher des choses via le protocole descripteur qui ne sait pas pourquoi i> ou pour quel but vous obtenez, définir ou supprimer sa valeur logique.
@martineau, s'il vous plaît corrigez-moi si je me trompe, mais il me semble une solution facile: après la construction ci-dessus partielle code>, mettez-le dans un
auto .__ wrapper code> et sur n'importe quelle suivante
__ Obtenir __ Code> Les appels renvoient le Cached
auto .__ wrapper code>. Je semble travailler pour moi. Non?
@ K3 --- RNC: Bonne pensée. Cela fonctionne et réduit considérablement les frais généraux d'appeler de manière significative une méthode mémotionnée. BTW Je ne vois pas de besoin réel de doubler (ou même d'un seul) principal (s) de soulignement (s) sur tout nouveau " wrapper code>" attribut.
@ K3 --- RNC: En outre, BTW, je trouve "Methinks" juste une façon vraiment prétentieuse et gênante de dire "je pense".
@martineau, je suis d'accord. J'aime juste préfixer les attributs "internes" avec un soulignement comme je l'entends est une sorte de convention. En outre, je ne suis pas un orateur anglais natif, et je trouve des bizarreries archaïques telles que " Methinks i>" un ajout de bienvenue à ce qui est sinon une langue terne et logique. :RÉ
@ K3 --- RNC: J'espère ce que dit Ye. Je comprends que tu aies envie d'utiliser un soulignement de premier plan bien que l'utilisation de tels autres soit incompatible avec le code de code existant ici. De plus, en utilisant deux d'entre eux a une signification particulière dans Python qui ne s'applique pas ici et pourrait même être considérée comme trompeuse.
Comment accéderiez-vous à la méthode code> code> si @ mémoized2 code> a été utilisé sur un
@property code>, c'est-à-dire
@property \ n @ mémoized2 \ ndef myProp (auto): code>?
Veuillez noter que cela réinitialisera le cache pour toutes les instances de "my_class".
Eh bien, je voudrais souligner deux problèmes de performance dans votre code. Ce n'est pas une réponse à votre question, mais je ne peux pas en faire un commentaire. Merci à @delnan pour avoir souligné que has_key code> est obsolète. Au lieu de:
resultDone = False
result = None
try:
if key in self.cache: return self.cache[key]
else:
result = self.func(*args, **kwargs)
resultDone = True
self.cache[key] = result
except TypeError: # unhashable key
pass
if resultDone: return result
else: return self.func(*args, **kwargs)
Vous avez tort d'essayer / sauf d'être un problème de performance. Il est vrai que des exceptions sont chères, mais seulement si elles déclenchent réellement: Ideone.com/8LXYO en cas de mémorisation, On pourrait s'attendre à ce que les hits de cache soient beaucoup plus fréquents puis manquent. En cas de cache Miss, les coûts de recalculition remplacent probablement le coût de la capture d'une exception.
@LQC I Je viens de dire que ESSAY / SAUF KeyError code> est plus lent que
has_key code> ou
de la touche code>, l'appelle une fois ou 1000 fois. Je ne suis pas informé de la distribution des données dans le domaine OP, désolé :)
Construire sur la réponse à la question originale donnée par @aix j'ai créé une classe à laquelle je pense pourrait l'améliorer. La caractéristique principale est que les valeurs en cache sont stockées comme une propriété de l'instance dont la méthode est décorée, il est donc très facile de les réinitialiser. Comme exemple d'utilisation: p> obtient comme sortie: p>
Je ne suis pas allé plus profondément dans votre code, mais pour de meilleures performances, vous devez utiliser
si cache.has_key (...): renvoyer le cache [...] code> au lieu de capture
KeyError code>.
@khachik:
Touche dans le cache code> est meilleur, car
has_key code> est obsolète.
N'oubliez pas que le point de mémoisation est qu'un appel de fonction avec les mêmes arguments produit la même sortie. Si vous avez vraiment besoin de réinitialiser le cache basé sur l'état de l'instance, vous devriez peut-être envisager de garder le cache dans l'instance au lieu d'un décorateur mémoqué.
@khachik @delnan - Ouais, j'aurais dû le poster complètement nettoyé. J'essayais juste d'avoir le concept. Désolé. @Falmarri - Je me rends compte que cela est conceptuellement gênant, mais j'espérais simplement être capable d'ajouter un décorateur (1 ligne), au lieu de faire toute la vérification de la cache (disons, 5 lignes) par méthode utilisée. Peut-être que je vais Revenez et découvrez comment procéder succinctement dans l'instance. Merci à tous!
@Falmarri: Je pense que je devrais être en désaccord avec cette évaluation. Il existe de nombreuses applications dans lesquelles vous souhaitez mémoriser des fonctions en fonction du contexte de l'application, puis invalider les caches lorsque vous avez quitté le contexte (par exemple, des requêtes de base de données, où l'application peut se déconnecter de la base de données et se connecter à un nouveau). Personne n'a jamais dit que la mémoisation devait rester valable pour l'ensemble de la demande. Il doit juste rester valable suffisamment longtemps pour vous donner un avantage de performance significatif.