Cette page tente de lister diverses "Bonne pratiques" ("Best Pratice" en anglais) afin de faire les choses mieux, pour que vos programmes fonctionnent mieux, plus vite et soient plus portables.
Sommaire
- Boucle for et itérateurs
- Quelques règles de bonne conduite
- Ce qu'on en dit ailleurs
- Mailing list
Importer un module ne faisant pas partie de la distribution standard de Python
Si votre programme utilise un module ne faisant pas partie de la distribution standard de Python, il peut être difficile pour un utilisateur de deviner de quel module il s'agit et où le télécharger. En ajoutant un petit try/except au début de votre programme, vous ferez gagner du temps à vos utilisateurs, surtout si vous indiquez l'URL où aller chercher le module.
Exemple:
Concaténer des chaînes
Si vous avez un grand nombre de chaîne à concaténer, n'utilisez pas l'opération +.
Ne faites pas:
Ajoutez plutôt les éléments à une liste, puis fusionnez les éléments de la liste.
Cette seconde solution est des centaines de fois plus rapide que la précédente.
Est-ce encore d'actualité ? Cf. mes tests sur http://www.biologeek.com/journal/index.php/2006/01/21/85-optimisation-des-chaines-de-caracteres-en-python qui sont en contradiction avec cette astuce d'optimisation... commentaires bienvenus.
Commentaire de sebsauvage (2006-01-25): Effectivement, je constate qu'avec Python 2.4.1, cette optimisation n'est plus d'actualité.
Concaténer des chemins et des noms de fichiers
Si vous avez des chemins des noms de fichiers à concaténer, ne faites pas:
C'est une très mauvaise façon de faire. En effet vous ne savez pas sur quelle plateforme va fonctionner votre programme.
Sous Windows, le séparateur de chemin est "\", sous Unix c'est "/" et sur Macintosh c'est ":".
Pour être sûr que votre programme fonction partout, utilisez os.path.join:
Pour connaître le séparateur : utilisez os.path.sep.
De même, pensez à utiliser les autres méthodes de os.path plutôt que de tenter de manipuler les chaînes vous-même.
Des objets lisibles
Quand on doit déboguer, il est parfois difficile de s'y retrouver, et d'avoir une vue claire sur l'état des objets. Voici une pratique que j'utilise presque systématiquement: créer une méthode repr dans tous mes objets.
La méthode repr permet de fournir une représentation textuelle d'un objet. C'est elle qui est appellée quand il faut convertir un objet en texte (par exemple lors d'un print).
Exemple 1
Définissons une classe client. Chaque client possède simplement un numéro et un nom.
Si maintenant on créé un client et qu'on l'affiche:
On obtient:
<__main__.client instance at 0x007D0E40>
Ce qui n'est pas très parlant.
Changeons notre classe client et ajoutons la méthode repr (notez les deux underscore avant et après le nom repr):
Maintenant, si on affiche un client:
On obtient:
<client id="5" nom="Dupont">
Ce qui est nettement plus parlant.
Exemple 2
On peut même appliquer cela à des classes composées. Si on créé un carnet (contenant des clients):
1 class carnet:
2 def __init__(self):
3 self.clients = []
4
5 def ajouteClient(self, client):
6 self.clients.append(client)
7
8 def __repr__(self):
9 lignes = []
10 lignes.append("<carnet>")
11 for client in self.clients:
12 lignes.append(" "+repr(client))
13 lignes.append("</carnet>")
14 return "\n".join(lignes)
Vous noterez qu'on a créé aussi une méthode repr qui renvoie le carnet lui-même, mais aussi chacun des clients qu'il contient.
On peut alors créer un carnet, et afficher de façon très lisible ce qu'il contient:
ce qui donne:
<carnet> <client id="5" nom="Dupont"> <client id="12" nom="Durand"> </carnet>
Ce genre de petite astuce - qui n'est pas exclusif à Python - pourra vous êtres d'un grand secours lorsque vous voudrez déboguer vos programmes.
Vous pouvez également utiliser cela pour afficher l'état des objets dans la clause except d'un try.
La méthode read()
La méthode read s'applique sur un tas d'objets: des fichiers, des sockets...
On l'utilise souvent sans paramètre, tel que:
Mais imaginons que le fichier fasse 40 Giga-octets, ou que le site web vous envoie des données sans jamais s'arrêter: Votre programme va ralentir les autres programmes, planter et peut-être mettre en péril la stabilité du système parcequ'il aura consommé une énorme quantité de mémoire.
Il faut limiter la quantité de données lues. Par exemple, je ne m'attend pas à trouver des pages HTML supérieures à 200 ko. On peut donc limiter le read à 200000. De même, les fichiers que je vais manipuler ne dépasseront pas 10 Mo. Je limite donc à 10 Mo.
Cela permet (en cas de problème) d'éviter à votre programme de partir en vrille
Utiliser des chaînes avec des accents
L'utilisation d'accents dans les chaînes de caractères provoque souvent des déconvenues. Entre autre des erreurs "UnicodeEncodeError: 'ascii' codec can't encode." ou autres joyeusetées. Pour éviter cela, indiquez quel est l'encodage utilisé dans votre fichier source, et utilisez des chaînes unicode. Pour plus de détails voir JouerAvecUnicode.
Exécuter une requête SQL
Faille potentielle
La méthode la plus "naturelle" de lancer une requête introduira une faille de sécurité dans votre programme si les paramètres de la requête viennent de l'extérieur (script CGI par exemple).
Ainsi, vous ne devez par faire :
mais ceci :
Explications
Que se passe-t-il si un utilisateur mal intentionné donne comme à la variable id la valeur "'; drop table tbl; select '" dans la requête "SELECT * FROM tbl WHERE id=%s" ?
Si vous avez utilisé la mauvaise méthode, la requête exécutée provient d'une simple concaténation de chaine qui produit le résultat suivant. La requête obtenue aura des conséquences catastrophiques, puisqu'une table sera détruite.
SELECT * FROM tbl WHERE id=''; drop table tbl; select ''
Si vous avez utilisez la bonne méthode, les caractères spéciaux de la variable id sont échappés par l'appel de execute. La requête obtenue n'est plus dangereuse et ne retournera probablement aucun enregistrement.
SELECT * FROM tbl WHERE id='\'; drop table tbl; select \''
Les caractères spéciaux sont préfixés par des caractères d'échappements par la méthode execute, évitant tout risque de pollution de votre requête par des données de l'extérieur.
Cette attaque est connue sous le nom de "SQL injection"
De plus, il est possible que le module de base de données tente d'optimiser la vitesse d'exécution en mettant en cache les "compilations" des requêtes précédentes. En introduisant des variables dans la requêtes, on empêche ce mécanisme d'agir, car toutes les requêtes sont différentes au yeux du cache. Avec la bonne méthode, les requêtes sont constantes, et on gagne en temps d'exécution si on ré-exécute la même requete avec des paramètres différents.
Autres cas
- Plusieurs paramètres :
- Utilisation d'un dictionnaire
Eliminer un suffixe dans une chaîne
Il y a plusieurs solutions pour éliminer un suffixe dans une chaîne de caractères. On ne peut pas utiliser rstrip qui est prévu pour éliminer un ensemble de caractères suffixes. Exemple :
'calendrier<br>'.rstrip('<br>')
donne 'calendrie' alors qu'on aurait souhaité 'calendrier' On peut donc utiliser chacune des quatre solutions (non limitatif) suivantes:
1 def rstringStrip1(s1,s2):
2 r= s1
3 if s1.endswith(s2):
4 r= s[:-len(s2)]
5 return r
6 def rstringStrip2(s1,s2):
7 return s1[:len(s1)-len(s2)*(s1[-len(s2):]==s2)]
8 def rstringStrip3(s1,s2):
9 return s1[:-len(s2)]+s1[-len(s2):].replace(s2,'')
10 def rstringStrip4(s1,s2):
11 import re
12 return re.compile(s2+'$',re.MULTILINE).sub('',s1)
13 #--
14 s= 'calendrier<br>'
15 print rstringStrip1(s,'<br>')
16 print rstringStrip2(s,'<br>')
17 print rstringStrip3(s,'<br>')
18 print rstringStrip4(s,'<br>')
Boucle for et itérateurs
Quand on vient d'autres langages, on est tenté d'utiliser les structures de ces autres langages. Par exemple, pour itérer sur une liste, on serait tenté de faire:
ou bien
Ce qui est une mauvaise idée. Vous devriez plutôt faire:
Le résultat est le même, mais:
- Vous avez économisé une variable (i)
- Le code est plus compact.
Le code est plus lisible.
De même, pour lire les lignes d'un fichier texte, ne faites pas:
mais plutôt:
C'est plus "Pythonique".
Quelques règles de bonne conduite
Le langage Python a été conçu pour que le rédacteur (souvent programmeur ou développeur, mais pas nécessairement) puisse exprimer clairement ce qu'il pense.
Comme dans tout autre langage, on peut aussi s'exprimer très obscurément. Si tel est votre but ...
il faut bien rédiger
Les bonnes habitudes sont énoncées dans la PEP 8 (Python Enhancement Proposal, propositions d'améliorations en Python). Il faut absolument les respecter. Les avantages ne deviennent évidents qu'au bout de quelques semaines.
Comme autres PEP, je mentionne les numéros 20, 257 et 263.
il faut contrôler
J'utilise pylint. C'est le contrôleur avec lequel je me suis bien entendu; en outre il me donne de très bonnes notes.
pychecker est un autre contrôleur qui va jusqu'à descendre dans les radicules de numpy voir si tous les modules sont bien là, même si on n'en a pas besoin.
il faut tester
Sans commentaire.
- il faut avoir de bonnes fréquentations
Par exemple le site de Laurent Pointal: http://www.limsi.fr/Individu/pointal/python-works.html avec une liste de liens http://www.limsi.fr/Individu/pointal/python.html
il faut sauvegarder
Sans commentaire.
Ce qu'on en dit ailleurs
http://sis36.berkeley.edu/projects/streek/agile/bad-smells-in-code.html
Mailing list
Une mailing-list francophone est gérée sur le site de http://www.aful.org