IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre Python et s'initier à la programmation

Partie 2 : Programmation avancée


précédentsommaire

VII. Qualité de code

Nous voilà déjà au bout de cette initiation à la programmation avec le langage Python. Pour terminer ce livre, on va s'intéresser à la qualité de code et aux conventions de codage à adopter lorsqu'on écrit un programme. C'est en effet une bonne chose que le programme fonctionne et fasse ce qu'il faut, mais s'il est de bonne qualité, c'est encore mieux. En particulier, on va évidemment voir quelles sont les conventions utilisées en Python, pour écrire des programmes pythoniques.

VII-A. Qualité de code

Un code est correct lorsqu'il fait ce qu'il faut, c'est-à-dire qu'il calcule le résultat qu'on attend de lui. Cette notion générale est plutôt difficile à vérifier et on s'intéresse dès lors plutôt à un code correct par rapport à ses spécifications. Un tel code, une fois exécuté, satisfait toujours ses postconditions, peu importe comment on l'appelle et avec quels paramètres, pour autant qu'on respecte les préconditions. Il s'agit, en somme, d'un code dont l'implémentation respecte le contrat établi par ses spécifications. Il est parfois possible de s'assurer qu'un code est correct par rapport à ses spécifications.

Une fois qu'on a un tel code, on pourrait déjà être content. Mais il faut savoir que, le temps de sa durée de vie, un code est plus souvent lu qu'écrit. Il est dès lors important d'accorder un certain soin à sa rédaction, tout comme un auteur n'écrit pas un livre n'importe comment. Un code de qualité en est un qui respecte une série de conventions et règles de bonne pratique, le rendant facile à lire et comprendre par n'importe qui. Un autre avantage d'un tel code est qu'il sera plus facile à mettre à jour et qu'il sera moins probable d'y avoir des bogues.

VII-A-1. Outil Pylint

Un code peut être audité pour évaluer sa qualité. Il s'agit, en bref, de parcourir un code pour vérifier qu'il respecte une série de critères et règles précises, notamment en termes de qualité. De manière générale, on peut également évaluer sa robustesse, son niveau de sécurité, identifier ses éventuelles vulnérabilités, etc.

Concernant la qualité de code, il est possible d'automatiser toute une série de vérifications. Par exemple, l'outil Pylint permet de vérifier automatiquement de nombreuses règles. Il peut notamment :

  • vérifier les conventions de codage décrites dans le PEP 0008 ;
  • détecter automatiquement des erreurs comme des variables ou imports non utilisés, etc. ;
  • repérer des duplications de code et autres mauvaises pratiques nécessitant de modifier le code pour améliorer sa qualité.

Prenons l'exemple de programme suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
import sys

def getbirthyear(value):
    while(True):
        try:
            birthyear=int(input('Année de naissance ? '))
            if 0 <= birthyear <= 2016: return birthyear
        except ValueError:
            pass

b = getbirthyear(None)          
print('Vous avez {} ans.'.format(2016 - b))

Le programme demande à l'utilisateur d'entrer son année de naissance. Tant qu'il ne fournit pas un nombre qui est compris entre kitxmlcodeinlinelatexdvp0finkitxmlcodeinlinelatexdvp et kitxmlcodeinlinelatexdvp2016finkitxmlcodeinlinelatexdvp, une boucle infinie permet de lui redemander d'entrer une valeur correcte. Une fois qu'il a donné une valeur correcte, son âge est calculé et affiché. Enfin, on a placé la partie du programme qui demande l'année de naissance dans une fonction getbirthyear.

L'exécution de l'outil Pylint sur ce programme donne le résultat suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
No config file found, using default configuration
************* Module test
C:  4, 0: Unnecessary parens after 'while' keyword (superfluous-parens)
C:  6, 0: Exactly one space required around assignment
            birthyear=int(input('Année de naissance ? '))
                     ^ (bad-whitespace)
C: 11, 0: Trailing whitespace (trailing-whitespace)
C: 12, 0: Final newline missing (missing-final-newline)
C:  1, 0: Missing module docstring (missing-docstring)
C:  3, 0: Missing function docstring (missing-docstring)
C:  7,39: More than one statement on a single line (multiple-statements)
W:  3,17: Unused argument 'value' (unused-argument)
C: 11, 0: Invalid constant name "b" (invalid-name)
W:  1, 0: Unused import sys (unused-import)

Chaque ligne de ce rapport contient plusieurs informations :

  • elle commence avec une lettre indiquant le type de message (C pour une convention non respectée, W pour un avertissement de mauvais style et E pour une erreur qui pourrait causer un bogue) ;
  • viennent ensuite deux nombres indiquant le numéro de la ligne et le numéro de la colonne liés au message fourni ;
  • et enfin, vient une description textuelle de l'élément de mauvaise qualité trouvé.

Pour comprendre le genre d'éléments de mauvaise qualité que Pylint est capable de retrouver, passons en revue chaque ligne du rapport :

  1. Des parenthèses inutiles ont été détectées après le mot réservé while, ce qui alourdit le code inutilement ;
  2. Il manque exactement une espace avant et après le symbole = utilisé pour faire une affectation ;
  3. Il y a des espaces inutiles au bout de la ligne ;
  4. Il manque un retour à la ligne en fin de fichier ;
  5. Il manque la documentation du module ;
  6. Il manque la documentation de la fonction ;
  7. Plusieurs instructions ont été placées sur la même ligne (on a en effet le corps de l'instruction if qui a été placé sur la même ligne, ce qui est autorisé par Python lorsque ce dernier n'est composé que d'une seule instruction) ;
  8. Le paramètre value n'est pas utilisé (la fonction getbirthyear reçoit en effet un paramètre inutile) ;
  9. Le nom de la constante b est invalide (le nom est en effet trop court) ;
  10. L'import de sys n'est pas utilisé.

Toutes ces remarques vont pouvoir être prises en compte afin de modifier le programme. Notez bien que ce dernier est correct, car il fait exactement ce pour quoi il a été conçu. Le seul reproche qu'on lui fait est de ne pas avoir un certain niveau de qualité étant donné qu'une série de conventions et règles de bonne pratique n'ont pas été respectées. Voici la nouvelle version du programme, qui passe tous les tests de qualité faits par Pylint :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
"""Module de calcul d'âge."""

def getbirthyear():
    """Demande son année de naissance à l'utilisateur."""
    while True:
        try:
            birthyear = int(input('Année de naissance ? '))
            if 0 <= birthyear <= 2016:
                return birthyear
        except ValueError:
            pass

YEAR = getbirthyear()
print('Vous avez {} ans.'.format(2016 - YEAR))

Pylint ne sait évidemment pas tout vérifier. Par exemple, la variable YEAR est-elle vraiment nécessaire puisqu'elle n'est utilisée qu'une seule fois. On aurait directement pu faire l'appel à la fonction getbirthyear dans la dernière instruction et remplacer les deux dernières lignes par l'unique instruction suivante :

 
Sélectionnez
print('Vous avez {} ans.'.format(2016 - getbirthyear()))

On détaillera les conventions PEP 0008, vérifiées par Pylint, dans la deuxième section de ce chapitre.

VII-A-1-a. Règles de bonne pratique

Voyons maintenant plusieurs règles de bonne pratique à respecter si l'on veut écrire un code de qualité. Ce que l'on va ici voir n'est pas nécessairement lié à Python et peut s'appliquer à des programmes écrits dans d'autres langages.

VII-A-1-a-i. Variable

On utilise une variable pour une seule raison, qui est clairement établie. Il faut, par exemple, éviter d'avoir une variable dont la signification dépend de la valeur de son contenu. De même, on évitera d'utiliser une variable fourre-tout qui change de rôle en fonction de la partie du programme dans laquelle on se trouve.

On utilise une variable pour stocker une valeur qui est utilisée plusieurs fois, afin d'éviter une duplication de code. On peut aussi utiliser une variable pour éviter un nombre magique, c'est-à-dire un nombre qui apparait dans une instruction sans que l'on sache forcément ce qu'il représente. Le mettre dans une variable, qui aura donc un nom, permet de rendre sa signification explicite. Pour faire mieux, on va même mettre son nom en majuscules, ce qui signifie, par convention, qu'il s'agit d'une constante. L'exemple suivant comporte deux erreurs de style :

 
Sélectionnez
for code in database['beverage']['beers']:
    print(database['beverage']['beers'][code]['name'])
    print(database['beverage']['beers'][code]['price'] * 1.21)

Comme on utilise plusieurs fois la valeur database['beverage']['beers'], on va la placer dans une variable. De plus, la multiplication par kitxmlcodeinlinelatexdvp1.21finkitxmlcodeinlinelatexdvp est faite pour calculer le prix incluant les taxes qui sont de kitxmlcodeinlinelatexdvp21%finkitxmlcodeinlinelatexdvp et on va le rendre explicite en définissant une constante TAX_RATE :

 
Sélectionnez
1.
2.
3.
4.
5.
TAX_RATE = 0.21
beers = database['beverage']['beers']
for code in beers:
    print(beers[code]['name'])
    print(beers[code]['price'] * (1 + TAX_RATE))

Après cette première amélioration du code, on se rend compte que l'accès à beers[code] est dupliqué au sein de la boucle. On pourrait donc encore l'améliorer en créant une nouvelle variable, dans la boucle, pour stocker la bière en cours de traitement :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
TAX_RATE = 0.21
beers = database['beverage']['beers']
for code in beers:
    beer = beers[code]
    print(beer['name'])
    print(beer['price'] * (1 + TAX_RATE))

Et qu'en est-il du chiffre magique 1 ? On pourrait vouloir le remplacer, mais c'est normalement suffisamment clair qu'il s'agit simplement d'une formule permettant de calculer le prix taxes incluses lorsqu'on connait le taux de taxation.

Enfin, vous aurez remarqué qu'on n'a pas choisi le nom des variables utilisées n'importe comment. Un bon nom de variable doit expliquer sa raison d'être, en décrivant la variable et son contenu. De manière générale, on évite des noms trop courts ou trop longs (on parle souvent d'un nombre de caractères compris entre kitxmlcodeinlinelatexdvp9finkitxmlcodeinlinelatexdvp et kitxmlcodeinlinelatexdvp15finkitxmlcodeinlinelatexdvp, en moyenne).

De manière générale, on évite les lettres ambigües. Il est, par exemple, facile de confondre char1 (chiffre kitxmlcodeinlinelatexdvp1finkitxmlcodeinlinelatexdvp) avec charl (lettre kitxmlcodeinlinelatexdvpLfinkitxmlcodeinlinelatexdvp) ou COnf (lettre kitxmlcodeinlinelatexdvpOfinkitxmlcodeinlinelatexdvp) et C0nf (chiffre kitxmlcodeinlinelatexdvp0finkitxmlcodeinlinelatexdvp). De plus, on essaie, dans la mesure du possible, d'utiliser des noms anglais et on n'oublie pas de faire attention à l'orthographe. Enfin, on évitera d'avoir des noms proches dans le même programme (par exemple input et inputVal) ou similaires (par exemple clientRep et clientRec).

Voici encore trois autres exemples de noms à éviter, avec un exemple de bon nom à mettre à la place :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
# Pas bien
My_AgE = 27
istheavatardeadornot = False
the_price_of_the_item = 8.99

# Bien
age = 25
dead = True
price = 12.99
VII-A-1-a-ii. Expression booléenne

Il est très important de bien maitriser le type booléen pour éviter d'écrire du code inutile. Tout d'abord, il faut se rappeler qu'une condition d'un if est une expression booléenne. Dès lors, si on utilise une instruction if-else pour initialiser une variable booléenne, on doit en fait s'en passer et directement affecter la condition à la variable :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
# Pas bien
if x > 170:
    accepted = True
else:
    accepted = False

# Bien
accepted = x > 170

Si les valeurs affectées à la variable accepted étaient inversées, on aurait utilisé l'opérateur logique not pour inverser la valeur de la condition :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
# Pas bien
if x > 170:
    refused = False
else:
    refused = True

# Bien
refused = not x > 170

De plus, pour tester si une variable booléenne vaut True ou False, on ne doit pas utiliser l'opérateur d'égalité. En effet, la variable, étant de type booléen, est déjà elle-même une condition valable :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
# Pas bien
if hasmoney == True:
    print('Je peux acheter !')

if canaccess == False:
    print('Ne pas rentrer !')

# Bien
if hasmoney:
    print('Je peux acheter !')

if not canaccess:
    print('Ne pas rentrer !')

Enfin, il ne faut pas hésiter à simplifier les expressions booléennes, surtout celles utilisées comme condition, en exploitant les équivalences logiques. Par exemple, not x == y est équivalent à x != y :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
# Pas bien
success = not grade < 10
result = not (temperature > 15 and temperature <= 32)

# Bien
success = grade >= 10
result = temperature <= 15 or temperature > 32

Le deuxième exemple exploite les lois de De Morgan qui disent que not (x and y) est équivalent à not x or not y et que not (x or y) est équivalent à not x and not y.

VII-A-1-a-iii. Variable indexée

Lorsqu'on doit stocker plusieurs fois la même information, mais concernant des entités différentes, on doit utiliser une liste plutôt que de déclarer plusieurs variables qui auront presque les mêmes noms, ne différant que par un nombre ajouté en fin de nom, par exemple. Cela permet d'éviter de déclarer pleins de variables et permet également un traitement plus facile à l'aide de boucles :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
# Pas bien
grade1 = 12.5
grade2 = 7.5
grade3 = 18.5

print(grade1)
print(grade2)
print(grade3)

# Bien
grades = [12.5, 7.5, 18.5]

for i in range(3):
    print(grades[i])

La liste est donc adaptée lorsqu'on a une séquence de données indexées par un nombre entier. Lorsque le nom des variables cache des propriétés, on va plutôt se tourner vers un dictionnaire :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
# Pas bien
grade_louis_python = 18.5
grade_louis_proba = 8.5
grade_valerian_python = 2.5
grade_sam_python = 10
grade_cousin_proba = 11.5

# Bien
grades = {
    'louis': {'python': 18.5, 'proba': 8.5},
    'valerian': {'python': 2.5},
    'sam': {'python': 10},
    'cousin': {'proba': 11.5}
}
VII-A-1-a-iv. Instruction inutile

De manière générale, il faut essayer de garder un code le plus compact possible et éviter les instructions inutiles. On essaiera ainsi de limiter la longueur des lignes (en nombre de caractères) et également le niveau d'indentation. On peut, par exemple, éliminer le bloc else dans deux situations précises.

Lorsqu'on utilise une instruction if-else pour initialiser une variable, qui peut donc recevoir deux valeurs différentes, on peut éliminer le bloc else en initialisant la variable avec la valeur qu'elle aurait eue dans ce dernier, avant l'instruction if. En somme, c'est comme si on affectait une valeur par défaut à la variable, puis qu'on la changeait si une condition est satisfaite :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
# Pas bien
if grade < 10:
    verdict = 'Raté'
else:
    verdict = 'Réussi'

# Bien
verdict = 'Réussi'
if grade < 10:
    verdict = 'Raté'

L'autre situation où on peut diminuer le niveau d'indentation est lorsqu'on définit une fonction qui renvoie deux valeurs possibles en fonction d'une condition. Dans ce cas, et puisque return quitte immédiatement l'exécution du corps d'une fonction, on peut simplement éliminer le else et placer le contenu de son bloc après l'instruction if :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
# Pas bien
def verdict(grade):
    if grade < 10:
        return 'Raté'
    else:
        return 'Réussi'

# Bien
def verdict(grade):
    if grade < 10:
        return 'Raté'
    return 'Réussi'
VII-A-1-a-v. Duplication de code

Enfin, un dernier point qu'il faut essayer d'éviter à tout prix, ce sont les duplications de code. On a déjà vu qu'on pouvait en éliminer en définissant des nouvelles variables lorsqu'on avait une duplication exacte qui faisait référence à une même valeur.

On peut en fait combattre la duplication exacte et quasi exacte de code, et on le fait pour plusieurs raisons. Tout d'abord, si on trouve une erreur dans le code dupliqué, il faudra aller la corriger partout avec le risque d'oublier des corrections et de laisser des bogues. De plus, la duplication alourdit inutilement le code, le rendant difficile à lire. Enfin, lorsqu'on fait de la quasi-duplication, on finit par s'embrouiller et ne plus cerner les subtiles différences, rendant toute mise à jour du code périlleuse.

Une technique permettant d'éliminer de la quasi-duplication de code consiste à définir une nouvelle fonction, qui prend autant de paramètres qu'il y a de différences entre les codes similaires. Regardons l'exemple suivant pour comprendre cela :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
# Pas bien
dvd = 17.50
coca = 0.70
print('Prix :', dvd * 1.21)
print('Prix :', coca * 1.21)

# Bien
def taxprice(price):
    return price * 1.21

items = {'dvd': 17.50, 'coca': 0.70}
for name in items:
    print('Prix :', taxprice(items[name]))
VII-A-1-a-vi. Découpe en fonctions

Lorsqu'on code, il faut également réfléchir au futur et à la réutilisabilité. Il est ainsi parfois utile de définir une nouvelle fonction, qui est suffisamment générale que pour pouvoir servir à nouveau, éventuellement dans un autre contexte. Partons de l'exemple suivant qui a pour but de trier un tuple de deux valeurs :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
# Pas bien
data = (12, 7)
if data[0] < data[1]:
    sorted = data
else:
    sorted = (data[1], data[0])

On pourrait commencer par éliminer le bloc else avec la technique de la valeur par défaut. On pourrait même, avant cela, inverser les deux blocs du if-else et la condition du if, de sorte que le cas par défaut soit sorted = data :

 
Sélectionnez
1.
2.
3.
4.
5.
# Mieux
data = (12, 7)
sorted = data
if data[0] >= data[1]:
    sorted = (data[1], data[0])

Enfin, pour faire encore mieux, il convient d'anticiper le fait que l'on risque de devoir trier un tuple de deux éléments dans le futur. Il deviendrait dès lors intéressant de définir une fonction pour cela, qui sera réutilisable en cas de besoin. On définit donc une fonction sortpair, qui n'est pour le moment appelée qu'une seule fois, mais qui pourra l'être directement dans le futur si besoin :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
# Bien
def sortpair(data):
    if data[0] >= data[1]:
        return (data[1], data[0])
    return data

sorted = sortpair((12, 7))
VII-A-1-a-vii. Mise en page

L'aspect visuel global d'un code, à savoir sa mise en page, est également important. Un bon layout permet de faire ressortir la logique du code et d'aider la personne qui doit le lire et le comprendre. On va pour cela utiliser les blancs et les lignes vides pour faire ressortir des blocs de code et les parenthèses pour mettre en évidence des parties d'expressions. L'exemple suivant montre un exemple de code compact et pas aéré qui, malgré qu'il fonctionne, n'est pas facile à comprendre :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
# Pas bien
x=12+3*y
if x>=0:print("Hello, it's me")
else:print("Heeeeey macarena!")

# Bien
x = 12 + 3 * y
if x >= 0:
    print("Hello, it's me")
else:
    print("Heeeeey macarena!")

Un autre élément de layout concerne l'ordre dans lequel on écrit certaines parties de code. Ainsi, dans une classe, on définira d'abord le constructeur, puis les accesseurs et mutateurs et enfin les méthodes.

VII-A-1-a-viii. Commentaire

Last but not least, on n'hésitera pas à agrémenter les programmes que l'on écrit de commentaires. On retrouve plusieurs types de commentaires dans un programme :

  • les commentaires qui se contentent de répéter ce que fait le code, le paraphrasant, sont tout à fait à proscrire. On suppose, en effet, que les personnes qui vont lire votre code savent tout aussi bien que vous comprendre le Python ;
  • expliquer ce que fait le code, non pas en le paraphrasant, mais en détaillant son fonctionnement ou sa logique, est une bonne chose à faire. Il s'agit donc d'aider les lecteurs à comprendre la logique du code, par rapport au problème résolu ;
  • enfin, un dernier type de commentaire parfois utilisé, et dont il ne faut pas abuser, permet d'insérer des marqueurs ou des métainformations dans le code. On ajoutera ainsi, par exemple, l'auteur et la version du code, ou des lignes de tirets pour faire ressortir des séparations logiques.

Dans l'exemple suivant, on a clairement mis trop de commentaires qui se contentent, par ailleurs, de décrire en français le code qui est écrit juste en dessous :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
data = [8, 4, -2, 6]
value = 12

found = False
# On parcourt tous les éléments de la liste data
# qui passent dans la variable elem qu'on compare
# avec value pour vérifier si c'est la même ou non
for elem in data:
    if elem == value:
        # Si on a trouvé la valeur cherchée, on le
        # signale via la variable found et on quitte
        # la boucle qui parcourt la liste data
        found = True
        break

print('Found' if found else 'Not found')

Un seul commentaire au début du bloc de code qui recherche la valeur dans la liste aurait été amplement suffisant, bien qu'il ne soit même pas forcément nécessaire. Si on veut vraiment en mettre un, alors on pourrait avoir quelque chose du genre :

 
Sélectionnez
1.
2.
3.
4.
# Cherche si 'value' se trouve dans la liste 'data'
found = False
for elem in data:
    # [...]

Dans la mesure du possible, si on peut se passer de commentaires, on les évitera pour ne pas alourdir inutilement un code source. Comme ledit très bien Steve McConnell : « Good code is its own documentation ». Python étant fait pour être lisible et concis, un code bien écrit en respectant les règles de style et conventions en usage devrait pouvoir être facilement compris. Les seuls commentaires qu'il devrait y avoir dans un code Python doivent donc servir à des explications en lien avec le problème résolu par le programme. Si vous ressentez le besoin de mettre un commentaire pour expliquer un bout de code, posez-vous d'abord la question de savoir si vous n'avez pas écrit un code trop complexe, qui peut être simplifié.

VII-B. Convention de codage

Terminons ce livre en beauté en passant en revue différentes conventions globalement acceptées, qui vous permettront d'écrire du code pythonique. Pour rappel, les deux principaux mots clés souvent associés au Python sont lisibilité et concision.

Suivre des conventions apporte plusieurs avantages aux développeurs. Il est tout d'abord plus facile de rentrer dans le code écrit par un autre développeur que soi. Cela permet également d'obtenir un code plus uniforme pour un projet qui est écrit par plusieurs développeurs. La productivité d'une équipe peut ainsi être accrue, grâce aux gains de temps obtenus par une meilleure lisibilité du code et grâce à la consistance accrue au sein du projet.

VII-B-1. PEP 0008

On a déjà eu l'occasion de découvrir une partie des règles énoncées dans le PEP 0008 au chapitre 2. Voyons maintenant les règles qu'on n'avait pas encore pu voir précédemment, car elles concernaient de la matière abordée entre-temps :

  • un code source Python devrait être enregistré en UTF-8 ;
  • concernant les espaces :

    • il faut utiliser quatre espaces pour un niveau d'indentation, et on évite la tabulation horizontale ;
    • lorsqu'on a une longue liste de valeurs entre parenthèses (paramètres d'une fonction/méthode, condition d'un if/while), on peut les séparer sur plusieurs lignes en alignant le tout verticalement :

       
      Sélectionnez
      def createperson(firstname, lastname, birthdate,
                       sex, address, weight, height):
          if (sex == 'F' or
              sex == 'M'):
              # ...
  • lorsqu'on a une longue liste de valeurs, entre parenthèses, crochets ou accolades, on peut les séparer sur plusieurs lignes en indentant le contenu :

     
    Sélectionnez
    data = [
        1, 2, 3,
        4, 5, 6
    ]
  • lorsqu'on sépare une expression sur plusieurs lignes, on placera les opérateurs en début de chaque nouvelle ligne :

     
    Sélectionnez
    total = (price
             + tax
             - sale)
  • on insère deux lignes vides avant et après une définition de classe ou de fonction, et une ligne vide avant et après une définition de méthode dans une classe ;

  • on ne met pas d'espace à l'intérieur de parenthèses, crochets ou accolades, avant un deux-points, autour d'un deux-points dans un slice, avant la parenthèse ouvrante d'un appel de fonction/méthode, avant un crochet pour accéder à un élément d'une liste ou d'un dictionnaire :

     
    Sélectionnez
    # Pas bien
    def func(name = 'Jon'):
        preferences = { name    : ['Seb'  , 'Alexis'  ] }
        values      = data   [2 :4]
        verdict     = getresult ('Tom')
    
    # Bien
    def func(name='Jon'):
        preferences = {name: ['Seb', 'Alexis']}
        values = data[2:4]
        verdict = getresult('Tom')
  • on ne met qu'une seule espace avant et après l'opérateur d'affectation :

     
    Sélectionnez
    # Pas bien
    data=32
    result     =   7
    
    # Bien
    data = 32
    result = 7
  • une ligne trop longue, notamment si elle dépasse kitxmlcodeinlinelatexdvp79finkitxmlcodeinlinelatexdvp caractères, peut être coupée avec le caractère de continuation (\) :

     
    Sélectionnez
    with open('/Users/johndoe/Desktop/video-ole-ole.mp4'), \
         open('/Users/johndoe/Desktop/marchand-brouette.jpg'):
        # [...]
  • concernant les imports :

    • on écrit une ligne pour chaque module importé avec import, mais on peut les rassembler lorsqu'on utilise from ... import :

       
      Sélectionnez
      # Pas bien
      from math import cos
      from math import sin
      import os, sys
      
      # Bien
      from math import cos, sin
      import os
      import sys
    • on organise les imports en trois blocs : d'abord les imports de la bibliothèque standard, puis ceux de bibliothèques tierces et enfin ceux des bibliothèques développées pour l'application ;

  • il faut éviter, autant que possible, d'importer * ;

  • il n'y a aucune recommandation particulière pour délimiter les chaines de caractères, on peut utiliser librement des guillemets simples ou doubles, sauf pour les triples guillemets où on préfèrera les doubles pour respecter la convention de Docstring ;

  • on n'ajoute pas d'espaces au bout d'une ligne ;

  • on entoure toujours les opérateurs binaires suivants d'espaces : =, +=, -=…, ==, <, >, !=, <=, >=, in, not in, is, is not, and, or et not ;

  • on ne met pas d'espaces autour de l'opérateur = pour les paramètres nommés, lors de l'appel ou pour spécifier une valeur par défaut ;

  • concernant les noms :

    • ne pas utiliser les lettres l (kitxmlcodeinlinelatexdvpLfinkitxmlcodeinlinelatexdvp minuscule), O (kitxmlcodeinlinelatexdvpOfinkitxmlcodeinlinelatexdvp majuscule) et I (kitxmlcodeinlinelatexdvpIfinkitxmlcodeinlinelatexdvp majuscule) dans un nom de variable, fonction, classe ;
    • un module ou un package doit avoir un nom court et rien qu'avec des minuscules ;
    • un nom de classe commence par une majuscule et suit la casse chameau (en casse chameau, appelée camelcase en anglais, chaque mot commence par une majuscule comme dans HotBeverageDistributor, par exemple) ;
    • un nom de fonction/méthode et de variable est en minuscules, en séparant éventuellement les mots avec des tirets de soulignement (_), si cela augmente la lisibilité ;
    • un nom de constante est complètement en majuscules, en séparant éventuellement les mots avec des tirets de soulignement ;
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
from math import sqrt

class ComplexNumber:
    def __init__(self, real, imag):
        self.__real = real
        self.__imag = imag

    def norm(self):
        return sqrt(self.__real ** 2 + self.__imag ** 2)

ZERO = ComplexNumber(0, 0)
VII-B-1-a. Code pythonique

Terminons avec quelques conventions de bon usage, adoptées par la grande majorité des programmeurs Python, qui font en sorte qu'un code est qualifié de pythonique, ou non. Voici, en vrac, une série d'éléments auxquels il convient de faire attention et qu'il faut exploiter pour rendre son code pythonique (pour en savoir plus, vous pouvez consulter les deux sites web suivants : http://docs.python-guide.org/en/latest/writing/style/ et http://web.archive.org/web/20180411011411/http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html) :

  • pour intervertir les valeurs de deux variables a et b, on doit exploiter l'affectation multiple :

     
    Sélectionnez
    # Pas bien
    temp = a
    a = b
    b = temp
    
    # Bien
    a, b = b, a
  • on doit exploiter le déballage lorsqu'on parcourt une structure de données qui contient des tuples ou des listes d'éléments. Par exemple, si on a une liste qui contient des contacts représentés par un prénom, un nom et un âge, on pourra écrire :

     
    Sélectionnez
    data = [['John', 'Doe', 27], ['Tom', 'Doe', 45]]
    for (first, last, age) in data:
        print('{} {} ({} ans)'.format(first, last, age))
  • construire une chaine de caractères à partir de plusieurs valeurs est plus rapide avec la méthode join qu'avec une boucle de concaténations :

     
    Sélectionnez
    data = ['hello', 'world!']
    
    # Pas bien
    result = ''
    for elem in data:
        result += str(elem) + ' '
    print(result[:-1])
    
    # Bien
    print(' '.join(data))
  • utiliser l'opérateur in lorsque c'est possible ;

  • utiliser la méthode get des dictionnaires, qui permet d'obtenir la valeur associée à une clé ou une autre valeur que l'on spécifie si la clé n'est pas dans le dictionnaire :

     
    Sélectionnez
    stock = {}
    
    # Pas bien
    for (item, quantity) in data:
        if item not in stock:
            stock[item] = 0
        stock[item] += quantity
    
    # Bien
    for (item, quantity) in data:
        stock[item] = stock.get(item, 0) + quantity
  • une séquence vide valant False dans une condition, on ne doit pas utiliser len pour tester qu'elle est non vide :

     
    Sélectionnez
    # Pas bien
    if len(data) != 0:
        print(data[0])
    
    # Bien
    if data:
        print(data[0])
  • exploiter au maximum la définition de liste par compréhension pour remplacer une construction faite par une boucle dans laquelle on utilise une condition. Par exemple, pour filtrer une liste et n'en garder que les valeurs paires, on peut faire :
 
Sélectionnez
data = [8, -1, 15, 7, 9, 12]

# Pas bien
even = []
for elem in data:
    if elem % 2 == 0:
        even.append(elem)

# Bien
even = [x for x in data if x % 2 == 0]

Enfin, si vous avez oublié la philosophie qui est derrière Python, si vous avez l'impression d'être sur le mauvais chemin et de ne plus être assez pythonique, lancez Python en mode interactif et importez le module this pour quelques rappels :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
>>> 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!

précédentsommaire

Copyright © 2019 Sébastien Combéfis. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.