2017-03-08 1 views
3

Bien qu'il y ait plenty of resources about using classes as decorators, je n'ai pas été en mesure de trouver tout ce qui traitent le problème de la décoration méthodes. Le but de cette question est de régler cela. Je vais poster ma propre solution, mais bien sûr, tout le monde est invité à poster le leur aussi.En utilisant des classes comme décorateurs de méthode


Pourquoi la mise en œuvre « standard » ne fonctionne pas

Le problème avec l'implémentation standard de classe décorateur est que Python ne crée pas une méthode liée de la fonction décorée:

class Deco: 
    def __init__(self, func): 
     self.func= func 

    def __call__(self, *args): 
     self.func(*args) 

class Class: 
    @Deco 
    def hello(self): 
     print('hello world') 

Class().hello() # throws TypeError: hello() missing 1 required positional argument: 'self' 

Un décorateur de méthode doit surmonter cet obstacle.


Exigences

Prendre les classes de l'exemple précédent, les choses suivantes devraient travailler:

>>> i= Class() 
>>> i.hello() 
hello world 
>>> i.hello 
<__main__.Deco object at 0x7f4ae8b518d0> 
>>> Class.hello is Class().hello 
False 
>>> Class().hello is Class().hello 
False 
>>> i.hello is i.hello 
True 

Idéalement, __doc__ et signature et attributs similaires de la fonction sont conservés aussi bien.

+0

Également pertinent: [Meilleure pratique du décorateur Python, utilisation d'une classe par rapport à une fonction] (http://stackoverflow.com/questions/10294014/python-decorator-best-practice-using-a-class-vs-a- function) –

+0

Pourquoi voulez-vous que ce soit un cours? Quel est le problème avec le décorateur juste une fonction? –

+0

@PaulRooney Dans mon cas particulier (j'écris une bibliothèque GUI), je veux stocker un tas d'attributs sur la fonction (comme raccourci clavier, description, catégorie, etc.) et aussi un tas de fonctions (comme '. start_in_new_thread() ',' .update_status() '). Au lieu de forcer tous ces attributs sur la fonction, je préfère écrire une classe wrapper et remplacer complètement la fonction. –

Répondre

2

Une classe de décorateur de base « ne rien faire »:

import inspect 
import functools 
from copy import copy 


class Deco: 
    def __init__(self, func): 
     self.__self__ = None # "__self__" is also used by bound methods 

     functools.update_wrapper(self, func) 

    def __call__(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy 
     bound = copy(self) 
     bound.__self__ = instance 

     # update __doc__ and similar attributes 
     functools.update_wrapper(bound, self.__wrapped__) 

     # add the bound instance to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 

     return bound 

Et qui prend des paramètres:

class DecoWithArgs: 
    #== change the constructor's parameters to fit your needs == 
    def __init__(self, *args): 
     self.args = args 

     self.__wrapped__ = None 
     self.__self__ = None 

    def __call__(self, *args, **kwargs): 
     if self.__wrapped__ is None: 
      return self.__wrap(*args, **kwargs) 
     else: 
      return self.__call_wrapped_function(*args, **kwargs) 

    def __wrap(self, func): 
     # update __doc__ and similar attributes 
     functools.update_wrapper(self, func) 

     return self 

    def __call_wrapped_function(self, *args, **kwargs): 
     # if bound to an object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     #== change the following line to make the decorator do something == 
     return self.__wrapped__(*args, **kwargs) 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self 

     # create a bound copy of this object 
     bound = copy(self) 
     bound.__self__ = instance 
     bound.__wrap(self.__wrapped__) 

     # add the bound decorator to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(instance, self.__wrapped__.__name__, bound) 
     return bound 

Une mise en œuvre comme celui-ci nous permet d'utiliser le décorateur sur les méthodes ainsi que des fonctions, donc Je pense que cela devrait être considéré comme une bonne pratique.