Améliorer son code Python
La syntaxe de Python est très permissive. Pour uniformiser l’écriture de code, la communauté des développeurs Python recommande un certain nombre de règles afin qu’un code soit lisible. Lisible par vous mais également par d’autres. Nous allons voir dans ces lignes quelles sont ces règles.
Essayez de relire un code que vous avez écrit « rapidement » il y a un mois, six mois ou un an. Si le code ne fait que quelques lignes, il se peut que vous vous y retrouviez, mais s’il fait plusieurs dizaines voire centaines de lignes, cela risque d’être différent. Le créateur de Python, Guido van Rossum, le dit simplement : « Code is read much more often than it is written » , le code est plus souvent lu qu’écrit. Voyons en quoi consistent ces bonnes pratiques. Les développeurs parlent de code pythonique lorsque ce dernier respecte les règles d’écriture définies par la communauté Python mais aussi les règles d’usage du langage. Plusieurs choses sont nécessaires pour écrire un code lisible : la syntaxe, l’organisation du code, le découpage en fonctions et en classes. Pour cela, les PEP peuvent nous aider.
Les PEP
Afin d’améliorer le langage, la communauté Python publie régulièrement des PEP ( Python Enhancement Proposal www. python. org/ dev/ peps/) numérotées. Il s’agit de propositions concrètes pour améliorer le code. La PEP 8, Style Guide for Python Code ( www. python. org/ dev/ peps/ pep- 0008/), est l’une des toutes premières PEP. Incontournable dès lors que vous voulez écrire du code Python correct, elle consiste en un nombre important de recommandations sur la syntaxe du langage. Il est vivement recommandé de lire cette
PEP en entier au moins une fois dans sa vie de développeur Python pour avoir une bonne vue d’ensemble. Nous allons vous en faire un rapide résumé.
Indentation
L’indentation est obligatoire en Python pour séparer les blocs d’instructions. Ce n’est pas seulement une bonne pratique destinée à améliorer la lisibilité d’un code. Dans la PEP 8, la recommandation pour la syntaxe de chaque niveau d’indentation est très simple : 4 espaces.
Importation des modules
Le chargement d’un module doit se faire avec l’instruction import module plutôt qu’avec from module import*. Pour utiliser une fonction d’un module, il vaut mieux écrire module. fonction(), ce qui précise explicitement la provenance de la fonction plutôt que fonction() tout court. Cette deuxième syntaxe peut mener à un conflit si au moins deux fonctions de modules distincts ont le même nom.
Qui plus est, cela rend la recherche de documentation plus difficile. Les modules doivent être importés ligne par ligne, en commençant par les modules internes ( classés par ordre alphabétique), c’est- à- dire les modules de base de Python, puis les modules externes – les autres. Si le nom d’un module est trop long, vous pouvez utiliser un alias ( as …). L’instruction from doit être réservée à l’import de fonctions clairement identifiées. Cela donne, en résumé : import module _ interne _ n1 import module _ interne _ n2 from module _ interne _ n3 import fonction _ spécifique from module _ interne _ n4 import constante _ 1, fonction _ 1, fonction _ 2 import module _ externe _ n1 import module _ externe _ n2 import module _ externe _ n3 _ qui _ a _ un _ nom _ tres _ long as mod3 # mod3 est un alias
Règles de nommage
Les noms de variables, de fonctions et de modules doivent être de la forme : age _ du _ capitaine calculer _ age() module _ trigonometrie
C’est- à- dire en minuscules en séparant les différents mots par un caractère underscore ( tiret du bas). Ce style est appelé snake_ case.
Les constantes doivent être écrites en majuscules : MONTANT _ MAX VITESSE _ MINIMALE
Pour les noms de classes et d’exceptions, il faut employer le Camelcase : Classeunetelle Exceptionspecifique
Donnez à vos variables des noms qui ont du sens. Évitez autant que possible les a1, a2, variable1, variable2, ma_ variable et autres titi, tata ou tutu. Les noms de variables à un caractère sont néanmoins autorisés pour les compteurs de boucles et, du coup, les indices de tableau : liste _ valeurs = [ 1, 3, 5, 7, 9, 11] for i in range( len( liste _ valeurs)):
print( liste _ valeurs[ i])
L’emploi d’un foreach ( for in en Python) vous évitera d’utiliser des indices tant que vous vous contentez de faire défiler une liste en lecture seule, comme dans l’exemple suivant : liste _ valeurs = [ 1, 3, 5, 7, 9, 11] for entier in liste _ valeurs:
print( entier)
L’autre exception possible concerne l’emploi de noms de variable à une lettre lorsqu’elles font référence à des coordonnées cartésiennes ( x, y, z, t, …) ou, plus généralement, à des éléments mathématiques ( paramètres d’équations, par exemple).
Gestion des espaces
La PEP 8 recommande d’entourer les opérateurs (+, -, /, *, ==, !=, >=, not, in, and, or...) d’un espace avant et d’un espace après. Par exemple : # code recommandé : variable _ somme = 3 + 7 nom _ periph = "souris" nom _ periph == variable _ somme # code non recommandé : variable _ somme= 3+ 7 nom _ periph=" souris" nom _ periph== variable _ somme
Ce n’est pas le cas à l’intérieur de crochets, d’accolades ou de parenthèses : # code recommandé : liste _ valeurs[ 1] dico _ clefs{" clé"} fonction _ unetelle( argument) # code non recommandé : liste _ valeurs[ 1 ] dico _ clefs{" clé" } fonction _ unetelle( argument )
Ni non plus avant la parenthèse ouvrante d’une fonction ou le crochet ouvrant d’une liste ou d’un dictionnaire : # code recommandé : liste _ valeurs[ 1] dico _ clefs{" clé"} fonction _ unetelle( argument) # code non recommandé : liste _ valeurs [ 1] dico _ clefs {" clé"} fonction _ unetelle ( argument)
Vous mettrez un espace après les caractères : et , mais pas avant : # code recommandé : liste _ valeurs = [ 1, 2, 3] dico _ clefs = {" clé1": "valeur1", "clé2": "valeur2"} fonction _ unetelle( argument1, argument2) # code non recommandé : liste _ valeurs = [ 1 , 2 ,3] dico _ clefs = {" clé1":" valeur1", "clé2":" valeur2"} fonction _ unetelle( argument1 , argument2)
En revanche, pour les plages dans les listes, on ne met pas d’espace autour du signe : liste _ valeurs = [ 1, 3, 5, 7, 9, 1] # code recommandé : liste _ valeurs[ 1: 3] liste _ valeurs[ 1: 4: 2] liste _ valeurs[:: 2] # code non recommandé : liste _ valeurs[ 1 : 3] liste _ valeurs[ 1: 4: 2 ] liste _ valeurs[ : : 2]
Vous n’ajouterez pas non plus plusieurs espaces autour du symbole = ou des autres opérateurs pour « faire joli » ou indenter bizarrement : # code recommandé : x1 = 10 x2 = 30 x _ ter = 25 # code non recommandé : x1 = 10 x2 = 30 x _ ter = 25
Longueur de ligne
Une ligne de code ne doit pas dépasser l’écran classique ( environ 80 caractères, mais cela peut varier selon vos paramètres d’affichage) pour des raisons assez évidentes de lisibilité, de relecture rapide du code. Le caractère \ ( antislash) permet de couper des lignes trop longues. Par exemple : variable _ somme = 3 if variable _ somme > 1 and \ variable _ somme < 10 and \ variable _ somme % 2 == 1 and \ variable _ somme % 3 == 0: À l’intérieur d’une parenthèse, vous pouvez passer à la ligne suivante sans utiliser le caractère \. C’est particulièrement utile pour préciser les arguments d’une fonction ou d’une méthode lors de sa création ou de son utilisation : def fonction _ unetelle( argument _ 1, argument _ 2, argument _ 3, argument _ 4): return argument _ 1 + argument _ 2 fonction _ unetelle(" texte très long", "jaguar", "panthère", "tigre")
’ texte très long tigre’
Les parenthèses sont également très pratiques pour découper une chaîne de caractères sur plusieurs lignes : print(" Rappelez- vous l’objet que nous vîmes, ""mon âme, Ce beau matin d’été si doux : ""Au détour … "
Rappelez- vous l’objet que nous vîmes, mon âme, Ce beau matin d’été si doux : Au détour …
Il n’est alors pas nécessaire d’employer l’opérateur + pour concaténer les chaînes de caractères. Les parenthèses peuvent aussi être utilisées pour évaluer une expression trop longue : variable _ somme = 3 if ( variable _ somme > 1 and variable _ somme < 10 and variable _ somme % 2 == 1 and variable _ somme % 3 == 0):
print(" variable _ somme vaut {}". format( variable _ somme)) variable _ somme vaut 3
Les parenthèses sont aussi très utiles lorsqu’on a besoin d’enchaîner des méthodes les unes à la suite des autres. Enfin, il est possible de créer des listes ou des dictionnaires
sur plusieurs lignes, en effectuant les sauts de ligne après la virgule : liste _ valeurs = [ 1, 2, 3, 4, 5, 6,
7, 8, 9] dico _ clefs = {" clé1": 13, "clé2": 42,
"clé3": - 10}
Lignes vides
Dans un script, les lignes vides sont indispensables pour séparer visuellement les différentes parties du code et le rendre plus lisible. Il est recommandé de laisser deux lignes vides avant la définition d’une fonction ou d’une classe et de laisser une seule ligne vide avant la définition d’une méthode de classe. Vous pouvez aussi laisser des lignes vides dans le corps d’une fonction pour séparer ses différentes sections logiques.
Commentaires
Les commentaires débutent toujours par le symbole # suivi d’un espace. Ils sont là pour donner des explications claires sur l’utilité du code et doivent être synchronisés avec les modifications qui lui sont apportées. Les commentaires doivent être sur le même niveau d’indentation que le code qu’ils commentent. La PEP 8 recommande très fortement d’écrire les commentaires en anglais. Néanmoins, la langue à employer devrait être de préférence la langue transverse des développeurs, donc le français si c’est le cas. Soyez cohérent concernant le choix de la langue utilisée pour les commentaires et de celle employée pour le nommage des variables. Pour un programme scientifique, les commentaires et les noms de variables seront plutôt en anglais. Ainsi liste_ premiere deviendra first_ list et fonction_ x deviendra function_ x, par exemple. Les commentaires qui suivent le code sur la même ligne sont à éviter le plus possible et doivent être séparés du code par au moins deux espaces, comme ceci : x = x + 100 # End of line comment.
Les docstrings et la PEP 257
Les docstrings, que l’on pourrait traduire par « chaînes de documentation » en français, sont un élément essentiel des programmes Python. La PEP 8 et la PEP 257 ( www. python. org/ dev/ peps/ pep- 0257/) donnent la manière de rédiger correctement ces docstrings. En voici un résumé.
Il est nécessaire d’écrire des docstrings aussi bien pour les modules que pour les fonctions, les classes ou les méthodes. Elles peuvent être courtes et compactes et n’occuper qu’une ligne si cela suffit : """ Docstring simple d’une ligne se finissant par un point."""
Sinon, prenez autant de lignes que nécessaire, mais restez concis et évitez le verbiage inutile. """ Docstring de plusieurs lignes, la première ligne est un résumé.
Après avoir sauté une ligne, les détails de cette docstring sont décrits sur plusieurs lignes : ……… …….. …….. ……..
Fin de la docstring avec les triples guillemets sur la ligne suivante : """
La PEP 257 recommande d’écrire des docstrings avec des triples doubles guillemets, c’est- à- dire : """ Cette docstring respecte la norme."""
et non de tripler les quotes ou apostrophes, comme ceci : ’’’ Celle- ci ne la respecte pas du tout.’’’.
N’oubliez pas que les docstrings sont destinées aux utilisateurs des modules, fonctions, méthodes et classes que vous avez développées. Les éléments essentiels pour les fonctions et les méthodes sont :
1. ce que fait la fonction ou la méthode,
2. ce qu’elle prend en argument,
3. ce qu’elle renvoie.
Pour les modules et les classes, on ajoute également des informations générales sur leur fonctionnement. Pour autant, la PEP 257 ne dit pas explicitement comment organiser les docstrings pour les fonctions et les méthodes. Pour répondre à ce besoin, deux solutions ont émergées : • La solution Google avec le Google Style Python Docstrings ( https:// sphinxcontrib- napoleon. readthedocs. io/ en/ latest/ example_ google. html) • La solution Numpy avec le Numpy Style Python Docstrings ( https:// sphinxcontrib- napoleon. readthedocs. io/ en/ latest/ example_ numpy. htmlnumpy) qui est un module complémentaire à Python, très utilisé en analyse de données.
Voici un résumé de la solution Numpy avec un exemple très simple : def multiplie _ nombres( nombre1, nombre2):
""" Multiplication de deux nombres entiers.
Cette fonction ne sert pas à grand chose. Parameters ---------nombre1 : int
Le premier nombre entier. nombre2 : int
Le second nombre entier.
Avec une description plus longue. Sur plusieurs lignes. Returns
------int
Le produit des deux nombres. """ return nombre1 * nombre2
La section Parameters précise les paramètres de la fonction. Les tirets sur la ligne 7 permettent de souligner le nom de la section et donc de la rendre visible. Sont ensuite indiqués le nom et le type du paramètre séparés par le caractère deux- points. Le type n’est pas obligatoire. En dessous, une description du paramètre en question est donnée. La description est indentée. Il est fait de même pour les autres paramètres. La description de chaque paramètre peut s’étaler sur plusieurs lignes. La section Returns indique ce qui est renvoyé par la fonction ( le cas échéant).
La mention du type renvoyé est obligatoire. Une description de ce qui est renvoyé par la fonction est indiqué en dessous. Cette description est aussi indentée. Rédigez vos docstrings au moment où vous écrivez vos modules, fonctions, classes ou méthodes. Passer plusieurs journées à écrire les docstrings d’un gros projet peut sembler particulièrement pénible mais est néanmoins totalement indispensable.
Outils de contrôle qualité du code
Pour évaluer la qualité d’un code Python, c’est- à- dire sa conformité avec les recommandations des deux plus importantes PEP, la 8 et la 257, vous pouvez utiliser des sites internet ou des outils dédiés. Le site pep8online ( http:// pep8online. com/), par exemple, est très simple à utiliser. Il suffit de copier/ coller le code à évaluer puis de cliquer sur le bouton Check code. Les outils pycodestyle, pydocstyle et pylint doivent en revanche être installés sur votre machine. Avec la distribution Miniconda, cette étape d’installation se résumera à une seule ligne de commande :
conda install - c conda- forge pycodestyle pydocstyle pylint
Les outils pycodestyle, pydocstyle et pylint sont des linters, c’est- à- dire des programmes qui vont chercher les sources potentielles d’erreurs dans un code informatique. Ces erreurs peuvent être des erreurs de style ( PEP 8 et 257) ou des erreurs logiques portant par exemple sur la manipulation de variables ou le chargement de modules.
Organisation du code
Il est fondamental de toujours structurer et organiser son code de la même manière. Ainsi vous saurez tout de suite où trouver telle ou telle information et les autres programmeurs pourront eux aussi retrouver plus facilement tel ou tel élément. Voici un
exemple de code avec les différents éléments dans le bon ordre : """ Docstring d’une ligne décrivant brièvement ce que fait le programme.
Usage:
======
python nom _ du _ script. py argument1 argument2 argument1: un entier signifiant quelque chose argument2: une chaîne de caractères décrivant autre chose
""" _ _ authors _ _ = (" Thierry Thaureaux", "Sam Suffit ")
_ _ contact _ _ = (" t. thaureaux@ metatron. fr ", "samsuffit@ labasijysuis. fr")
_ _ version _ _ = "1.0.0"
_ _ copyright _ _ = "Creative Commons 2.0" _ _ date _ _ = "2021/ 03" import module _ interne1 import module _ interne _ 2 import module _ externe1 import module _ externe2 CONSTANT _ ONE = valeur ANOTHER _ CONSTANT = autre _ valeur class Superclass():
""" Résumé de la docstring décrivant la classe. Description détaillée ligne 1 Description détaillée ligne 2 Description détaillée ligne 3 Description détaillée ligne 4 """ def _ _ init _ _ ( self):
""" Résumé de la docstring décrivant le constructeur. Description détaillée ligne 1 Description détaillée ligne 2 Description détaillée ligne 3 Description détaillée ligne 4 """
[...] def simple _ methode( self):
""" Docstring d’une ligne décrivant la méthode."""
[...] def complex _ methode( self, arg1): """ Résumé de la docstring décrivant la méthode. Description détaillée ligne 1 Description détaillée ligne 2 Description détaillée ligne 3 Description détaillée ligne 4 """
[...] return valeur _ de _ retour def complex _ function( arg1, arg2, arg3, arg4):
""" Résumé de la docstring décrivant la fonction. Description détaillée ligne 1 Description détaillée ligne 2 Description détaillée ligne 3 Description détaillée ligne 4 """
[...] return valeur _ de _ retour def simple _ function( arg1, arg2):
""" Docstring d’une ligne décrivant la fonction."""
[...] return valeur _ de _ retour if _ _ name _ _ == " _ _ main _ _ ":
# Début du programme principal ( point d’entrée)
[...]
Cette docstring décrit très globalement le script. Elle pourra être consultée en invoquant la commande help() si le script est importé en tant que module. Les variables commençant et se terminant par un double underscore (__) donnent des informations sur la version du script, ses auteurs, la licence et sa date de conception. Ces métadonnées pourront être affichées via la commande help(). Ces métadonnées ne sont en rien obligatoires, mais elles sont très utiles pour la gestion du code. Suivent les informations sur l’importation des modules, sur les constantes et sur la classe et ses méthodes. Comme évoqué plus haut, une ligne vide précède la description de chaque méthode. Après les classes suivent les fonctions classiques. Leur description est elle aussi précédée de deux lignes vides. Enfin, le test retourne vrai seulement si le script est utilisé en tant que programme. Les lignes suivantes ne seront donc pas exécutées si le script est chargé en tant que module.
La PEP 20
La PEP 20 est une sorte de recueil de réflexions plus ou moins philosophiques avec des phrases simples censées guider les programmeurs Python vers la lumière du code presque parfait. Elle est accessible sous la forme d’un easter egg ( oeuf de Pâques) ou encore « fonctionnalité cachée d’un programme » . ✖