5. Développer en python

5.1. Le zen du python

Le zen du Python (PEP 20) est une série de 20 aphorismes [1] donnant les grands principes du Python:

>>> import this
  1. Beautiful is better than ugly.
  2. Explicit is better than implicit.
  3. Simple is better than complex.
  4. Complex is better than complicated.
  5. Flat is better than nested.
  6. Sparse is better than dense.
  7. Readability counts.
  8. Special cases aren’t special enough to break the rules.
  9. Although practicality beats purity.
  10. Errors should never pass silently.
  11. Unless explicitly silenced.
  12. In the face of ambiguity, refuse the temptation to guess.
  13. There should be one– and preferably only one –obvious way to do it.
  14. Although that way may not be obvious at first unless you’re Dutch.
  15. Now is better than never.
  16. Although never is often better than right now.
  17. If the implementation is hard to explain, it’s a bad idea.
  18. If the implementation is easy to explain, it may be a good idea.
  19. Namespaces are one honking great idea – let’s do more of those!

Une traduction libre en français:

  1. Préfèrer le beau au laid,
  2. l’explicite à l’implicite,
  3. le simple au complexe,
  4. le complexe au compliqué,
  5. le déroulé à l’imbriqué,
  6. l’aéré au compact.
  7. La lisibilité compte.
  8. Les cas particuliers ne le sont jamais assez pour violer les règles,
  9. même s’il faut privilégier l’aspect pratique à la pureté.
  10. Ne jamais passer les erreurs sous silence,
  11. ou les faire taire explicitement.
  12. Face à l’ambiguïté, ne pas se laisser tenter à deviner.
  13. Il doit y avoir une – et si possible une seule – façon évidente de procéder,
  14. même si cette façon n’est pas évidente à première vue, à moins d’être Hollandais.
  15. Mieux vaut maintenant que jamais,
  16. même si jamais est souvent mieux qu’immédiatement.
  17. Si l’implémentation s’explique difficilement, c’est une mauvaise idée.
  18. Si l’implémentation s’explique facilement, c’est peut-être une bonne idée.
  19. Les espaces de nommage sont une sacrée bonne idée, utilisons-les plus souvent !

5.1.1. Us et coutumes

Quelques conseils supplémentaires:

  • « Ne réinventez pas la roue, sauf si vous souhaitez en savoir plus sur les roues » (Jeff Atwood [3]): cherchez si ce que vous voulez faire n’a pas déjà été fait (éventuellement en mieux...), construisez dessus (en citant évidemment vos sources), et contribuez si possible!
  • Codez proprement: commentez votre code, utilisez des noms de variable qui ont un sens, créez des objets si nécessaire, etc.
  • Codez proprement dès le début: ne croyez pas que vous ne relirez jamais votre code (ou même que personne n’aura jamais à le lire), ou que vous aurez le temps de le refaire mieux plus tard...
  • « L’optimisation prématurée est la source de tous les maux » (Donald Knuth [4]): mieux vaut un code lent mais juste et maintenable qu’un code rapide et faux ou incompréhensible. Dans l’ordre absolu des priorités:
    1. Make it work.
    2. Make it right.
    3. Make it fast.
  • Respectez le zen du python, il vous le rendra.

5.2. Développement piloté par les tests

Le Test Driven Development (TDD, ou en français « développement piloté par les tests ») est une méthode de programmation qui permet d’éviter des bugs a priori plutôt que de les résoudre a posteriori. Ce n’est pas une méthode propre à Python, elle est utilisée très largement par les programmeurs professionnels.

Le cycle préconisé par TDD comporte cinq étapes :

  1. écrire un premier test ;
  2. vérifier qu’il échoue (puisque le code qu’il teste n’existe pas encore), afin de s’assurer que le test est valide et exécuté ;
  3. écrire un code minimal pour passer le test ;
  4. vérifier que le test passe correctement ;
  5. éventuellement « réusiner » le code (refactoring), c’est-à-dire l’améliorer (rapidité, lisibilité) tout en gardant les mêmes fonctionnalités.

Diviser pour mieux régner: chaque fonction, classe ou méthode est testée indépendemment. Ainsi, lorsqu’un nouveau morceau de code ne passe pas les tests qui y sont associés, il est certain que l’erreur provient de cette nouvelle partie et non des fonctions ou objets que ce morceau de code utilise. On distingue ainsi hiérarchiquement:

  1. Les tests unitaires vérifient individuellement chacune des fonctions, méthodes, etc.
  2. Les tests d’intégration évaluent les interactions entre différentes unités du programmes.
  3. Les tests système assurent le bon fonctionnement du programme dans sa globalité.

Il est essentiel de garder tous les tests au cours du développement, ce qui permet de les réutiliser lorsque l’on veut compléter ou améliorer une partie du code. Si le nouveau code passe toujours les anciens test, on est alors sûr de ne pas avoir cassé les fonctionnalités précédentes.

Nous avons déjà vu aux TD précédents plusieurs façon de rédiger des tests unitaires:

  • Les doctest sont des exemples (assez simples) d’exécution de code inclus dans les docstring des classes ou fonctions:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    def mean_power(alist, power=1):
        """
        Retourne la racine `power` de la moyenne des éléments de `alist` à
        la puissance `power`:
    
        .. math:: \mu = (\frac{1}{N}\sum_{i=0}^{N-1} x_i^p)^{1/p}
    
        `power=1` correspond à la moyenne arithmétique, `power=2` au *Root
        Mean Squared*, etc.
    
        Exemples:
        >>> mean_power([1, 2, 3])
        2.0
        >>> mean_power([1, 2, 3], power=2)
        2.160246899469287
        """
    
        s = 0.                  # Initialisation de la variable *s* comme *float*
        for val in alist:       # Boucle sur les éléments de *alist*
            s += val ** power   # *s* est augmenté de *val* puissance *power*
        s /= len(alist)         # = somme valeurs / nb valeurs
        # *mean* = (somme valeurs / nb valeurs)**(1/power)
        mean = s ** (1 / power)
    
        return mean
    

    Les doctests peuvent être exécutés de différentes façons (voir ci-dessous):

    • avec le module standard doctest: python -m doctest -v mean_power.py
    • avec pytest: py.test --doctest-modules -v mean_power.py
    • avec nose: nosetests --with-doctest -v mean_power.py
  • Les fonctions dont le nom commence par test_ et contenant des assert sont automatiquement détectées par pytest [2]. Cette méthode permet d’effectuer des tests plus poussés que les doctests, éventuellement dans un fichier séparé du code à tester. P.ex.:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    def test_empty_init():
        with pytest.raises(TypeError):
            youki = Animal()
    
    
    def test_wrong_init():
        with pytest.raises(ValueError):
            youki = Animal('Youki', 'lalala')
    
    
    def test_init():
        youki = Animal('Youki', 600)
        assert youki.masse == 600
        assert youki.vivant
        assert youki.estVivant()
        assert not youki.empoisonne
    

    Les tests sont exécutés via py.test programme.py.

  • Le module unittest de la librairie standard permet à peu près la même chose que pytest, mais avec une syntaxe souvent plus lourde. unittest est étendu par le module non-standard nose.

5.3. Outils de développement

Je fournis ici essentiellement des liens vers des outils pouvant être utiles pour développer en python.

5.3.1. Integrated Development Environment

5.3.2. Vérification du code

Il s’agit d’outils permettant de vérifier a priori la validité syntaxique du code, de mettre en évidence des constructions dangereuses, les variables non-définies, etc. Ces outils ne testent pas nécessairement la validité des algorithmes et de leur mise en oeuvre...

5.3.3. Documentation

5.3.4. Débugage et optimisation

Débogage

Les débogueurs permettent de « plonger » dans un code au cours d’exécution ou juste après une erreur (analyse post-mortem).

  • Modules de la bibliothèque standard: pdb, timeit

    Ainsi, pour déboguer un script, il est possible de l’exécuter sous le contrôle du débogueur pdb en s’interrompant dès la 1ère instruction:

    python -m pdb script.py
    (Pdb)
    
  • Commandes ipython: %run, %debug, %timeit

    Si un script exécuté sous ipython (commande %run) lève une exception, il est possible d’inspecter l’état de la mémoire au moment de l’erreur avec la commande %debug.

Profilage et optimisation

Avertissement

Premature optimization is the root of all evil – Donald Knuth

Le profilage permet de déterminer le temps passé dans chacune des sous-fonctions d’un code, afin d’y identifier les parties à optimiser.

5.3.5. Python packages

Comment installer/créer des modules externes:

5.3.6. Python 2 vs. python 3

Notes de bas de page

[1]Dont seulement 19 ont été écrits.
[2]pytest ne fait pas partie de la librairie standard. Il vous faudra donc l’installer indépendemment si vous voulez l’utiliser.
[3]« Don’t reinvent the wheel, unless you plan on learning more about wheels » – Jeff Atwood
[4]« Premature optimization is the root of all evil » – Donald Knuth