Cours
PEP‑8, pour Python Enhancement Proposal, présente des points clés qui vous aideront à rendre votre code plus organisé et lisible. Comme le dit Guido Van Rossum, le créateur de Python :
Le code est bien plus souvent lu qu'écrit.
Dans cet article, vous allez commencer à explorer PEP‑8 au travers d'exemples de code. Nous couvrirons les sujets suivants :
- Introduction à PEP‑8 : ce que c'est et pourquoi vous en avez besoin ;
- Indentation, sujet brûlant chez les programmeurs. Onglets ou espaces ? Vous trouverez la réponse dans cette section ;
- On ne s'y attend pas toujours, mais il existe des recommandations sur la longueur maximale des lignes ;
- Il y a aussi une manière conseillée de gérer les lignes vides ;
- Les espaces dans les expressions et les instructions : facile à maîtriser dès le départ ;
- Qu'est-ce que l'encodage et pourquoi en auriez-vous besoin en Python ? Quel est l'encodage par défaut de Python 3 ? La section sur l'encodage des fichiers source répond à ces questions.
- Vous faites probablement des imports très souvent en codant. Cette section traite de l'ordre des imports, des imports absolus et relatifs, des imports génériques (wildcards), etc. ;
- La documentation est essentielle pour suivre tous les aspects d'une application et améliorer la qualité globale du produit final. Les commentaires sont clés ici !
- Connaissez-vous les dunder names au niveau module ? Ils sont particulièrement utiles dans les docstrings !
- Enfin, vous en apprendrez plus sur les conventions de nommage : comment nommer vos fonctions, quels styles employer, et bien plus encore ;
- Votre code est-il conforme à PEP‑8 ? C'est une question à vous poser systématiquement. La dernière section présente des outils pour vérifier si votre code respecte les recommandations évoquées ici et bien d'autres encore.
Introduction à PEP‑8
Le langage Python s'est imposé comme l'un des langages de programmation privilégiés par beaucoup. Il est relativement simple à apprendre, multi‑paradigme, dispose de nombreux modules open source qui étendent ses possibilités, et c'est un outil de référence en data science comme en développement web.
Cependant, vous ne tirerez pleinement parti de Python que si vous savez exprimer clairement vos idées dans votre code. Python a été conçu avec certains objectifs en tête, que vous pouvez découvrir en tapant import this.
import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Voici les 20 principes qui guident la programmation Python, connus sous le nom de Zen of Python. Vous remarquerez notamment « Readability counts », qui devrait rester votre priorité lorsque vous écrivez du code : d'autres développeurs ou data scientists doivent pouvoir comprendre et contribuer à votre code pour résoudre la tâche visée.
Les sections suivantes vous donnent des pistes concrètes pour y parvenir.
Indentation
En Python, l'indentation est incontournable. Manipulez‑la toutefois avec soin, sous peine d'erreurs de syntaxe. La recommandation est d'utiliser quatre espaces pour l'indentation. Par exemple, cette instruction utilise quatre espaces :
if True:
print("If works")
Cette boucle for suivie d'un print est également indentée avec quatre espaces :
for element in range(0, 5):
print(element)
Lorsque vous écrivez une expression longue, alignez‑la verticalement. Vous créez ainsi un « hanging indent ».
Voici des exemples de hanging indent dans des expressions longues, avec plusieurs variantes d'usage :
-
value = square_of_numbers(num1, num2, num3, num4) -
def square_of_number( num1, num2, num3, num4): return num1**2, num2**2, num3**2, num4**2 -
value = square_of_numbers( num1, num2, num3, num4) -
list_of_people = [ "Rama", "John", "Shiva" ] -
dict_of_people_ages = { "ram": 25, "john": 29, "shiva": 26 }
Toute personne qui code en Python (ou dans un autre langage) se demande un jour s'il faut utiliser des tabulations ou des espaces pour l'indentation. Le débat tabs vs spaces fait rage depuis longtemps dans la communauté. Voyez par exemple cet article de Stackoverflow.
En général, les espaces sont préférés. Mais si vous travaillez sur un script Python qui utilise déjà les tabulations, poursuivez avec des tabulations. Dans le cas contraire, remplacez l'indentation de toutes les expressions par des espaces.
Remarque : Python 3 n'autorise pas de mélanger tabulations et espaces pour l'indentation. Choisissez donc l'un des deux et tenez‑vous‑y !
Longueur maximale des lignes
Visez en règle générale une longueur de ligne de 79 caractères dans votre code Python.
Respecter cet objectif présente plusieurs avantages :
- Vous pouvez ouvrir des fichiers côte à côte pour les comparer ;
- Vous visualisez une expression entière sans faire défiler horizontalement, ce qui améliore la lisibilité et la compréhension du code.
Les commentaires doivent se limiter à 72 caractères par ligne. Vous verrez plus loin les conventions les plus courantes pour les commentaires.
Au final, si vous travaillez dans une petite équipe, vous êtes libre d'adapter votre style et de vous écarter raisonnablement de cette règle. En revanche, si vous créez ou contribuez à un projet open source, vous voudrez et/ou devrez probablement respecter la longueur maximale fixée par PEP‑8.
Lorsque vous utilisez l'opérateur +, privilégiez une coupure de ligne appropriée pour rendre le code plus clair :
| À privilégier… | À éviter… |
|---|---|
|
|
|
Vous pourriez aussi écrire :
total = A
+ B
+ C
En bref, vous pouvez couper avant ou après un opérateur binaire, du moment que vous restez cohérent. Pour du nouveau code, essayez de suivre la dernière option présentée, en ajoutant la coupure avant l'opérateur.
Lignes vides
Dans les scripts Python, les fonctions et classes de premier niveau sont séparées par deux lignes vides. Les méthodes au sein des classes sont séparées par une ligne vide. L'exemple suivant l'illustre clairement :
import unittest
class SwapTestSuite(unittest.TestCase):
"""
Swap Operation Test Case
"""
def setUp(self):
self.a = 1
self.b = 2
def test_swap_operations(self):
instance = Swap(self.a,self.b)
value1, value2 =instance.get_swap_values()
self.assertEqual(self.a, value2)
self.assertEqual(self.b, value1)
class OddOrEvenTestSuite(unittest.TestCase):
"""
This is the Odd or Even Test case Suite
"""
def setUp(self):
self.value1 = 1
self.value2 = 2
def test_odd_even_operations(self):
instance1 = OddOrEven(self.value1)
instance2 = OddOrEven(self.value2)
message1 = instance1.get_odd_or_even()
message2 = instance2.get_odd_or_even()
self.assertEqual(message1, 'Odd')
self.assertEqual(message2, 'Even')
Les classes SwapTestSuite et OddOrEvenTestSuite sont séparées par deux lignes vides, tandis que les définitions de méthodes comme .setUp() et .test_swap_operations() ne sont séparées que par une ligne vide. Le code s'exécutera sans produire de sortie, car il manque le déclenchement des tests unitaires (il s'agit juste de bonnes pratiques).
Espaces dans les expressions et instructions
Évitez les espaces superflus dans les cas suivants :
| À privilégier… | À éviter… |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ou
ou
|
|
|
|
|
|
|
Exemples extraits de PEP‑8.
Encodage des fichiers source
Un ordinateur ne stocke pas des « lettres », « chiffres », « images » ou quoi que ce soit d'autre ; il ne stocke et ne manipule que des bits, qui prennent des valeurs binaires : oui ou non, vrai ou faux, 1 ou 0, etc. Comme vous le savez, un ordinateur fonctionne à l'électricité : un bit « réel » correspond à la présence ou l'absence d'une impulsion électrique. On représente généralement cette (non‑)présence par 1 et 0.
Pour représenter autre chose que des bits, il faut un ensemble de règles : convertir une suite de bits en lettres, chiffres ou images via un schéma d'encodage (encoding). Exemples : ASCII, UTF‑8, etc. :
- L'American Standard Code for Information Interchange (ASCII) est le format le plus courant pour les fichiers texte sur ordinateur et sur Internet. Dans ces fichiers, chaque caractère alphabétique, numérique ou spécial est représenté par un nombre binaire sur 7 bits (une chaîne de sept 0 ou 1).
- Le standard Unicode (Unicode Worldwide Character Standard), abrégé en Unicode, est un système pour « l'échange, le traitement et l'affichage des textes écrits des différentes langues du monde moderne ». En bref, Unicode vise à couvrir tous les systèmes d'écriture connus. Unicode utilise actuellement trois encodages pour représenter les jeux de caractères : UTF‑8, UTF‑16 et UTF‑32.
- UTF‑16 est un encodage Unicode à longueur variable : les points de code sont encodés sur une ou deux unités de 16 bits.
- UTF‑8 est un autre encodage Unicode à longueur variable, utilisant d'un à quatre octets de 8 bits.
- UTF‑32 est un encodage à longueur fixe utilisant exactement 32 bits par point de code Unicode.
Astuce : pour en savoir plus sur l'encodage, consultez cet article.
Pourquoi est‑ce important ?
Les chaînes de caractères figurent parmi les types de données les plus utilisés en Python. Il arrivera donc que vous manipuliez des chaînes contenant (ou composées de) caractères hors ASCII standard. Par exemple, des textes avec des caractères accentués comme á, ž, ç, etc.
En Python 3, l'encodage source par défaut est UTF‑8. En Python 2, l'encodage par défaut était ASCII.
Que se passe‑t‑il alors si vous avez une chaîne contenant un caractère non‑ASCII, comme "Flügel" ?
En la référençant dans Python 2, vous obtiendrez :
>>> s
'Fl\xfcgel'
Ce n'est pas exactement votre chaîne ! Et si vous l'imprimez ?
>>> print(s)
Flügel
L'affichage renvoie la valeur assignée à la variable. Le caractère non‑ASCII ÃŒ a été encodé. C'est pourquoi vous avez vu \xfc en référence directe. Pour gérer cela, utilisez les méthodes de chaîne .encode() et .decode(). La première renvoie une version octet (8 bits) de la chaîne Unicode dans l'encodage demandé ; la seconde interprète la chaîne selon l'encodage fourni.
Imports
Importer des bibliothèques et/ou des modules est une action courante en Python, notamment en data science. Comme vous le savez sans doute, placez toujours vos imports au début du script.
Remarque : si vous avez beaucoup d'imports, déclarez‑les un par ligne.
Le tableau suivant illustre cette bonne pratique :
| À privilégier… | À éviter… |
|---|---|
|
ou
|
|
De plus, respectez l'ordre d'import suivant :
- Imports de la bibliothèque standard.
- Imports de dépendances tierces.
- Imports spécifiques à l'application/bibliothèque locale.
Imports absolus et relatifs
Il est utile de distinguer imports absolus et relatifs. En général, les imports absolus sont préférés en Python car ils améliorent la lisibilité. Cependant, à mesure que votre application gagne en complexité, les imports relatifs peuvent aussi être pertinents. Les imports relatifs implicites ne doivent jamais être utilisés et ont été supprimés en Python 3.
De quoi s'agit‑il exactement ?
-
Un import absolu utilise le chemin complet de la fonction ou de la classe, séparé par des
.. Par exemple,import sklearn.linear_model.LogisticRegression -
Un import relatif est relatif à l'emplacement du fichier Python courant. Utile quand votre projet grossit, il peut améliorer la lisibilité de la structure. Ainsi, si votre projet ressemble à ceci :
. ├── __init__.py ├── __init__.pyc ├── __pycache__ │ ├── __init__.cpython-35.pyc │ ├── bubble_sort.cpython-35.pyc │ ├── selection_sort.cpython-35.pyc ├── bubble_sort.py ├── heap_sort.py ├── insertion_sort.py ├── insertion_sort.pyc ├── merge_sort.py ├── merge_sort.pyc ├── quick_sort.py ├── radix_sort.py ├── selection_sort.py ├── selection_sort.pyc ├── shell_sort.py ├── tests │ ├── test1.py
Vous pouvez utiliser un import relatif pour importer l'algorithme de tri à bulles BubbleSort, défini dans bubble_sort.py, dans test1 :
from ..bubble_sort import BubbleSort
Pour en savoir plus sur les imports absolus et relatifs, consultez PEP 328.
Imports génériques (wildcards)
Évitez les imports génériques, car ils nuisent à la lisibilité : on ne sait pas quelles classes, fonctions ou variables sont réellement utilisées depuis le module. Par exemple :
from scikit import *
Commentaires
Les commentaires servent de documentation inline en Python. Ils facilitent la compréhension du code. Il existe de nombreux outils pour générer de la documentation, notamment à partir de commentaires et docstrings, pour votre propre module. Les commentaires doivent être suffisamment explicites pour qu'un lecteur comprenne le code et son articulation avec le reste.
Les commentaires commencent par le symbole #. Tout ce qui suit n'est pas exécuté par l'interpréteur. Par exemple, le fragment suivant n'affichera que "This is a Python comment".
# This is a Python single line comment
print("This is a Python comment")
Rappel : comme vu plus haut, les commentaires ne doivent pas dépasser 72 caractères par ligne.
On distingue trois types de commentaires :
- Les commentaires de bloc expliquent du code complexe ou peu familier. Généralement plus longs, ils s'appliquent à une portion de code qui suit. Ils sont indentés au même niveau que le code, chaque ligne commence par
#suivi d'un espace. Si vous avez besoin de plusieurs paragraphes, séparez‑les par une ligne contenant un seul#.
Extrait suivant, tiré de la bibliothèque scikit-learn :
if Gram is None or Gram is False:
Gram = None
if copy_X:
# force copy. setting the array to be fortran-ordered
# speeds up the calculation of the (partial) Gram matrix
# and allows to easily swap columns
X = X.copy('F')
- Utilisez avec parcimonie les commentaires en fin de ligne (inline), même s'ils peuvent être utiles pour éclairer une instruction précise, vous rappeler sa signification, ou aider un collaborateur moins familier avec votre code. Ils se placent sur la même ligne que l'instruction, commencent par
#et un espace.
Par exemple :
counter = 0 # initialize the counter
- Les chaînes de documentation (docstrings) s'écrivent en tête des modules, fichiers, classes et méthodes publics. Elles commencent par
"""et se terminent par""":
"""
This module is intended to provide functions for scientific computing
"""
Dunder names au niveau module
Maintenant que vous savez ce que sont les docstrings, parlons des dunder names (noms entourés de deux underscores), très utiles en Python. Ces noms spéciaux sont définis par Python pour éviter les conflits avec des noms ou fonctions définis par l'utilisateur. Pour en savoir plus, lisez cet article.
Des dunders au niveau module comme (__all__, __author__, __version__) doivent être placés après la docstring principale du module et avant toute instruction import. Les imports from __future__ doivent être définis avant tout autre code, à l'exception des docstrings :
"""
Algos module consists of all the basic algorithms and their implementation
"""
from __future__ import print
__all__ = ['searching', 'sorting']
__version__ = '0.0.1'
__author__ = 'Chitrank Dixit'
import os
import sys
Astuce : consultez ces conventions pour rédiger vos docstrings.
Conventions de nommage
En Python, vous suivrez presque toujours des conventions de nommage : un ensemble de règles pour choisir les séquences de caractères utilisées pour les identifiants désignant variables, types, fonctions et autres entités dans le code source et la documentation.
Si vous hésitez sur les styles possibles, en voici quelques‑uns :
bou une seule lettre minuscule ;Bou une seule lettre majuscule ;lowercaseUPPERCASElower_case_with_underscoresUPPER_CASE_WITH_UNDERSCORESCapitalizedWords, aussi appeléCapWords,CamelCaseouStudlyCaps.mixedCaseCapitalized_Words_With_Underscores_single_leading_underscore: indicateur (faible) d'usage interne. Par exemple,from M import *n'importe pas les objets dont le nom commence par un underscore.single_trailing_underscore_: utilisé par convention pour éviter un conflit avec un mot‑clé Python, par exempleTkinter.Toplevel(master, class_='ClassName')__double_leading_underscore: pour un attribut de classe, déclenche le name mangling (dans la classeFooBar,__boodevient_FooBar__boo).__double_leading_and_trailing_underscore__: objets ou attributs « magiques » présents dans des espaces de noms contrôlés par l'utilisateur. Par exemple,__init__,__import__ou__file__. N'inventez jamais de tels noms ; utilisez‑les uniquement tels que documentés.
Conventions générales
Le tableau suivant donne des lignes directrices générales pour nommer vos identifiants :
| Identifiant | Convention |
|---|---|
| Module | lowercase |
| Classe | CapWords |
| Fonctions | lowercase |
| Méthodes | lowercase |
| Variables de type | CapWords |
| Constantes | UPPERCASE |
| Package | lowercase |
- N'utilisez pas « l », « O » ou « I » comme noms de variable isolés : ces caractères ressemblent à zéro (
0) et un (1) dans certaines polices. - En général, privilégiez des noms courts quand c'est possible. Les underscores améliorent parfois la lisibilité.
Pour connaître les exceptions à ces conventions générales, consultez cet article.
Votre code est‑il conforme à PEP‑8 ?
Après cette présentation, vous vous demandez sans doute comment vérifier que votre code respecte ces recommandations (et bien d'autres non abordées ici).
Au‑delà de la lecture directe de PEP‑8, pensez au module pep8, au package coala et à d'autres alternatives décrites dans les sections suivantes.
Package Python pep8
Le package pep8 permet de détecter les incompatibilités avec PEP‑8 dans votre code Python et de suggérer des corrections. Installez le module pep8 avec pip via la commande suivante :
pip install pep8
Pour illustrer le fonctionnement de pep8, créez un fichier Python example.py avec le code suivant :
def my_function(a, b):
print("The sum of a and b is: ", a + b)
a = 1
b = 2
my_function(a,b)
Enregistrez le fichier puis exécutez la commande pep8 dessus dans votre terminal :
pep8 example.py
La sortie indiquera les incompatibilités détectées, par exemple :
example.py:1:1: E302 expected 2 blank lines, found 0
example.py:3:5: E225 missing whitespace around operator
example.py:5:5: E225 missing whitespace around operator
La première ligne indique qu'il devrait y avoir deux lignes vides avant la définition de fonction à la ligne 1. Les deux suivantes signalent l'absence d'espace autour de l'opérateur d'addition dans l'instruction print et dans les affectations.
Pour corriger, modifiez le code ainsi :
def my_function(a, b):
print("The sum of a and b is:", a + b)
a = 1
b = 2
my_function(a, b)
En relançant pep8 example.py, vous ne devriez plus avoir de sortie : le code est conforme à PEP‑8.
Vous pouvez aussi afficher la portion de code concernée avec l'argument --show-source :
$ pep8 --show-source --show-pep8 testsuite/E40.py
testsuite/E40.py:2:10: E401 multiple imports on one line
import os, sys
^
Imports should usually be on separate lines.
Okay: import os\nimport sys
E401: import sys, os
Ou afficher des statistiques avec --statistics :
$ pep8 --statistics -qq Python-2.5/Lib
232 E201 whitespace after '['
599 E202 whitespace before ')'
631 E203 whitespace before ','
842 E211 whitespace before '('
2531 E221 multiple spaces before operator
4473 E301 expected 1 blank line, found 0
4006 E302 expected 2 blank lines, found 1
165 E303 too many blank lines (4)
325 E401 multiple imports on one line
3615 E501 line too long (82 characters)
612 W601 .has_key() is deprecated, use 'in'
1188 W602 deprecated form of raising exception
Astuce : regardez aussi flake8, autopep8 ou pylint !
Analyser votre code avec coala
coala propose du linting et des corrections pour de nombreux langages ; ici, on s'intéresse à Python. Installez coala avec pip :
$ pip3 install coala-bears
Dans la commande ci‑dessus, vous installez coala-bears : les bears sont des modules/plugins qui étendent les capacités de coala selon les langages. Dans notre cas, utilisez pep8bear, qui détecte et corrige les écarts à PEP‑8. À envisager sérieusement pour contrôler votre code Python.
$ coala -S python.bears=PEP8Bear python.files=\*\*/\*.py \
python.default_actions=PEP8Bear:ApplyPatchAction --save
# other output ...
Executing section python...
[INFO][11:03:37] Applied 'ApplyPatchAction' for 'PEP8Bear'.
[INFO][11:03:37] Applied 'ApplyPatchAction' for 'PEP8Bear'.
pep8online : vérifiez votre code Python en ligne
En plus du module pep8 et du package coala, vous pouvez tester la conformité PEP‑8 de votre code sur pep8online. Collez votre code dans l'éditeur en ligne, cliquez sur « Check code » et obtenez un retour immédiat sur les points à améliorer. Pratique !
Conclusion
Avec Python, on néglige parfois la qualité du code sous la pression des livraisons. Pourtant, les pratiques décrites dans ce tutoriel — et bien d'autres — devraient faire partie de votre cycle développer‑stager‑tester‑déployer. Tout le monde y gagne : la compréhension du projet progresse et la plupart des modifications peuvent être réalisées sans plonger dans un débogueur pour comprendre le code.
Si vous travaillez sur un projet open source, vos contributeurs apprécieront le respect de PEP‑8 et comprendront mieux votre code, puisqu'il s'agit du standard universel suivi par la communauté Python.
Après cette lecture, prenez le temps d'explorer directement PEP‑8 : il y a encore beaucoup à découvrir.
Vous avez des astuces supplémentaires pour respecter PEP‑8, ou pensez que nous avons oublié un point important ? Dites‑le‑nous sur @DataCamp.