Les docstrings en Python

Les docstrings en Python

Une des mes fonctionnalités favorites en Python est son mécanisme de documentation du code : les doctrings. En effet, je crois qu’il est très important de rendre simple les tâches over chiantes comme les tests unitaires ou la doc car moins il y a de frein à le faire, plus il y a de chances qu’on le fasse.

Principe

La docstring est une chaîne de caractères que l’on n’assigne pas, et qui est placée à un endroit spécifique du code pour décrire ce dernier.

La docstring la plus courante est placée sous une fonction. Voici une fonction SANS docstring :

def ajouter(a, b): return a + b

Et voici une fonction AVEC docstring :

def ajouter(a, b): «  » » Ajoute deux nombres l’un à l’autre et retourne le résultat. «  » » return a + b

La chaîne de caractère doit être placée juste en dessous de la signature de la fonction.

Écrire des docstrings offrent de nombreux avantages :

  • La fonction help() affiche cette documentation dans un shell.
  • Les outils de programmation tels que les shells ou les IDE affichent cette documentation quand le développeur qui ne lit pas votre code, mais l’utilise, en a besoin.
  • On peut générer une bonne doc du code avec des commandes qui extraient ces docstrings.
  • C’est un mécanisme standardisé de documentation : tout le monde sait que si c’est là, et que ça a cette forme, c’est une documentation.
  • Le code Python peut utiliser la docstring pour la lire ou l’afficher.
  • On peut mettre des tests dans les docstrings, qui servent alors d’exemples d’utilisation.

Usage

Si vous avez une fonction ainsi faite :

def ajouter(a, b): «  » » Ajoute deux nombres l’un à l’autre et retourne le résultat. «  » » return a + b

Alors dans un shell, toute personne qui va utiliser votre fonction pourra faire :

>>> help(ajouter) Help on function ajouter in module __main__:   ajouter(a, b) Ajoute deux nombres l’un à l’autre et retourne le résultat.

Il y a ainsi une documentation de votre fonction DANS le shell, sans avoir à se connecter à Internet ou quoi que ce soit. Il n’a pas à ouvrir le moindre fichier.

On peut documenter également les modules en plaçant la docstring comme première expression Python (qui n’est pas un commentaire) tout en haut du fichier :

Découvrir aussi  5 choses à apprendre en priorité en Python
#!/usr/bin/env python # -*- coding: utf-8 -*-     «  » » Ceci est un module génial qui va faire plein de trucs super cool. «  » »   import threading import multiprocessing from functools import wraps from Queue import Empty   class BaseAsbtractAdapterStrategyFactoryMock(object): pass

On peut aussi documenter une classe et ses méthodes :

class ADallas(object): «  » » Cette classe vous donne le classe à Dallas quand vous en avez vraiment besoin. «  » »   def univers_impitoyable(self): «  » » Retourne un objet univers, prêt à être impitoyable. «  » »

La plupart des fonctions et modules de la lib standard sont ainsi documentées, vous pouvez donc faire :

>>> import os >>> help(os) >>> from functools import partial >>> help(partial) >>> help(str) >>> help(‘foo’.upper)

Bonnes pratiques

D’abord, et malgré mes exemples à caractère purement pédagogique précédents, votre docstring devrait être en anglais. Même quand vous travaillez uniquement avec des français. L’anglais est la lingua franca (oui, oui, je sais…) de l’informatique, et de plus vous évitez tout problème d’encoding car vous n’avez aucun moyen de savoir si cette doc sera lue dans un shell rêglé avec les pieds (comme celui de Windows).

J’écrirai un article pour motiver les résistants à se mettre à l’anglais une bonne fois pour toute.

L’anglais est votre ami. Il est la novlang de notre métier. C’est pas vendeur ça ?

Bref.

Ensuite, il existe plusieurs manières de formater une docstring, et il y a même un PEP 257 qui ne parle que de ça. En résumé :

def foo(): «  » »Docstring d’une ligne » » »     def foo(): «  » »Résumé de la docstring de plusieurs lignes.   Contenu détaillé de la doctstring. Contenu détaillé de la doctstring. Contenu détaillé de la doctstring.   «  » »

Je ne respecte jamais cette convention. Généralement je fais plutôt :

def foo(): «  » » Docstring d’une ligne. «  » »   def foo(): «  » » Résumé de la docstring de plusieurs lignes.   Contenu détaillé de la doctstring. Contenu détaillé de la doctstring. Contenu détaillé de la doctstring. «  » »

Je trouve ça immensément plus lisible dans le code. Je ne peux pas vous recommander de faire comme moi, puisque c’est aller à l’encontre du PEP. Tout ce que je peux vous dire c’est que personne ne s’est jamais plaint de cette habitude. En matière de docstring, la plupart des gens sont juste déjà trop heureux qu’il y en ait.

Découvrir aussi  La méthode strip des chaines en Python

En revanche, tout le monde est d’accord sur le fait qu’une ligne de la docstring ne doit pas faire plus de 80 caractères. Donc indentez en conséquence. Le plugin SublimeText Wrap-Plus permet de le faire automatiquement avec Alt + Q (et bien plus). Un must have.

Usage avancé

Python étant un langage qui aime l’instrospection, la docstring est accessible depuis le code sous la forme de l’attribut __doc__ :

>>> def foo(): … «  » » … Can foo a bar with ease … «  » » … pass … >>> foo.__doc__ ‘\n Can foo a bar with ease\n ‘ >>>

Vous ne vous en servirez pas souvent, mais c’est utile pour créer le help d’un script (c’est ce que fait clize) ou faire une popup dans un IDE.

Une autre particularité des docstrings, c’est qu’elles sont très utilisées dans les générateurs de documentation comme sphinx. Et ils comprennent généralement très bien le format RST.

Le format RST est une convention de balisage pour formater du texte. Il garde le texte lisible, mais permet de générer du HTML, du PDF et un tas d’autres trucs plus propres. Aussi je vous invite à l’utiliser si vous avez une docstring dont vous sentez qu’elle a besoin d’être aussi complète que possible.

Voici toutes les balises à votre disposition :

:param arg1: description
:param arg2: description
:type arg1: type
:type arg1: type
:return: description de la valeur de retour
:rtype: type de la valeur de retour

:Example:

Un exemple écrit après un saut de ligne.

.. seealso:: Référence à une autre partie du code
.. warning:: Avertissement
.. note:: Note
.. todo:: A faire

On peut aussi utilise ``element`` pour signaler un morceau de code au milieu du texte. Sur les docstrings très longues (il n’est pas rare qu’une docstring soit plus longue que le code qu’elle documente), comme celles des modules, on peut sous-ligner les titres et sous-titres avec des = et des -.

Par exemple :

#!/usr/bin/env python # -*- coding: utf-8 -*-   «  » » The « obvious«  module ======================   Use it to import very obvious functions.   :Example:   >>> from obvious import add >>> add(1, 1) 2   This is a subtitle ——————-   You can say so many things here ! You can say so many things here ! You can say so many things here ! You can say so many things here ! You can say so many things here ! You can say so many things here ! You can say so many things here ! You can say so many things here !   This is another subtitle ————————   Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.     «  » »   def add(a, b): «  » » Adds two numbers and returns the result.   This add two real numbers and return a real result. You will want to use this function in any place you would usually use the « +«  operator but requires a functional equivalent.   :param a: The first number to add :param b: The second number to add :type a: int :type b: int :return: The result of the addition :rtype: int   :Example:   >>> add(1, 1) 2 >>> add(2.1, 3.4) # all int compatible types work 5.5   .. seealso:: sub(), div(), mul() .. warning:: This is a completly useless function. Use it only in a tutorial unless you want to look like a fool. .. note:: You may want to use a lambda function instead of this. .. todo:: Delete this function. Then masturbate with olive oil. «  » » return a + b

Aucun champ n’est obligatoire, aucuns ne sont interdépendant. Cela vous donne une grande flexibilité pour savoir jusqu’à quel point vous voulez documenter votre fonction.

Découvrir aussi  Manipuler les dates et les durées en Python

La section la plus importante à mon sens est :Example:. Avec ça une personne peut généralement avoir une bonne idée de ce qui se passe, et en plus ça sert de tests (comme on le verra plus loin).

La section la plus inutile est de loin .. todo::. En fait je vous recommande de ne pas l’utiliser. Si vous avez des todos, utilisez plutôt la convention de commentaire :

# TODO: un truc à faire

Car :

  • Je pense que vos TODO n’ont rien à foutre dans la doc.
  • Il faut mieux avoir un TODO le plus proche du truc qu’il doit modifier. Le mettre en haut de la fonction n’a pas toujours de sens.
  • De très nombreux outils et services détectent ce format automatiquement et en font quelque chose d’utile.

Le typage des arguments et de la valeur de retour n’est pas toujours utile, surtout avec Python faisant massivement usage du duck typing. La description est plus importante. Mettez le typage quand le type n’est pas intuitif ou signalez une caratéristique comme : itérable, indexable, file-like object, etc.

EDIT: ah, y aussi un field raises pour déclarer que le code peut lever une exception en particulier. J’avais zappé. On m’a aussi demandé si il y avait des équivalent à @since et @depreciated mais non, en général on fout ça dans .. note:: ou .. warning::

Doc tests

Une fonctionalité controversée des docstrings sont les doctests, des tests unitaires directement intégrés dans la docstring.

Mon conseil : utilisez les docstests pour des petites fonctions simples ou pour quelques exemples sur les fonctions complexes, et complétez les avec des tests ordinnaires. Ce sont des bons compléments, mais pas forcément idéales pour contenir TOUS les tests. Après, si c’est le seul truc qui vous motive pour écrire des tests, mettez tout dedans, il vaut mieux ça que rien du tout.

Une doc test est donc la rédaction d’une partie de la docstring avec la syntaxe d’un shell :

def add(a, b): «  » » Do I neeed to explain this ?   :Example:   >>> add(1, 1) 2 >>> add(2.1, 3.4) # all int compatible types work 5.5   «  » » return a + b   # A la fin de votre script, mettez ce snippet qui va activer les doctest if __name__ == « __main__ »: import doctest doctest.testmod()

Si vous importez ce module, il ne se passera rien. Mais si vous faites python script.py, Python va exécuter add(1, 1) et vérifier que cela affiche bien 2, puis exécuter add(2.1, 3.4) et vérifier que cela affiche bien 5.5.

Si il n’y a aucune erreur, le script se termine silencieusement (il donne des détails si on utilise l’option -v). Sinon, il beugle. Par exemple si je rajoute :

>>> add(1, 1) 3

On obtient en sortie :

$ python script.py
*******************************************
File "script.py", line 7, in __main__.add
Failed example:
    add(1, 1)
Expected:
    3
Got:
    2
*******************************************
1 items had failures:
   1 of   2 in __main__.add
***Test Failed*** 1 failures.

Attention !

Python compare non pas la valeur, mais CE QUI S’AFFICHE. Ça peut être très déroutant. Si j’ai les tests :

>>> print str(add(1, 1)) 2 >>> str(add(1, 1)) 2

Ça va planter :

$ python script.py
*******************************************
File "script.py", line 10, in __main__.add
Failed example:
    str(add(1, 1))
Expected:
    2
Got:
    '2'
*******************************************
1 items had failures:
   1 of   2 in __main__.add
***Test Failed*** 1 failures.

Il aurait fallu que j’écrive :

>>> print str(add(1, 1)) 2 >>> str(add(1, 1)) ‘2’

Notez les guillemets. C’est ainsi que ça s’afficherait dans le shell. Donc c’est ce que teste Python.

C’est la raison pour laquelle les docstests ne sont pas parfaites pour les gros tests. Si vous testez des caratères d’échappements ou du texte unicode, il vous faudra préfixer vos doctests de ur sinon ça va échouer :

ur » » » Ceci est une doctring écrite en unicode, sans interprétation des caractères d’échappement. «  » »

Même problème pour les textes longs. Il faut préciser qu’on veut tester une sortie tronquée avec +ELLIPSIS :

>>> print range(1000) # doctest: +ELLIPSIS [0, 1, …, 18, 999]

Car vous allez pas écrire les 1000 entiers pour le fun dans le test. Pareil pour les stacktraces.

Les espaces sont signficatifs, du coup il faut parfois marquer les tests avec +NORMALIZE_WHITESPACE :

>>> print range(20) # doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

Sinon c’est galère car il faut reformater la sortie à la main correctement.

Enfin, sur les structures de données comme les dicos, l’ordre des éléments n’est pas garanti, donc l’ordre d’affichage non plus. Quand aux données aléatoires…

Bref, les docstests, c’est cool, mais il ne faut pas en abuser.

4.9/5 - (24 votes)
John Loerse

A propos de l'auteur

John Loerse est un spécialiste du marketing qui a une passion pour la rédaction. Il a travaillé dans plusieurs entreprises au fil des ans, acquérant une grande expérience dans le domaine du marketing et de la communication. Grâce à sa solide expertise en rédaction et en communication, John a réussi à aider de nombreuses entreprises à atteindre leurs objectifs de marketing et à se développer de manière significative. Finalement, John a décidé de rejoindre Sametmax, une entreprise de mise en relation de freelance et entreprises, pour poursuivre sa carrière dans le marketing et la rédaction. Chez Sametmax, John travaille avec une équipe talentueuse de professionnels pour aider les entreprises à trouver les freelances les plus qualifiés et à réaliser des projets de qualité supérieure.

Laisser un commentaire