2017-01-30 1 views
0

J'ai actuellement un décorateur qui enveloppe une fonction dans une classe.Utiliser un décorateur qui renvoie une classe sur une méthode?

(Nous utilisons actuellement ce bizarre, cadre async sur mesure où chaque appel asynchrone est défini comme une classe avec une tonne de code de passe-partout. Mon idée était de décorer les fonctions puis retourner la classe appropriée.)

Ce décorateur fonctionne bien sur les fonctions en dehors des classes. Toutefois, lors de l'utilisation avec des méthodes, l'argument self n'est plus passé implicitement, et je ne sais pas pourquoi.

Voici le meilleur exemple que je pourrais mettre ensemble

from __future__ import print_function 

import functools 


def test_wrap(func): 
    @functools.wraps(func) 
    def wrapper(*args, **kwargs): 
     print("Args:", args) 
     print("Kwargs:", kwargs) 
     func(*args, **kwargs) 
    return wrapper 


def test_class_wrap(func): 
    """Return a Command object for use with the custom framework we are using.""" 
    @functools.wraps(func, assigned=('__name__', '__module__'), updated=()) 
    class Command(object): 
     def __init__(self, *args, **kwargs): 
      print("Args:", args) 
      print("Kwargs:", kwargs) 
      func(*args, **kwargs) 

    return Command 


class MyObject(object): 

    def __init__(self): 
     self.value = 100 

    @test_wrap 
    def foo(self): 
     print(self.value) 

    @test_class_wrap 
    def bar(self): 
     print(self.value) 


if __name__ == '__main__': 
    obj = MyObject() 
    obj.foo() 

    print() 
    obj.bar(obj) # works 
    # obj.bar() # TypeError: bar() takes exactly 1 argument (0 given) 
    # Why is self implicitly passed as an argument like with outher methods? 

# Output 
# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,) 
# Kwargs: {} 
# 100 

# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,) 
# Kwargs: {} 
# 100 
+0

Bien. . . Réfléchissez à la façon dont cela fonctionnerait si vous le faisiez manuellement. Sans le décorateur, étiez-vous en train d'écrire du code qui avait une classe imbriquée dans une autre? Si oui, comment avez-vous fait cela? Les classes ne sont pas des méthodes, donc si vous avez un décorateur qui remplace une méthode par une classe, vous aurez une classe dans votre autre classe, ce qui est une situation assez inhabituelle en Python. Pouvez-vous montrer un exemple de comment écrire "MyObject" et "bar" "manuellement", sans utiliser le décorateur? – BrenBarn

+0

Cette question est plus axée sur pourquoi 'self' n'est pas implicitement passé à' bar'. Le code ci-dessus reproduit le problème, pas l'utilisation réelle. Je peux essayer d'ajouter un exemple, mais ne vous attendez pas à ce qu'il ait beaucoup de sens. – Shatnerz

+0

La raison pour laquelle self n'est pas transmise est qu'il n'y a pas d'instance de votre classe. 'self' fait référence à l'instance, donc il ne peut être passé que lorsque vous appelez une méthode sur une instance, mais votre' obj.bar' est la classe * appelée 'Command'. Si vous écriviez cette classe Command séparément et que vous essayiez d'appeler 'Command()', vous obtiendriez la même erreur. – BrenBarn

Répondre

1

test_class_wrap ne fait rien, juste retourner une classe si __init__ est pas appelé. Essayez d'envelopper la classe avec une fonction en passant args et kwargs:

def test_class_wrap(func): 
    """Return a Command object for use with the custom framework we are using.""" 
    @functools.wraps(func, assigned=('__name__', '__module__'), updated=()) 
    def wrapper(*args, **kwargs): 
     class Command(object): 
      def __init__(self, *args, **kwargs): 
       print("Args:", args) 
       print("Kwargs:", kwargs) 
       func(*args, **kwargs) 
     return Command(*args, **kwargs) 
    return wrapper 

... 

if __name__ == '__main__': 
    obj = MyObject() 
    obj.foo() 

    print() 
    obj.bar() 
+0

Je suppose que cela pourrait "réparer", mais ce n'est pas clair comment. Cela signifie simplement que 'obj.bar()' retourne la classe Command, alors que maintenant la valeur de 'obj.bar' est la classe Command. Donc 'obj.bar()' ne "fera" rien; ça va juste retourner une classe. – BrenBarn

+0

Je suis sûr que c'est exactement ce dont j'avais besoin. Il passe tous mes tests existants aussi. @BrenBarn Le but était d'instancier la classe en appelant 'bar()'. C'est un design merdique et non-évident, mais je n'ai pas beaucoup de mot à dire pour le changer. C'est pourquoi j'essaye de tout emballer et de le rendre plus facile à traiter. – Shatnerz