2 votes

Comment faire fonctionner la fonction help() de Python correctement avec les décorateurs?

Python a la fonction intégrée help() qui affiche la chaîne de documentation d'un objet. Lorsque je l'utilise dans l'interpréteur en passant une fonction de mon module, il affiche clairement :

>>> help(mymodule.myfunction)
Aide sur la fonction myfunction dans le module mymodule.main :

myfunction(parameter=False) -> Dict[str, str]
    Chaîne de documentation de myfunction

Mais si j'utilise un décorateur comme functools.@lru_cache, la fonction d'aide est un peu déconcertante :

>>> help(mymodule.myfunction)
Aide sur _lru_cache_wrapper dans le module mymodule.main :

myfunction(parameter=False) -> Dict[str, str]
    Chaîne de documentation de myfunction

La chaîne de documentation est affichée, mais la première ligne du message est déroutante pour mes utilisateurs qui ne sont pas des programmeurs Python expérimentés.

Notez que je n'ai pas créé le décorateur, il provient du module functools dans la bibliothèque standard. Il semble que la solution d'utiliser functools.wraps ne fonctionnera pas pour moi.

Puis-je faire quelque chose pour forcer l'affichage du premier message même si la fonction a un décorateur ?

0 votes

Est-ce que cela répond à votre question? Python decorator handling docstrings

1 votes

@SpearAndShield merci pour le conseil, mais je pense que cela ne s'applique pas à mon cas. J'ai mis à jour la question

1 votes

La fonction est désormais le résultat du décorateur. Si les personnes qui ont créé la fonction n'ont pas ajouté wraps (ou ne l'ont pas fait manuellement) alors je ne pense pas qu'il y ait quelque chose que vous puissiez faire, à moins d'écrire votre propre version du décorateur qui le fait (peut-être en le reportant à celui qui ne fait pas ce que vous voulez).

2voto

SpearAndShield Points 81

Le wrapper suivant rend la fonction mise en cache plus proche de l'originale.

from functools import wraps

def restore(func):
    @wraps(func.__wrapped__)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

Cela crée encore un autre wrapper autour de votre fonction décorée qui restaure son type en tant que fonction tout en préservant la docstring.

Par exemple, si vous avez une fonction comme ceci :

@restore
@lru_cache
def func_dup(x: int):
    """ma doc"""
    return x

Ensuite, exécutez help(func_dup)

Aide sur la fonction func_dup dans le module __main__:

func_dup(x: int)
    ma doc

Pourquoi la différence

Je vais utiliser CPython 3.10, qui est la dernière version au moment où j'ai écrit cette réponse.

La fonction help est en fait implémentée dans pydoc en tant qu'objet Helper. La méthode magique Helper.__call__ est définie pour appeler Helper.help. Elle appelle ensuite doc, qui appelle render_doc. La fonction render_doc compose la chaîne qui est imprimée. À l'intérieur de cette fonction, elle appelle pydoc.describe pour un nom descriptif de votre fonction.

Votre mymodule.myfunction original est une fonction, donc describe retourne dans cette branche.

if inspect.isfunction(thing):
        return 'function ' + thing.__name__

Cela donne "function myfunction".

Cependant, après avoir décoré votre fonction avec @lru_cache, elle devient une instance du type intégré/extension functools._lru_cache_wrapper. Je ne suis pas sûr pourquoi c'est implémenté de cette manière, mais la fonction décorée n'est plus de type types.FunctionType. Ainsi, la fonction describe(mymodule.myfunction) retourne à la dernière ligne après avoir été décorée.

return type(thing).__name__

Cela renvoie "_lru_cache_wrapper".

La fonction functools.update_wrapper tente de

Mettre à jour une fonction wrapper pour ressembler à la fonction enveloppée

Elle ne restaure pas le wrapper en tant qu'instance de types.FunctionType. Cependant, elle fait référence à la fonction originale dans l'attribut __wrapped__. Ainsi, nous pouvons utiliser cela pour envelopper à nouveau votre fonction originale.

Référence

Il y a un problème Python bpo-46761 qui peut ou non être lié à ce problème.

lorsque vous utilisez functools.partial() pour pré-fournir des arguments à une fonction, si vous appelez ensuite functools.update_wrapper() pour mettre à jour cet objet partiel, inspect.signature() retourne la signature de la fonction originale, pas la signature de la fonction enveloppée.

C'est principalement sur functools.partial, qui ne préserve même pas la signature de la fonction enveloppée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X