diff options
Diffstat (limited to 'content')
| -rw-r--r-- | content/index.rst | 498 |
1 files changed, 0 insertions, 498 deletions
diff --git a/content/index.rst b/content/index.rst deleted file mode 100644 index e2d6e65..0000000 --- a/content/index.rst +++ /dev/null @@ -1,498 +0,0 @@ -Bonnes pratiques en Python --------------------------- - -.. sidebar:: Sommaire - - .. contents:: :local: - -Ce document est une petite compilation de divers éléments relatifs à Python : - -* les erreurs ou maladresses que je rencontre le plus souvent quand je lis du - code Python (y compris et surtout le code écrit par moi-même) et les façons - d'y remédier. - -* certains aspects du langage sur lesquels Python diffère sensiblement d'autres - langages qui permettent d'en utiliser toute la puissance. - -* des conseils généraux sur les bonnes pratiques en Python. - - -List comprehensions -=================== - -Les *list comprehensions* offrent une syntaxe très concise pour construire une -liste depuis une autre liste (ou depuis un autre objet itérable). La syntaxe -est la suivante : - -.. code:: python - - result_list = [expression(item) for item in original_list if condition(item)] - -et signifie ``result_list`` contient l'ensemble des éléments -``expression(item)`` où ``item`` est un élément de ``original_list`` pour -lequel ``condition(item)`` est vérifié. La condition qui permet de filtrer les -éléments est optionnelle. - -Exemple, pour calculer les carrés des éléments d'une liste. Au lieu de : - -.. code:: python - - l = [1, 2, 3] - result = [] - for i in l: - result.append(i*i) - -qui est particulièrement inefficace à cause de l'utilisation répétée de la -fonction ``append``, on peut utiliser une *list comprehension* : -On fait : - -.. code:: python - - l = [1, 2, 3] - result = [i*i for i in l] # result = [1, 4, 9] - -Pour calculer les racines carrées des éléments positifs (sinon ça fait boum) -d'une liste : - -.. code:: python - - from math import sqrt - - l = [4, -3, 9] - result = [sqrt(i) for i in l if i >= 0] # result = [2, 3] - -La même syntaxe existe également pour les dictionnaires, on parle alors de -*dict comprehensions* (original, n'est-ce pas ?). Par exemple pour transformer -une liste de couples nom/téléphone en un dictionnaire (pour une recherche plus -rapide) : - -.. code:: python - - l = [("Barthes", "0629649212"), ("Babar", "0614798553")] - d = { name : phone for (name, phone) in l } - -Voir `la page -<http://docs.python.org/tutorial/datastructures.html#list-comprehensions>`__ sur -les *list comprehensions* dans la documentation officielle de python. - -Les multiples facettes de ``in`` -================================ - -Le mot-clé ``in`` a beaucoup d'usages différents et rend le code tellement -facile à écrire que les gens oublient parfois de l'utiliser. - -* ``in`` offre une syntaxe universelle pour itérer sur tous les objets - itérables du langage python. On évitera les fioritures inutiles. Par exemple, - pour itérer sur une liste, au lieu de : - - .. code:: python - - l = [ 1, 2, 3] - for i in range(len(l)): - print l[i] - - on fait simplement : - - .. code:: python - - l = [1, 2, 3] - for i in l: - print l[i] - - de même pour itérer sur un dictionnaire, au lieu de : - - .. code:: python - - d = {...} - for key in d.keys(): - print d[key] - - on peut se contenter de : - - .. code:: python - - d = {...} - for key in d: - print d[key] - -* ``in`` permet également de faire des tests d'appartenance dans de nombreuses - situations : appartenance d'un élément à une liste, un dictionnaire (ou - n'importe quel objet itérable), occurence d'une sous-chaîne dans une chaîne - de caractères. Par exemple : - - .. code:: python - - l = [line for line in open("server.log") if "Connected" in line] - - va retourner la liste des lignes du fichier ``server.log`` contenant la - sous-chaîne ``Connected``. - -Manipuler les listes en une seule instruction -============================================= - -Plus généralement, il faut essayer au maximum de ne pas écrire de boucle -``for`` pour itérer sur les éléments d'une liste. On peut presque toujours s'en -sortir sans, c'est plus concis et surtout Python peut optimiser en interne car -il comprend qu'on essaie de faire une seule opération sur la liste entière. - -Les *list comprehensions* permettent souvent de remplacer une itération par une -seule instruction. Je rappelle ici quelques fonctions utiles qui aident à se -débarrasser de boucles ``for`` inutiles : - -* ``join`` pour formater une liste. Exemple, pour imprimer la liste des mots - commençants par ``a`` dans une liste de mots. Au lieu de : - - .. code:: python - - result = "" - l = [...] - for word in l: - if word[0] == 'a': - result += word + " " - print result - - On fait : - - .. code:: python - - l = [...] - print " ".join([word for word in l if word[0] == 'a' ]) - -* ``sum``. Exemple, pour sommer les éléments positifs d'une liste : - - .. code:: python - - l = [2, -1, 3] - total = sum([i for in in l if i > 0]) - -* ``map`` pour appliquer la même fonction à tous les éléments d'une liste. - Exemple, pour inverser (au sens des palindromes) tous les mots d'une liste - : - - .. code:: python - - l = [ "roustine", "alicia" ] - - def reverse(word): - return word[::-1] - - m = map(reverse, l) # m = ['enitsuor', 'aicila'] - - -Les *slices* sont également très utiles pour manipuler les listes en blocs. -Rappel : si ``l`` est une liste, ``l[begin:end:step]`` extrait les éléments de -``l`` depuis l'indice ``begin`` inclus à l'indice ``end`` exclu par pas de -``step``. Le ``:step`` est optionnel. - -Si on laisse le paramètre ``begin`` vide, il prend la valeur 0 par défaut. De -même le paramètre ``end`` prend la valeur ``len(l)`` par défaut. Une valeur -négative pour ``begin`` ou ``end`` est décomptée depuis la fin de la liste. Par -exemple, pour extraire tous les éléments d'une liste, sauf le dernier : - -.. code:: python - - l = [1 , 2, 3] - m = l[:-1] # m = [1, 2] - -Utiliser une valeur négative pour le paramètre ``step`` peut être utile pour -parcourir un itérable en sens inverse comme dans l'exemple donné plus haut pour -renverser un mot : - -.. code:: python - - word = "castor" - drow = word[::-1] # drow = "rotsac" - -qui pallie au manque cruel d'une fonction ``reverse`` pour les strings. - -Exceptions -========== - -Les exceptions constituent un outil très puissant (souvent sous-utilisé) des -langages hauts niveaux. Elles permettent un style de programmation moins -défensif en traitant les erreurs au moment où elles surviennent plutôt qu'en -faisant des tests pour éviter de générer l'erreur. - -En Python, dès que l'on essaie de faire une opération illicite (ex: accéder -à un élément en dehors d'une liste, diviser par zéro, etc.) au lieu de -simplement faire planter le programme, Python lève une exception que l'on peut -capturer, ce qui laisse une dernière chance de se rattraper avant que le -programme plante définitivement. - -La syntaxe pour détecter les exceptions est la suivante : - -.. code:: python - - try: - ... # bout de code pouvant éventuellement lever l'exception KaBoum - except Kaboum: - ... # bout de code à effectuer si le bout de code précédant a levé l'exception KaBoum - -Par exemple, si une ligne de code contient une division par un nombre -potentiellement (mais rarement) égal à zéro, au lieu de faire systématiquement -un test pour vérifier la non-nullité du nombre, il est beaucoup plus efficace -d'inclure la ligne dans un ``try: ... except ZeroDivisionError:`` pour gérer -spécifiquement les rares cas où le nombre est nul. C'est le principe bien connu -de *mieux vaut demander l'absolution que la permission*. - -Autre exemple, quand on essaie d'accéder à un élément d'un dictionnaire qui -n'existe pas, Python lève une exception ``KeyError``. On peut utiliser cette -exception pour initialiser la valeur associée à une clé non encore existante du -dictionnaire. Par exemple, pour créer un dictionnaire des occurrences des mots -dans un texte, on voit souvent : - -.. code:: python - - text = "..." - result = {} - for word in text.split(): - if word in result: - result[word] += 1 - else: - result[word] = 1 - - -On peut utiliser avantageusement l'exception ``KeyError`` pour éviter le test -``if`` systématique : - -.. code:: python - - text = "..." - result = {} - for word in text.split(): - try: - result[word] += 1 - except KeyError: - result[word] = 1 - -Voir `la page -<http://docs.python.org/tutorial/errors.html#handling-exceptions>`__ sur les -exceptions dans la documentation officielle. - -Valeurs équivalentes à ``True`` ou ``False`` -============================================ - -Si ``test`` est une variable booléenne (égale à ``True`` ou ``False``), on sait -qu'il est redondant d'écrire : - -.. code:: python - - if test == True: - ... - -alors qu'il suffit d'écrire: - -.. code:: python - - if test: - ... - -Plus généralement, Python a des règles de conversion automatique de certains -types vers les booléens pour permettre une syntaxe plus légère (et parfois un -gain en performances) pour les tests conditionnels : - -* comme dans la plupart des langages, un entier strictement positif est - converti en ``True`` et zéro est converti en ``False`` -* une chaîne de caractères est convertie en ``False`` si et seulement si elle - est vide. Ainsi, pour tester si une chaîne de caractères ``title`` est non - vide, on peut simplement faire : - - .. code:: python - - if title: - ... - - au lieu de : - - .. code:: python - - if len(title) > 0: - ... - -* la valeur ``None`` (qui est une constante utilisée quand on ne sait pas - quelle valeur donner à une variable) est convertie en ``False``. Pour tester - si une variable ``var`` n'est pas égale à ``None`` on peut donc simplement - faire : - - .. code:: python - - if not var: - ... - - **Attention** toutefois, ceci n'est correct que si ``var`` ne peut pas prendre de - valeurs converties en ``False`` par Python. Par exemple, si ``var`` peut être - ``None`` ou une chaîne de caractères, le test ``if not var:`` ne permet pas - de distinguer le cas où ``var`` est ``None`` du cas où ``var`` est une chaîne - vide. - -Générateurs -=========== - -Les générateurs permettent de créer facilement des itérateurs (des objets -itérables). Il en existe deux parfums différents : - -* les *generator expressions* : celles-ci sont exactement - identiques aux *list comprehensions* à ceci près qu'on remplace les crochets - par des parenthèses. Ainsi le code suivant : - - .. code:: python - - l = [1, 2, 3] - m = (i*i for i in l) - print '\n'.join(m) - - produit exactement le même résultat que si la deuxième ligne avait été - remplacée par : - - .. code:: python - - m = [i*i for i in l] - - La différence est que dans le cas d'une *list comprehension* la liste ``m`` - est construite intégralement (et placée en mémoire) au moment de la - définition de la variable ``m``. Dans le cas où ``m`` est définie par une - *generator expression*, les éléments de ``m`` sont générés de façon - paresseuse : à chaque itération de la boucle ``for``, Python revient à la - définition de ``m`` et génère un nouvel élément. - - En termes de performance, les deux solutions sont équivalentes (au final - chaque élément de ``m`` est généré une et une seule fois). Les *generator - expressions* sont par contre beaucoup plus avantageuses au niveau de - l'occupation de la mémoire : puisque les éléments sont générés dynamiquement, - jamais plus d'un élément n'est stocké en mémoire au même moment. Lorsque la - liste est trop grosse pour tenir dans la mémoire, ou lorsqu'elle n'est pas - appelée à être utilisée ultérieurement dans le code, alors il est préférable - d'utiliser une *generator expression*. - - Lorsque l'on utilise une *generator expression* comme argument d'une - fonction, Python autorise à ne garder qu'une seule paire de parenthèses. - Exemple : - - .. code:: python - - l = [1, 2, 3] - total = sum((i*i for i in l)) - total2 = sum(i*i for i in l) - equal = (total == total2) # equal est égal à True - -* une deuxième façon de définir un générateur est d'écrire une fonction - utilisant le mot-clé ``yield``. Prenons l'exemple de la fonction suivante : - - .. code:: python - - def file_minmax_generator(filename): - for line in open(filename): - l = map(int, line.split()) - yield min(l), max(l) - - cette fonction lit un fichier ligne à ligne. Chaque ligne contient une liste - de nombres dont on extrait le minimum et le maximum. La signification du - mot-clé ``yield`` est la suivante : *retourner la valeur indiquée et arrêter - l'exécution de la fonction (rendre la main) en attendant d'être appelé à - nouveau*. On peut alors utiliser cette fonction dans une boucle ``for`` de la - façon suivante : - - .. code:: python - - for (inf, sup) in file_minmax_generator(filename): - print (inf + sup)/2. - - à chaque itération de la boucle ``for`` la fonction ``file_minmax_generator`` - est exécutée jusqu'à rencontrer le mot-clé ``yield`` et la valeur retournée - est utilisée dans le corps de la boucle jusqu'à la prochaine itération. - -* enfin, certaines fonctions prédéfinies par Python retournent des générateurs. - C'est le cas par exemple de la fonction ``xrange`` qui accepte exactement les - mêmes arguments que la fonction ``range`` (pour générer une liste de - nombres). Ainsi le code suivant : - - .. code:: python - - for i in xrange(10000): - ... - - est équivalent au même code utilisant la fonction ``range`` mais a l'avantage - de ne pas stocker en mémoire une liste de 10000 entiers (ceux-ci sont générés - dynamiquement). Il est presque toujours préférable d'utiliser la fonction - ``xrange`` plutôt que la fonction ``range``, à tel point que dans la version - 3.x de Python, ``range`` se comporte comme ``xrange``. - -Voir `la page -<http://docs.python.org/tutorial/classes.html#generators>`__ sur les -générateurs dans la documentation officielle. - -Classes avec deux méthodes dont l'une est ``__init__`` -====================================================== - -Petit rappel sur les classes. On définit une classe de la façon suivante : - -.. code:: python - - class Cipher: - - def __init__(self, key): - self.key = key - - def decrypt(self, message): - return (message & self.key) - -toutes les méthodes d'une classe prennent comme premier argument (qu'on nomme -toujours ``self`` par convention) l'instance de la classe sur laquelle la -méthode est appelée. Ainsi, si la variable ``a`` est une instance de la classe -``Cipher``, l'appel ``a.decrypt(message)`` est équivalent à ``decrypt(a, -message)``. - -La fonction spéciale ``__init__`` est appelée au moment où une instance de la -classe est créée (c'est l'équivalent d'un constructeur). On l'utilise -principalement pour initialiser les attributs de l'instance. On crée une -instance de la classe ``Cipher`` ainsi : - -.. code:: python - - d = Cipher(key) - -Un défaut assez répandu parmi les gens qui viennent des langages orientés -objets (et tout particulièrement Java) et de créer des classes pour tout et -n'importe quoi. Ceci conduit souvent à des classes qui ne contiennent que deux -méthodes, l'une d'elles étant la fonction ``__init__``. C'est le cas de la -classe donnée en exemple ci-dessus. En y regardant de plus près, on pourrait -complètement se passer d'une classe ici : il suffit d'une fonction ``decrypt`` -prenant également en argument la clé (au lieu de l'obtenir comme attribut de -l'objet) : - -.. code:: python - - def decrypt(key, message): - return (message & key) - -Certaines personnes objecteraient ceci : *oui, mais dans ce cas une classe peut -quand même faire sens, car si on veut étendre plus tard la classe Cypher -pour lui ajouter d'autres méthodes (par exemple une méthode pour encrypter) -alors on a déjà une structure en place*. Mon point de vue sur la question est -qu'il vaut mieux commencer par le code le plus simple possible, et si plus tard -il s'avère qu'on a vraiment besoin d'étendre le code, alors il n'est jamais -trop tard pour restructurer quelques fonctions pour les mettre dans une même -classe. - -PEP8 -==== - -Je ne pouvais pas terminer une liste de recommandations sans parler de la PEP8 -:). Comme je l'avais déjà mentionnée, la PEP8 est un ensemble de -recommandations stylistiques sur la façon d'écrire du code Python. Sans être -une règle absolue, je trouve que suivre le style de la PEP8 donne plus de -lisibilité au code. De plus, puisque beaucoup de développeurs Python s'alignent -sur ce standard, le fait de l'appliquer réduit le décalage entre son propre -code et le code écrit par d'autres, ce qui est plus confortable au moment de -rentrer dans du code étranger. - -Voici ici les points de la PEP8 qui sont, je trouve, les plus agaçants quand -ils ne sont pas suivis : - -* on respecte les règles de typographie (anglaise) : pas d'espace avant les - deux points, espace après la virgule mais pas avant, etc. -* on met des espaces autour des opérateurs (comme le signe égal, le signe plus, - etc.). -* on essaie de limiter la longueur des lignes à 80 caractères maximum. - -`Lire la PEP8 <http://www.python.org/dev/peps/pep-0008/>`__ |
