Conventions de codage
Temps de lecture10 minEn bref
Résumé de l’article
Python est devenu l’un des langages de programmation les plus populaires, notamment grâce à sa flexibilité et la simplicité de sa syntaxe. Il faut cependant différencier un script à usage quasi-unique et un programme qui a vocation à être maintenu, complété et partagé. Un moyen d’améliorer la qualité de votre code est de respecter des conventions de codage. Cet article décrit quelques bonnes pratiques issues de la PEP 8 (Python Enhancement Proposal 8) qui n’accélèreront pas l’exécution de votre code, mais qui ne le ralentiront pas non plus tout en augmentant significativement sa réutilisation.
Points importants à retenir
- La PEP 8, rédigée par les concepteurs du langage Python, décrit des bonnes pratiques de programmation.
- Ajouter des déclarations de types dans les prototypes des fonctions simplifie leur utilisation.
Contenu de l’article
1 — Quelques éléments de la PEP 8
La Python Enhancement Proposal 8 est un document assez long mais très simple à exploiter. Voici quelques bonnes pratiques issues de ce document que nous vous invitons à suivre.
Quelques conventions de nommage
- les noms de variables décrivant des indices (e.g. incréments de boucles) sont courts et en minuscule, ceux décrivant des résultats sont en minuscule avec chaque mot séparé par un underscore
_
. - les noms de fonctions sont en minuscule avec chaque mot séparé par un underscore
_
. - les noms de modules sont en minuscule et si possible sans underscore
_
. - les noms de classe commencent par une lettre en majuscule, tout comme chacun de ses mots et sans utiliser un underscore
_
.
La section suivante de la PEP8 https://peps.python.org/pep-0008/#naming-conventions décrit spécifiquement les conventions de nommage.
Commentaires
- Utilisez de manière parsimonieuse les commentaires de ligne dans le code
- Ajoutez systématiquement une docstring pour chaque module, classe et fonction.
Vous pouvez consulter la PEP 257 pour avoir plus de détails sur les conventions liées aux docstrings.
"""
Test module for UselessTools class.
This module contains a utility class for number manipulation operations,
specifically for calculating sums of odd numbers within given bounds.
"""
class UselessTools:
"""Utility class with some functions to manipulate numbers
"""
def bounded_sum_odd(self) -> int:
"""
This function calculates the sum of all odd numbers up to a given bound.
It prompts the user for the maximum bound and returns the sum of odd numbers.
"""
sum_odd = 0
i = 1
max_b = int(input("Max bound: ")) # turn the str input into an int
if max_b < 1:
raise ValueError("Max bound must be a positive integer.")
for j in range(i, max_b, 2):
sum_odd += j
return sum_odd
def __str__(self):
return "UselessTools class for number manipulation."
if __name__ == "__main__":
print(UselessTools().bounded_sum_odd())
2 — Tester la qualité syntaxique de son code
L’outil pylint
est un vérificateur statique de votre code. Il va notamment vous attribuer une note entre 0 et 10 selon le nombre de conventions de codage décrits dans la PEP8 que vous ne respectez pas.
Il peut être intéressant d’utiliser cet outil :
- lors de l’apprentissage du Python
- avant la livraison d’un code ou dans une chaîne d’intégration continue pour s’assurer de la qualité du code.
Voici comment utiliser cet outil :
session2 % pylint test.py
************* Module test
test.py:16:0: C0303: Trailing whitespace (trailing-whitespace)
test.py:21:0: C0304: Final newline missing (missing-final-newline)
test.py:1:0: C0114: Missing module docstring (missing-module-docstring)
test.py:1:0: R0903: Too few public methods (1/2) (too-few-public-methods)
-------------------------------------------------------------------
Your code has been rated at 6.36/10 (previous run: 10.00/10, -3.64)
session2 % pylint test.py
-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 6.36/10, +3.64)
3 — Typer les prototypes des fonctions
Python est un langage dit fortement typé, toute variable et toute valeur a un type (e.g. int
, float
, str
, list
, etc.). De plus, il s’agit d’un langage à typage dynamique. Ceci signifie qu’il n’est pas nécessaire de déclarer le type des variables que l’on crée, l’interpréteur Python le devine à partir de la valeur qui lui est affectée (e.g. 8
en int
, -0.5
en float
, "bonjour"
en str
, [3, 2, 5]
en list
, etc.).
Par contre, il est fortement recommandé, bien qu’uniquement informatif, d’ajouter un type à chaque paramètre de fonction ainsi que de spécifier le type du résultat retourné. Voici ci-dessous deux versions d’une même fonction ayant exactement le même comportement et la même efficacité, la seconde version étant plus simple à maintenir grâce au typage des paramètres et aux docstrings.
def add_to_sublist(lst: list[int], value: int=1, ib: int=0, ie : int=None) -> list[int]:
"""
Adds a value to each element in the provided list.
:param lst: List of integers to which the value will be added.
:param value: Integer value to add to each element of the list (default 1).
:param ib: Index of the first element to modify (default 0).
:param ie: Index of the last element to modify (default None, meaning all elements).
:return: New list with the added value.
"""
if ie is None:
ie = len(lst)
return lst[:ib] + [x + value for x in lst[ib:ie]] + lst[ie:]
def add_to_sublist2(l, v=1, ib=0, ie=None):
if ie is None:
ie = len(l)
return l[:ib] + [x + v for x in l[ib:ie]] + l[ie:]
Ces annotations informatives de type étaient définies initialement dans un module externe nommé typing
qui intègre de nombreux types possibles.
Depuis peu, les annotations de type font partie intégrante du langage Python, même si pour certains types il est nécessaire d’avoir toujours recours au module typing
. Vous pouvez consulter la page suivante https://docs.python.org/fr/3.10/library/typing.html pour avoir plus d’information sur le typage et les PEP associées.
4 — Tester les appels de fonctions
Les annotations de type sont purement informatives, l’interpréteur Python les ignore. Par contre, un outil comme mypy
peut effectuer une vérification statique que le type des paramètres fournis aux fonctions correspond bien aux types déclarés dans les prototypes.
À titre d’exemple, l’appel de fonction suivant déclenchera une erreur de mypy
car les types des deux premiers paramètres effectifs (i.e. valeurs fournies) ne sont pas en accord avec les paramètres formels (i.e. prototype de fonction) :
print(UselessTools().add_to_sublist(10, [1, 2, 3, 4, 5], 1, 4))
L’appel de mypy
sur le fichier génèrera les erreurs suivantes :
session2 % mypy test.py
test.py:51: error: Argument 1 to "add_to_sublist" of "UselessTools" has incompatible type "int"; expected "list[int]" [arg-type]
test.py:51: error: Argument 2 to "add_to_sublist" of "UselessTools" has incompatible type "list[int]"; expected "int" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
Ce genre d’outil peut par exemple être très utile dans une chaîne d’intégration continue de code pour tracker d’éventuelles erreurs qui ne seraient découvertes qu’à l’exécution du programme.