2009-11-23 20 views
40

J'ai un problème en utilisant docstrings avec des décorateurs. Compte tenu de l'exemple suivant:Python décorateur manipulant docstrings

def decorator(f): 
    def _decorator(): 
     print 'decorator active' 
     f() 
    return _decorator 

@decorator 
def foo(): 
    '''the magic foo function''' 
    print 'this is function foo' 

help(foo) 

Maintenant, l'aide ne me montre pas le docstring de foo comme prévu, il montre:

Help on function _decorator in module __main__: 

_decorator() 

Sans le décorateur, l'aide est correcte:

Help on function foo in module __main__: 

foo() 
    the magic foo function 

Je sais, que la fonction foo est enveloppée par le décorateur, et donc l'objet fonction n'est plus la fonction foo. Mais quelle est la bonne solution pour obtenir la docstring (et l'aide) comme prévu?

Répondre

62

Utilisez functools.wraps() pour mettre à jour les attributs du décorateur:

from functools import wraps 

def decorator(f): 
    @wraps(f) 
    def _decorator(): 
     print 'decorator active' 
     f() 
    return _decorator 

@decorator 
def foo(): 
    '''the magic foo function''' 
    print 'this is function foo' 

help(foo) 

Voir aussi la Standard Library documentation pour functools.

+0

Cela ne fonctionne pas si 'foo' prend n'importe quel argument - ils sont remplacés par tout ce que' _decorator' utilise. C'est un problème surtout quand vous voulez que votre décorateur prenne '* args, ** kwds'. Je n'ai jamais réussi à trouver un moyen d'obtenir la docstring correcte en utilisant 'functools.wraps'. –

+3

@Scott Griffiths: La docstring sera toujours correcte même si 'foo' prend des arguments. Cependant, 'help (foo)' affichera la liste des paramètres de '_decorator', car elle remplace réellement la fonction' foo'. Il n'y a pas de bonne solution si vous écrivez des décorateurs qui prennent des arguments arbitraires en utilisant '* args, ** kwargs', mais pour moi, le point important est que la docstring reste intacte. Les détails des paramètres peuvent toujours être spécifiés dans la docstring pour plus de clarté. –

+0

Merci pour les informations supplémentaires. J'ai récemment échoué à obtenir la description de l'aide correcte pour les fonctions décorées - cela semble être un mauvais état de choses, mais je comprends la difficulté car la fonction décorée pourrait avoir une signature complètement différente. Pourtant, il doit y avoir un moyen ... :) –

12

J'ai trouvé une solution, mais je ne sais pas si c'est vraiment agréable:

def decorator(f): 
    def _decorator(): 
     print 'decorator active' 
     f() 
    _decorator.__name__=f.__name__ 
    _decorator.__doc__=f.__doc__ 
    return _decorator 

La partie avec _decorator.__name__=f.__name__ semble un peu hideux ... Que pensez-vous?

+4

En fait, c'est (presque?) Exactement ce que font functools.wraps :) – thomas

+2

Il ne semble pas affreux pour moi. Il dit exactement ce que vous voulez dire. "Je veux que le nom de cette fonction soit 'myfunction' au lieu de '_decorator'." – jcdyer

-5

Il est probablement un peu vieux maintenant, mais voici comment vous le faites. Assurez-vous que @decorator est dentelés la même ligne que def decorator(f):

from functools import wraps 

def decorator(f): 
    @wraps(f) 
    def _decorator(): 
     print 'decorator active' 
     return f() 
    return _decorator 

@decorator 
def foo(): 
    '''the magic foo function''' 
    print 'this is function foo' 

help(foo) 
+8

Cela fait écho à la réponse déjà acceptée. –