2010-04-26 5 views
4

Objectif: Faire un décorateur qui peut modifier la portée qu'il est utilisé danspython pour modifier la variable portée actuelle

Si cela a fonctionné.

class Blah(): # or perhaps class Blah(ParentClassWhichMakesThisPossible) 

    def one(self): 
     pass 

    @decorated 
    def two(self): 
     pass 

>>> Blah.decorated 
["two"] 

Pourquoi? Je veux essentiellement écrire des classes qui peuvent maintenir des dictionnaires spécifiques de méthodes, de sorte que je puisse récupérer des listes de méthodes disponibles de différents types sur une base par classe. errr .....

Je veux faire:

class RuleClass(ParentClass): 
    @rule 
    def blah(self): 
     pass 

    @rule 
    def kapow(self): 
     pass 

    def shazam(self): 

class OtherRuleClass(ParentClass): 
    @rule 
    def foo(self): 
     pass 

    def bar(self): 
     pass 

>>> RuleClass.rules.keys() 
["blah", "kapow"] 
>>> OtherRuleClass.rules.keys() 
["foo"] 

Répondre

9

Vous pouvez faire ce que vous voulez avec un décorateur de classe (en Python 2.6) ou un métaclasse. La version de décorateur de classe:

def rule(f): 
    f.rule = True 
    return f 

def getRules(cls): 
    cls.rules = {} 
    for attr, value in cls.__dict__.iteritems(): 
     if getattr(value, 'rule', False): 
      cls.rules[attr] = value 
    return cls 

@getRules 
class RuleClass: 
    @rule 
    def foo(self): 
     pass 

La version métaclasse serait:

def rule(f): 
    f.rule = True 
    return f 

class RuleType(type): 
    def __init__(self, name, bases, attrs): 
     self.rules = {} 
     for attr, value in attrs.iteritems(): 
      if getattr(value, 'rule', False): 
       self.rules[attr] = value 
     super(RuleType, self).__init__(name, bases, attrs) 

class RuleBase(object): 
    __metaclass__ = RuleType 

class RuleClass(RuleBase): 
    @rule 
    def foo(self): 
     pass 

Notez que aucun de ces faire ce que vous demandez (modifier l'espace de noms d'appel) car il est fragile, difficile et souvent impossible. Au lieu de cela, ils post-traitent la classe - à l'aide du décorateur de classe ou de la méthode de la métaclasse - en inspectant tous les attributs et en remplissant l'attribut rules. La différence entre les deux est que la solution de métaclasse fonctionne dans Python 2.5 et plus tôt (jusqu'à 2.2), et que la métaclasse est héritée. Avec le décorateur, les sous-classes doivent appliquer le décorateur individuellement (si elles veulent définir l'attribut rules.)

Les deux solutions ne tiennent pas compte de l'héritage - elles ne regardent pas la classe parente quand elles recherchent des méthodes. marqués comme règles, et ne regardent pas l'attribut rules de la classe parente. Ce n'est pas difficile d'étendre non plus, si c'est ce que vous voulez.

+0

Il n'y a pas de décorateurs de classe dans Python 2.6. –

+0

Oui, il y en a. J'ai effectivement testé ces deux morceaux de code avant de les poster. Ils fonctionnent très bien en Python 2.6. –

+0

si vous changez la version du décorateur de classe pour utiliser 'inspect.getmembers' comme dans ma réponse, il s'occupera de l'héritage - alors je peux supprimer ma réponse, car elle duplique la plupart du temps la vôtre (sans surprise ;-) mais la tienne est plus complète . –

4

Le problème est, au moment où le décorateur decorated est appelé, il est aucun objet Blah encore: l'objet de classe est construit après le corps de classe fin de l'exécution. Le plus simple est d'avoir decorated stocker l'information "ailleurs", par ex. un attribut de fonction, puis un dernier passage (un décorateur de classe ou une métaclasse) récolte cette information dans le dictionnaire que vous désirez.

décorateurs de classe sont plus simples, mais ils ne sont hérités (donc ils ne viennent pas d'une classe parente) non, tandis que les métaclasses sont hérités - donc si vous insistez sur l'héritage, un métaclasse il devra être. Simplest-première, avec un décorateur de classe et la « liste » variante que vous avez au début de votre Q plutôt que la « dict » variante vous avez plus tard:

import inspect 

def classdecorator(aclass): 
    decorated = [] 
    for name, value in inspect.getmembers(aclass, inspect.ismethod): 
    if hasattr(value, '_decorated'): 
     decorated.append(name) 
     del value._decorated 
    aclass.decorated = decorated 
    return aclass 

def decorated(afun): 
    afun._decorated = True 
    return afun 

maintenant,

@classdecorator 
class Blah(object): 

    def one(self): 
     pass 

    @decorated 
    def two(self): 
     pass 

donne Construire un dict à la place, comme vous le demandez dans la deuxième partie de votre Q, signifie juste changer decorated.append(name) à decorated[name] = value dans le code ci-dessus, et bien sûr initialiser decorated dans le décorateur de classe à un dict vide plutôt que d'une liste vide.

La variante metaclass utiliserait __init__ de la métaclasse pour effectuer essentiellement le même post-traitement après que le corps de classe est construit - __init__ de métaclasse obtient un dict correspondant au corps de classe comme son dernier argument (mais vous devrez soutenez vous-même l'héritage en traitant de manière appropriée la dictée ou la liste analogue d'une classe de base). Ainsi, l'approche de la métaclasse est seulement «un peu» plus complexe dans la pratique qu'un décorateur de classe, mais conceptuellement, il est ressenti comme étant beaucoup plus difficile par la plupart des gens. Je donnerai tous les détails pour la métaclasse si vous en avez besoin, mais je recommanderais de rester avec le décorateur de classe plus simple si possible.