2010-03-23 15 views
6

j'ai une fonction décorée (version simplifiée):ajouter une fonction décorez à une classe

class Memoize: 
    def __init__(self, function): 
     self.function = function 
     self.memoized = {} 
    def __call__(self, *args, **kwds): 
     hash = args 
     try: 
      return self.memoized[hash] 
     except KeyError: 
      self.memoized[hash] = self.function(*args) 
      return self.memoized[hash] 


@Memoize 
def _DrawPlot(self, options): 
    do something... 

maintenant je veux ajouter cette méthode à une classe pré-esisting.

ROOT.TChain.DrawPlot = _DrawPlot 

quand je l'appelle cette méthode:

chain = TChain() 
chain.DrawPlot(opts) 

J'obtenu:

self.memoized[hash] = self.function(*args) 
TypeError: _DrawPlot() takes exactly 2 arguments (1 given) 

pourquoi ne pas propager l'auto?

+0

Toujours hériter de 'object' au lieu de rien afin que vous utilisiez des classes de style nouveau. –

+0

Ne nommez pas les méthodes avec les premières lettres majuscules si vous avez le choix en la matière. Utilisez des noms comme '_draw_plot' (recommandé par PEP 8) ou' _drawPlot'. –

+0

Ma réponse était trompeuse, donc il a fallu la côtelette. Merci pour les commentaires Mike! –

Répondre

3

Le problème est que vous avez défini votre propre classe appelable puis essayé de l'utiliser comme méthode. Lorsque vous utilisez une fonction en tant qu'attribut, l'accès à la fonction en tant qu'attribut appelle sa méthode __get__ pour renvoyer autre chose que la fonction elle-même - la méthode liée. Lorsque vous avez votre propre classe sans définir __get__, il retourne juste votre instance sans passer implicitement self.

Descripteurs sont expliqués certains sur http://docs.python.org/reference/datamodel.html#descriptors si vous ne les connaissez pas. Les méthodes __get__, __set__ et __delete__ modifient l'interaction de votre objet en tant qu'attribut.


Vous pouvez mettre en œuvre memoize en fonction et utiliser le haut-__get__ magique qui fonctionne ont déjà

import functools 

def memoize(f): 
    @functools.wraps(f) 
    def memoized(*args, _cache={}): 
     # This abuses the normally-unwanted behaviour of mutable default arguments. 
     if args not in _cache: 
      _cache[args] = f(*args) 
     return _cache[args] 
    return memoized 

ou en modifiant votre classe le long des lignes de

import functools 

class Memoize(object): #inherit object 
    def __init__(self, function): 
     self.function = function 
     self.memoized = {} 
    def __call__(self, *args): #don't accept kwargs you don't want. 
     # I removed "hash = args" because it shadowed a builtin function and 
     # because it was untrue--it wasn't a hash, it was something you intended for 
     # Python to hash for you. 
     try: 
      return self.memoized[args] 
     except KeyError: 
      self.memoized[args] = self.function(*args) 
      return self.memoized[args] 
    def __get__(self, obj, type): 
     if obj is None: #We looked up on the class 
      return self 

     return functools.partial(self, obj) 

Remarque que ces deux étranglent si l'un des arguments que vous passez est mutable (bien, techniquement impossible à résoudre). Cela peut convenir à votre cas, mais vous pouvez également traiter le cas où args est indisponible.

+0

Je préfère la deuxième version, parce que je peux contrôler le cache, je peux créer plus d'un objet Mémoriser et cache diffent pour différentes fonctions. J'utilise quelque chose de plus compliqué que 'hash = args', parce que je dois gérer des objets mutables. Comme vous l'avez dit "hash" n'est pas un très bon nom. –

+0

@wiso, Le premier exemple de code aurait un cache différent pour chaque fonction mémoized. Soyez toujours prudent en essayant de gérer les types mutables dans la mémo; ils ne sont pas lavables pour de bonnes raisons. Vous devez comprendre comment fonctionne la fonction * being * memoized pour savoir si elle est boguée ou non. –

+0

désolé, mais votre solution ne fonctionne pas: @Memoize fonction def (auto, x): impression auto return x * x classe my_class: passe my_class.do = function c = my_class() impression c.do (2) TypeError: function() prend exactement 2 arguments (1 donné) –