2010-08-12 3 views
35

Je n'ai pas vraiment besoin de faire cela, mais je me demandais simplement s'il existe un moyen de lier un décorateur à toutes les fonctions d'une classe, plutôt que de le spécifier explicitement pour chaque fonction. Je suppose que ça devient alors une sorte d'aspect, plutôt qu'un décorateur et que ça me semble un peu étrange, mais que je pensais à quelque chose comme le timing ou l'authenticité, ce serait plutôt sympa.Associer un décorateur à toutes les fonctions d'une classe

Répondre

20

Le moyen le plus propre de le faire, ou d'apporter d'autres modifications à une définition de classe, est de définir une métaclasse.

Sinon, il suffit d'appliquer votre décorateur à la fin de la définition de classe:

class Something: 
    def foo(self): pass 

for name, fn in inspect.getmembers(Something): 
    if isinstance(fn, types.UnboundMethodType): 
     setattr(Something, name, decorator(fn)) 

Pour Python 3 remplacer types.UnboundMethodType avec types.FunctionType. En pratique, bien sûr, vous voudrez appliquer votre décorateur plus sélectivement, et dès que vous voulez décorer toutes les méthodes sauf une, vous découvrirez qu'il est plus facile et plus flexible juste pour utiliser la syntaxe du décorateur dans le façon traditionnelle.

+0

Je suis tout à fait d'accord, je suis aussi beaucoup plus à l'aise avec la décoration explicite de chaque fonction plutôt qu'un décorateur implicite magique. Était juste curieux est tout. – dochead

-1

Vous pouvez remplacer la méthode __getattr__. Il ne s'agit pas d'attacher un décorateur, mais il vous permet de retourner une méthode décorée. Vous auriez probablement envie de faire quelque chose comme ceci:

class Eggs(object): 
    def __getattr__(self, attr): 
     return decorate(getattr(self, `_` + attr)) 

Il y a une récursion laid cacher là-bas que vous voulez protéger contre, mais c'est un début.

+0

Ne vous dire '__getattribute__'? –

+0

@JaceBrowning: Non, je ne pense pas. Si Eggs.attr est réellement appelé Eggs._attr, cela fonctionnera. Si vous voulez remplacer les accès à Eggs.attr, qui est un attribut réel, alors peut-être. C'est là que les trucs récursifs entrent en jeu. – nmichaels

+0

Je comprends ce que vous faites maintenant - vos méthodes non décorées commencent toutes par '_'. Vous pouvez éviter cela en utilisant '__getattribute__', mais vous avez raison, il y a une récursion difficile à éviter. –

21

Chaque fois que vous pensez changer de définition de classe, vous pouvez utiliser le décorateur de classe ou la métaclasse. par exemple. en utilisant métaclasse

import types 

class DecoMeta(type): 
    def __new__(cls, name, bases, attrs): 

     for attr_name, attr_value in attrs.iteritems(): 
     if isinstance(attr_value, types.FunctionType): 
      attrs[attr_name] = cls.deco(attr_value) 

     return super(DecoMeta, cls).__new__(cls, name, bases, attrs) 

    @classmethod 
    def deco(cls, func): 
     def wrapper(*args, **kwargs): 
     print "before",func.func_name 
     result = func(*args, **kwargs) 
     print "after",func.func_name 
     return result 
     return wrapper 

class MyKlass(object): 
    __metaclass__ = DecoMeta 

    def func1(self): 
     pass 

MyKlass().func1() 

Sortie:

before func1 
after func1 

Note: il ne sera pas décorer staticmethod et classmethod

+1

Grande info. J'ai utilisé cette technique pour créer des méthodes de test à [Gold/approuvé fichier testant toutes les méthodes dans une classe de test contre chaque fichier dans un répertoire via la métaprogrammation de métaclasse en Python] (https://gist.github.com/patkujawa-wf/1f569d245bbfc73f83a3) . – Pat

+0

Je ne comprends pas vraiment la différence entre '__new__' et' __init__ 'quand il s'agit de métaclasses. Les deux semblent fonctionner pour ce problème, bien qu'ils aient des arguments différents. – Pat

+0

@Pat, il est similaire à '__new__' et' __init__' pour une classe normale, '__new__' est appelé pour construire un objet (classe dans ce cas),' __init__' est appelé pour initialiser cet objet (classe dans ce cas) , donc dans la plupart des cas, cela n'a pas d'importance à moins que vous ayez besoin de faire quelque chose avant ou après la création de classe/obj. –

1

Bien sûr que les méta-classes sont la façon la plus pythonique aller quand vous voulez modifier la façon ce python crée les objets. Ce qui peut être fait en remplaçant la méthode __new__ de votre classe. Mais il y a quelques points autour de ce problème (spécialement pour 3.X python) que je voudrais mentionner:

  1. types.FunctionType ne protège pas les méthodes spéciales d'être décorée, car ils sont les types de fonctions. De façon plus générale, vous pouvez simplement décorer les objets dont les noms ne sont pas démarrés avec un double trait de soulignement (__). Un autre avantage de cette méthode est qu'elle couvre également les objets qui existent dans l'espace de noms et commence par __ mais sont fonctionne pas comme __qualname__, __module__, etc.
  2. L'argument namespace en-tête de __new__ ne contient pas les attributs de classe au sein du __init__. La raison est que le __new__ s'exécute avant le __init__ (initialisation).

  3. Il n'est pas nécessaire d'utiliser un classmethod comme décorateur, car la plupart du temps, vous importez votre décorateur depuis un autre module.

  4. Si votre classe est contient un élément global (hors côté de la __init__) pour avoir refusé d'être décoré à côté de vérifier si le nom ne démarre pas avec __ vous pouvez vérifier le type avec types.FunctionType pour être sûr que vous n'êtes pas décorer un objet non-fonction.

Voici un metacalss exemple que vous pouvez utiliser:

class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # if your decorator is a class method of the metaclass use 
     # `my_decorator = cls.my_decorator` in order to invoke the decorator. 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 

Démo:

def my_decorator(func): 
     def wrapper(self, arg): 
      # You can also use *args instead of (self, arg) and pass the *args 
      # to the function in following call. 
      return "the value {} gets modified!!".format(func(self, arg)) 
     return wrapper 


class TheMeta(type): 
    def __new__(cls, name, bases, namespace, **kwds): 
     # my_decorator = cls.my_decorator (if the decorator is a classmethod) 
     namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()} 
     return type.__new__(cls, name, bases, namespace) 


class MyClass(metaclass=TheMeta): 
    # a = 10 
    def __init__(self, *args, **kwargs): 
     self.item = args[0] 
     self.value = kwargs['value'] 

    def __getattr__(self, attr): 
     return "This class hasn't provide the attribute {}.".format(attr) 

    def myfunction_1(self, arg): 
     return arg ** 2 

    def myfunction_2(self, arg): 
     return arg ** 3 

myinstance = MyClass(1, 2, value=100) 
print(myinstance.myfunction_1(5)) 
print(myinstance.myfunction_2(2)) 
print(myinstance.item) 
print(myinstance.p) 

Sortie:

the value 25 gets modified!! 
the value 8 gets modified!! 
1 
This class hasn't provide the attribute p. # special method is not decorated. 

Pour vérifier le 3 i tem des notes ci-dessus, vous pouvez décommenter la ligne a = 10 et faire print(myinstance.a) et voir le résultat puis changer la compréhension dans le dictionnaire __new__ comme suit voyez le résultat à nouveau:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\ 
      else my_decorator(v) for k, v in namespace.items()} 
Questions connexes