2017-07-01 4 views
3

Sur Ian McCracken's blog, il a un article where he talks about decorator factory factories. Dans l'article, il donne un exemple de:Python autorise-t-il les appels aux usines d'usine de décoration?

def decorator_factory_factory(method): 
    def decorator_factory(regex): 
     def decorator(f): 
      def inner(*args, **kwargs): 
       # do stuff with f(*args, **kwargs) 
       # and method and regex 
      return inner 
     return decorator 
    return decorator_factory 

Il donne alors un exemple de la façon dont il pourrait appeler le décorateur:

@decorator_factory_factory('GET')('^/.*$') 
def onGetAnything(self): 
    pass 

Cela a attiré mon attention. Je ne l'avais jamais essayé d'appeler une usine de décorateur de décorateur avant, donc j'ai décidé de voir comment le code se comporterait:

>>> def decorator_factory_factory(method): 
    def decorator_factory(regex): 
     def decorator(f): 
      def inner(*args, **kwargs): 
       print(args, kwargs) 
      return inner 
     return decorator 
    return decorator_factory 

>>> @decorator_factory_factory('GET')('^/.*$') 
def onGetAnything(self): 
    pass 
SyntaxError: invalid syntax 
>>> 

Comme vous pouvez le voir ci-dessus, Python déclenche une SyntaxError. Pourquoi donc? En plus du code qui semble fonctionner pour M. McCraken, il semble que le code devrait fonctionner parfaitement bien. N'est-ce pas fondamentalement la même syntaxe que l'enchaînement des appels de fonction, qui fonctionne? par exemple:

>>> def foo(): 
    def bar(): 
     return 2 
    return bar 

>>> foo()() 
2 
>>> 

Je pensais que peut-être qu'il utilisait une ancienne version de Python qui a permis à cette syntaxe, donc je regardais la grammaire pour la version Python qu'il était probablement en utilisant quand il a écrit l'article en 2009; 2.6.9. Mais the grammar ne semble pas encore pour permettre décorateur enchaînée appelle:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE 
decorators: decorator+ 
decorated: decorators (classdef | funcdef) 

a cette syntaxe jamais été autorisée dans une version Python. Si non, comment Ian a-t-il pu exécuter son code? At-il simplement fait une erreur?

+1

Il est presque certain que seule la coque indentant la définition de la fonction. Faites cela dans un fichier à la place. –

+0

@DanielRoseman Non. [Cela génère toujours une erreur] (https://repl.it/JJxJ/0). –

+1

Je ne pense pas que cet exemple fourni dans l'article est valide.Une fabrique de décorateurs peut être utilisée pour générer un décorateur 'GET = decorator_factory_factory ('GET')' -> '@GET ('regex')', mais je ne pense pas qu'il soit possible d'utiliser les décorateurs comme décrit par l'auteur. Probablement c'est trompeur. Je serais heureux d'avoir tort et d'obtenir l'explication détaillée, mais cela ne ressemble pas à un comportement python «out of the box». –

Répondre

2

Pour contourner ce problème, pour obtenir ce travail, vous pouvez faire un décorateur decorator_caller, qui fera les appels pour vous:

def decorator_caller(decorator, args_list): 
    d = decorator 
    for args in args_list: 
     d = d(*args) 
    return d 

utilisé comme ceci:

@decorator_caller(decorator_factory_factory, (('GET',), ('^/.*$',))) 
def onGetAnything(self): 
    pass 

Ou à l'aide de listes à la place (depuis tuples un seul article paraître confus):

@decorator_caller(decorator_factory_factory, (['GET'], ['^/.*$'])) 
def onGetAnything(self): 
    pass 

Notez que je ne crois pas qu'une telle usine d'usine est une chose utile à construire. Les usines d'usine ont du sens quand les usines extérieures vont vraiment affecter la logique de l'usine intérieure construite. Quand tout ce que vous faites est de créer une usine temporaire pour créer un seul objet juste pour jeter l'usine immédiatement, alors vous ne gagnez vraiment rien sauf de plus de complexité et une performance globale pire.

Ce serait une autre chose si vous avez réellement stocké l'usine autour de la réutilisation:

getFactory = decorator_factory_factory('GET') 

@getFactory('^/index.*$') 
def index(): 
    pass 

@getFactory('^/.*$') 
def x(): 
    pass 

Cela est logique et la syntaxe fonctionne aussi très bien. Mais si vous voulez juste enchaîner des appels d'usine pour ajouter un autre argument pour configurer le décorateur, alors vous devriez juste ajuster l'usine pour prendre ces deux arguments.

+0

Merci pour la réponse. Votre droite, vous devriez stocker l'usine d'usine. [C'est ce que l'auteur a fait dans son API REST] (https://github.com/iancmcc/txrestapi/blob/master/txrestapi/methods.py). Je me demandais vraiment si c'était possible ou non. Mais je suppose que la réponse semble être un son "non", puisque toutes les grammaires de la version Python que j'ai examinées rejetteraient une telle syntaxe. Même versions dès 2.4. –