G. Lghd
web developper
Emmanuelle R. et Gaëtan L.
21/01/2020
"La perfection est atteinte, non pas lorsqu'il n'y a plus rien à ajouter, mais lorsqu'il n'y a plus rien à retirer.“
Saint Exupery
l'idée de "sport collectif", de discipline :
Pourquoi ce sujet ?
La qualité est là pour établir une règle du jeu :
D'un côté,
il est important d'apprendre à respecter la règle du jeu,
càd des normes de développement
dans les méthodes, au fond, dans la forme.
D'un autre côté,
Python, par son mode communautaire, entretient un rapport spécifique à la qualité : le pythonique.
La simplicité est un principe à la base de nombreuses règles de qualité en général, et en particulier avec Python.
Cf. "Less is more" et van der Rohe : il fut directeur du Bauhaus
or en informatique aussi on parle de design, d'architecture…
Mais la simplicité n'est pas un garant suffisant
a=[0]*3
#nom de variable ésotérique
#objet délicat à manipuler
#proposition 1
a = 4
z = 5
def az(a): #MOCHE ET BUGUÉ :
a+=z
az(z)
print(a)
4
#proposition 2
nombre = 4
augm = 5
def incrementer_valeur(valeur): #MIEUX :
"""modifies the parameter $valeur and returns it"""
return valeur + augm
nombre = incrementer_valeur(nombre)
print(nombre)
9
Les finalités de la qualité d'un programme :
Ces finalités mettent en relation des lignes de code avec un contexte : un client, un utilisateur, d'autres programmeurs.
Le programmeur semble seul face à son code - et il en tire parfois une certaine jouissance. En fait, ce qu'il produit est un objet destiné à entrer dans un réseau de relations, et il doit en tenir compte ! dans une perspective temporelle : la dette technique doit être minimisée
Côté code, la qualité est un impératif qui pèse dans le succès - et le coût potentiel - d'une application.
« Pythonique » ?
Code Python bien conçu, un code idiomatique (en accord avec les règles d’usage du langage, et donc compréhensible par tout développeur).
PEP20 : le Zen de Python
20 règles Ecrite par Tim Peters. Celle-ci énonce les règles suivant un poème. On peut la retrouver via l’instruction import this dans un interpréteur Python.
class User:
def __init__(self, name):
self.name = name
class SecureUser(User):
def __init__(self, name, password):
super().__init__(name)
self.password = password
L’explicite est préférable à l’implicite.
Lire un code Python sans se demander sans cesse ce que fait telle ou telle ligne. Utiliser des noms et des constructions explicites.
Par exemple, en programmation objet, lors d’un héritage et de la surcharge de la méthode d’initialisation (__init__), il convient d’appeler explicitement la méthode de la classe parente. Cela ne sera jamais fait automatiquement dans le dos du développeur, afin d’avoir la main sur le comportement voulu.
Le plat est préférable à l’imbriqué.
Quand on lit un code, il est facile de perdre le fil et d’oublier à quel endroit on se trouve si de nombreux niveaux d’imbrications se succèdent.
Préférer donc d'éviter les imbrications inutile (code plat).
==> retourner directement quand des préconditions ne sont pas validées, plutôt que de placer le contenu de notre fonction dans plusieurs sous-niveaux de conditions.
def print_items(obj):
if not hasattr(obj, 'items'):
return
for item in obj.items:
print(item)
Keep it simple, stupid (KISS)
Il est inutile de créer de nouvelles classes trop vite.
Par exemple, pour un objet qui ne contiendrait que des données, associées à aucune méthode, un dictionnaire suffit:
user = {'username': 'Giles', 'realname': 'Giles Dupont', 'password': '12345'}
= Regrouper dans des fonctions, une action en une seule.
Chaque fonctions doit avoir une action a la fois, et c’est l'enchaînement des fonctions qui produit la fonctionnalité finale.
Tu n’en auras pas besoin
Les mécanismes du langage:
a = 5
b = 2
a, b = b, a
print(a, b)
2 5
Technique qui permet l’assignation de plusieurs variables en une seule instruction.
Ce qui se passe en interne lors de la 3ème ligne est la création d’un tuple (b, a), qui est ensuite déconstruit et son contenu stocké dans les variables a et b.
Toute valeur en Python peut s’évaluer sous forme d’un booléen, sans conversion.
if ma_liste:
print("non vide")
La bibliothèque standard
Les fonctions Built-in:
Voir la liste exhaustive sur : Built-in Functions — Python 3.8.1 documentation
ex: min(), max(), sum(), sorted(), etc.
print('{} + {} = {}'.format(2, 3, 2 + 3))
.format():
.zip()
x = [1, 2, 3]
y = [4, 5, 6]
zipped = zip(x, y)
Un exemple:
with open(pythonique, 'r') as f:
for i in enumerate(f):
longueur = abc.split(',')
for i in range (1, nb):
...
# et non: for i in range (nb-1):
for i in mylist:
# et non : for i in len(mylist) /
# + increment
- différents paradigmes de programmation
- l'agilité
- les tests
- un sens des responsabilités en Python
Différents paradigmes de programmation ont un impact
sur la qualité du code :
POO : tout le monde est d'accord sur les méthodes
et sur les propriétés des objets : en Python, tout est objet
On l'a vu en introduction, parallèle possible avec le design, qui implique aussi un collectif.
=> en programmation : les design patterns
Programmation fonctionnelle : tout le monde est d'accord sur les variables, standardisation de la forme des fonctions
Dans un cadre professionnel, des méthodes renforcent aussi la qualité des spécifications, des livrables : l'agilité par exemple.
Pas d'assez d'expérience en ce domaine pour l'évoquer plus, et cela nous écarterait du sujet de l'approfondir alors que nous l'aborderons prochainement. Rappelons justes deux des maximes "professionnelles" de la PEP 20 : "Maintenant est préférable à jamais.", "Bien que jamais soit souvent préférable à tout de suite."
Eviter l'urgence ne signifie pas procrastiner => maintenant > jamais > tout de suite
L'encadrement formel de la production de code par
les tests
les tests unitaires (qui s'appliquent à une partie précise d'une application)
le TDD par ex. : test driven development
le développement guidé par les tests :
le fait que l'écriture du code soit soumise aux contraintes du test produit un code qui répond strictement aux besoins, qui est plus concis, qui évite les régressions…
Là aussi, temps et expérience manquent encore
pour en parler plus et mieux
mais les tests sont intrinsèquement
partie prenante de la qualité
Un sens des responsabilités en Python
Autres principes qui participent du pythonique dans le sens d'une morale de la responsabilité. Postulat : j'assume donc je fais de la qualité.
We’re all consenting adults here - Nous sommes ici entre adultes consentants
Exemple : on accède à un attribut préfixé par _ (donc signalé comme protégé) en connaissance de cause : rien ne nous en empêche.
NB : l'accès direct aux attributs est préférable en Python aux méthodes getter/setter.
Easier to ask forgiveness than permission (EAFP) - Il est plus facile de demander pardon que la permission.
Tenter une instruction et gérer les erreurs au fur et à mesure, plutôt que de vérifier à l'avance une condition susceptible d'avoir évolué le moment venu (ex. : lecture de fichier).
- import this ?
- exploiter les ressources propres au Python
- recourir aux modules
- les fonctions dans le fond
- du côté des variables
- conventions de nommage des variables dans le fond
Il devrait y avoir une – et de préférence une seule – manière évidente de le faire.
Si l’implémentation est difficile à expliquer, c’est une mauvaise idée.
Si l’implémentation est facile à expliquer, il peut s’agir d’une bonne idée.
Les espaces de noms sont une sacrée bonne idée – utilisons-les plus souvent !
import this ?
Que dit Tim Peters dans le Zen de Python concernant le fond ?
Exploiter les ressources propres au Python
Une illustration : l'exemple à la fin de la partie Pythonique
Connaissance de la bibliothèque standard, et toujours de ses fonctions
La fonction help() pour avoir plus d'information sur un objet
sur une fonction, un type, un module
et la fonction dir() pour lister les méthodes d'un objet
Des mécanismes propres au Python :
Déjà vu : unpacking (a, b = b, a), conditions ([] == False), gestionnaire de contexte with
Mais aussi : listes de compréhension [expr for x in foo], décorateurs @deco; def foo():
=> à voir, les décorateurs de la bib. standard : staticmethod, classmethod, property
Recourir aux modules
Extraits de l'article Les secrets d'un code pythonique
les illustrations en moins, mais lien à la fin :-)
"Le module collections comporte d’autres structures de données essentielles au langage : OrderedDict, namedtuple, Counter, ou encore defaultdict qui sera préférable à une utilisation systématique de setdefault. Des développeurs débutants auront le réflexe de recréer ces classes, alors qu’elles sont à portée de main."
"Viennent ensuite les autres modules, tels que itertools, functools ou operator. Ces modules regroupent divers utilitaires sympathiques, qui simplifient grandement le code. En faire bon usage permet de se conformer aux standards du langage."
"Enfin, suivant le domaine d’application du projet, entrent en compte les modules dédiés : re, math, random, urllib, datetime, struct, etc., et leurs propres bonnes pratiques, souvent détaillées dans la documentation."
Les fonctions dans le fond
Encore un principe : DRY
Pour éviter la redondance : les fonctions (et au-delà : classes et POO)
des fonctionnalités factorisées dans code mutualisé - parfois l'on peut en faire un module.
Vie plus facile : paramètres qui ont des valeurs par défaut.
Return : il vaut mieux prévoir aussi une valeur explicite retournée :
def filtrer_pair_qualite_inf(liste):
"""modifie la liste $liste passée en argument et retourne None"""
liste[:] = [x for x in liste if x % 2 == 0]
liste_originale = list(range(66))
execution = filtrer_pair_qualite_inf(liste_originale)
print(liste_originale) # liste modifiée
print(execution) # None
def filtrer_pair_qualite_sup(liste):
"""retourne une copie modifiée de la liste $liste passée en argument"""
return [x for x in liste if x % 2 == 0]
liste_originale = list(range(66))
execution = filtrer_pair_qualite_sup(liste_originale)
print(liste_originale) # liste originale
print(execution) # liste modifiée
Ne pas hésiter à adopter les principes de la programmation fonctionnelle (cf. partie 3).
Du côté des variables
commencer par éviter la confusion entre les noms des variables et des paramètres
Une autre manière (que la programmation fonctionnelle)
de préserver la valeur des variables : l'usage des constantes
(cf. fin de la partie 5 : "moyens dans la forme")
Optimisation en mémoire des collections :
éviter les éléments inutiles dans des objets itérables tels que les listes
employer des générateurs quand les éléments auront à être disponibles un par un
Optimisation algorithmique des collections :
les clés de dict liés à une interface correspondent aux identifiants des éléments du DOM
Nomenclature optimisée : les espaces de nom (pour les attributs et méthodes)
Conventions de nommage des variables dans le fond :
incrémentation : i et j
éléments des boucles : x, y, z
fichier dans un bloc with: f
variable inutilisée : _
est également alias de gettext, et a un sens dans le Shell
arguments hétérogènes : *args, **kwargs
for i, stuff in enumerate(foo):
for x in foo:
with open(stuff) as f:
(random.randint(10) for _ in range(10))
def foo(a, b, **kwarg):
instance en cours : self ; et classe en cours : cls
Exemples extraits de Le PEP8 et au delà, par la pratique
Voir la partie 5 pour les conventions de nommage sur la forme
La PEP8:
Style Guide for Python Code: une des plus anciennes
Le site pep8online permet de vérifier si son code respecte ce guide.
L'indentation, tabulations ou espaces
if True:
print("True")
print("False")
Longueur maximum d'une ligne
Un_long_calcul = variable + \
taux * 100
Traditionnellement les terminaux des années 80 etaient limités à 80 colonnes et 24 lignes. Standard VT100:
1 ligne == 20 tabulations == 80 espaces
Parenthèse de culture générale :
Directives d'importation
import os
import math
# et non
import os, math
from random import randomint
# et surtout pas:
from random import *
sauf :
Dans l'ordre :
les directives d'importation faisant référence à la bibliothèque standard ;
les directives d'importation faisant référence à des bibliothèques tierces ;
les directives d'importation faisant référence à des modules de votre projet.
Le signe espace dans les expressions et instructions
pas d'espace :
# Oui
spam(salade[1], {oeufs: 2})
# Non
spam( salade[ 1 ], { oeuf: 2 } )
# Oui
spam(1)
# Non
spam (1)
# Oui
if x == 4: print x, y; x, y = y, x
# Non
if x == 4 : print x , y ; x , y = y , x
def fonction(parametre=5):
i = i + 1
# NON
def fonction (parametre = 5):
i=i+1
La PEP 257 : de belles documentations
https://www.python.org/dev/peps/pep-0257/
Cette chaîne de caractères devient l'attribut spécial__doc__de l'objet:
fonction.__doc__
'Documentation de la fonction.'
def fonction():
"""Documentation brève sur une ligne.
Documentation plus longue...
"""
En bref:
Le BDFL ( Benevolent Dictator For Life) conseille de sauter une ligne avant de fermer les docstrings, si sur plusieurs lignes.
Conventions de nommage
Astuce: Depuis python 3.8.1, Il existe un moyen de faire des Constantes avec python:
from typing import Final
MA_CONSTANTE: Final = 1
https://docs.python.org/3/library/typing.html
By G. Lghd
sujet de veille pour la formation CDA Python à Simplon : fork