summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/index.rst498
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/>`__