.. _code: Développer en Python #################### .. contents:: Table des matières :local: .. 1 Le zen du Python 1.1 Us et coutumes 1.2 Principes de conception logicielle 2 Développement piloté par les tests 3 Outils de développement 3.1 *Integrated Development Environment* 3.2 Vérification du code 3.3 Débogage 3.4 Profilage et optimisation 3.5 Documentation 3.6 *Python packages* 3.7 Système de gestion de versions 3.8 Intégration continue Le zen du Python ================ .. index:: Zen du Python Le *zen du Python* (:pep:`20`) est une série de 20 aphorismes [#zen]_ 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 la praticité à la pureté. 10. Ne jamais passer les erreurs sous silence, 11. ... ou les faire taire explicitement. 12. En cas d’ambiguïté, résister à la tentation de deviner. 13. Il devrait y avoir une -- et de préférence 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 préférable à 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 noms sont une sacrée bonne idée, utilisons-les plus souvent ! Us et coutumes -------------- - :wfr:`Principe KISS`: *Keep it simple, stupid!* - *Don't repeat yourself.* - *Fail early, fail often, fail better!* (`raise`) - *Easier to Ask for Forgiveness than Permission.* (`try ... except`) - *We’re all consenting adults here.* (attributs privés) .. rubric:: Quelques conseils supplémentaires: - « *Don't reinvent the wheel, unless you plan on learning more about wheels.* » (Jeff Atwood) Cherchez si ce que vous voulez faire n'a pas déjà été fait (éventuellement en mieux...) pour vous concentrer sur *votre* valeur ajoutée; réutilisez le code (en citant évidemment vos sources), améliorez le, et contribuez en retour si possible! - Écrivez des programmes pour les humains, pas pour les ordinateurs: codez *proprement*, structurez vos algorithmes, commentez votre code, utilisez des noms de variable qui ont un sens, soignez le style et le formatage, etc. - *Code is read far more often than it is written.* 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... - *You ain't gonna need it*: se concentrer sur les fonctionnalités nécessaires plutôt que de prévoir d'emblée l'ensemble des cas. - « *Premature optimization is the root of all evil.* » (Donald Knuth) 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. .. rubric:: Voir également: - le *Style Guide for Python Code* (:pep:`8`, :ref:`Coding Style `) - `Google Python Style Guide `_ - `The Best of the Best Practices (BOBP) Guide for Python `_ - The hitchhiker's guide to Python `Code Style `_ - `les secrets d'un code pythonique `_ |fr| Principes de conception logicielle ---------------------------------- La bonne conception d'un programme va permettre de gérer efficacement la complexité des algorithmes, de faciliter la maintenance (p.ex. correction des erreurs) et d'accroître les possibilités d'extension. **Modularité** Le code est structuré en répertoires, fichiers, classes, méthodes et fonctions. Les blocs ne font pas plus de quelques dizaines de lignes, les fonctions ne prennent que quelques arguments, la structure logique n'est pas trop complexe, etc. En particulier, le code doit respecter le *principe de responsabilité unique*: chaque entité élémentaire (classe, méthode, fonction) ne doit avoir qu'une unique raison d'exister, et ne pas tenter d'effectuer plusieurs tâches sans rapport direct (p.ex. lecture d'un fichier de données *et* analyse des données). **Flexibilité** Une modification du comportement du code (p.ex. l'ajout d'une nouvelle fonctionnalité) ne nécessite de changer le code qu'en un nombre restreint de points. Un code *rigide* devient rapidement difficile à faire évoluer, puisque chaque changement requiert un grand nombre de modifications. **Robustesse** La modification du code en un point ne change pas de façon inopinée le comportement dans une autre partie *a priori* non reliée. Un code *fragile* est facile à modifier, mais chaque modification peut avoir des conséquences inattendues et le code tend à devenir instable. **Réutilisabilité** La réutilisation d'une portion de code ne demande pas de changement majeur, n'introduit pas trop de dépendances, et ne conduit pas à une duplication du code. L'application de ces principes de développement dépend évidemment de l'objectif final du code: - une bibliothèque de bas niveau, utilisée par de nombreux programmes (p.ex. :mod:`numpy`), favorisera la robustesse et la réutilisabilité aux dépends de la flexibilité: elle devra être particulièrement bien pensée, et ne pourra être modifiée qu'avec parcimonie; - inversement, un script d'analyse de haut niveau, d'utilisation restreinte, pourra être plus flexible mais plus fragile et peu réutilisable. Outils de développement ======================= Je fournis ici essentiellement des liens vers des outils pouvant être utiles pour développer en Python. .. rubric:: Voir également: * `formateurs de code, analyse statique, etc. `_ .. _ide: *Integrated Development Environment* ------------------------------------ - :ref:`IDLE `, l'IDE de base intégré à Python (très limité, pas recommandé) - Éditeurs avancés: :program:`emacs` (voir `Emacs: the best python editor? `_), :program:`vim` (voir `Vim & Python `_) - IDE open source: - `pyzo `_ et `thonny `_, deux IDE légers spécifiques à Python, - `spyder `_, l'IDE de référence spécifique à Python, - `VS Codium `_, la version open-source et sans mouchard de `Visual Studio Code `_ de Microsoft, un IDE générique, - `eclipse-PyDev `_, le plugin Python d'`Eclipse `_, l'IDE générique de référence dans l'industrie, puissant mais lourd. - IDE propriétaires: - `pyCharm `_ version *community*, l'IDE propriétaire de référence spécifique à Python, puissant mais lourd, - `SublimeText `_, un éditeur/IDE générique (une utilisation prolongée requiert une licence). .. tip:: Dans le doute, je conseille :program:`spyder` (python uniquement) ou :program:`codium` (générique). .. rubric:: Voir également: - `10 Best Python IDE & Code Editors `_ - `Python IDEs and Code Editors `_ - :wen:`List of IDEs for Python ` Vérification du code -------------------- Il s'agit d'outils permettant de vérifier *a priori* la validité stylistique et syntaxique du code, de mettre en évidence des constructions dangereuses, les variables non-définies, etc. Ces outils ne testent pas la validité des algorithmes et de leur mise en oeuvre: c'est le rôle des :doc:`tests`. - *code formatters:* :pypi:`pycodestyle` et :pypi:`autopep8`, :pypi:`black`, - *static code analysers*: :pypi:`pyflakes` (rapide, mais peu exigeant), :pypi:`pylint` (plus exigeant, voir pointilleux). `pylint` étant un peu tatillon, vous pouvez le configurer avec le fichier :download:`pylintrc <../Projets/pylintrc>`:: $ pylint --rcfile pylintrc mypackage [...] Your code has been rated at 8.53/10 Débogage -------- Les débogueurs permettent de se « plonger » dans un code en cours d'exécution ou juste après une erreur (analyse post-mortem). - Module de la bibliothèque standard: :mod:`pdb` Pour déboguer un script, il est possible de l'exécuter sous le contrôle du débogueur :mod:`pdb` en s'interrompant dès la 1re instruction:: python -m pdb script.py (Pdb) Commandes (très similaires au :wfr:`GNU Debugger`): * :samp:`h[elp] {[command]}`: aide en ligne; * :samp:`q[uit]`: quitter; * :samp:`r[un] {[args]}`: exécuter le programme avec les arguments; * :samp:`d[own]/u[p]`: monter/descendre dans le stack (empilement des appels de fonction); * :samp:`p {expression}`: afficher le résultat de l'expression (`pp`: *pretty-print*); * :samp:`l[ist] {[first[,last]]}`: afficher le code source autour de l'instruction courante (`ll`: *long list*); * :samp:`n[ext]/s[tep]`: exécuter l'instruction suivante (sans y entrer/en y entrant); * :samp:`unt[il]`: continuer l'exécution jusqu'à la ligne suivante (utile pour les boucles); * :samp:`c[ont[inue]]`: continuer l'exécution (jusqu'à la prochaine interruption ou la fin du programme); * :samp:`r[eturn]`: continuer l'exécution jusqu'à la sortie de la fonction; * :samp:`b[reak] {[[filename:]lineno | function[, condition]]}`: mettre en place un point d'arrêt (`tbreak` pour un point d'arrêt *temporaire*). Sans argument, afficher les points d'arrêts déjà définis; * :samp:`disable/enable {[bpnumber]}`: désactiver/réactiver tous ou un point d'arrêt; * :samp:`cl[ear] {[bpnumber]}`: éliminer tous ou un point d'arrêt; * :samp:`ignore {bpnumber [count]}`: ignorer un point d'arrêt une ou plusieurs fois; * :samp:`condition {bpnumber}`: ajouter une condition à un point d'arrêt; * :samp:`commands {[bpnumber]}`: ajouter des instructions à un point d'arrêt. - Commandes :program:`ipython`: :samp:`%run {monScript.py}`, `%debug`, `%pdb` Si un script exécuté sous :program:`ipython` (commande `%run`) génère une exception, il est possible d'inspecter l'état de la mémoire au moment de l'erreur avec la commande `%debug`, qui lance une session :mod:`pdb` au point d'arrêt. `%pdb on` lance systématiquement le débogueur à chaque exception. L'activité de débogage s'intégre naturellement à la nécessité d'écrire des tests unitaires: 1. trouver un bogue; 2. écrire un test qui aurait du être validé en l'absence du bogue; 3. corriger le code jusqu'à validation du test. Vous aurez alors au final corrigé le bug, *et* écrit un test s'assurant que ce bogue ne réapparaîtra pas inopinément. .. _ProfOpt: Profilage et optimisation ------------------------- .. Warning:: *Premature optimization is the root of all evil* -- Donald Knuth Avant toute optimisation, s'assurer extensivement que le code fonctionne et produit les bons résultats dans tous les cas. S'il reste trop lent ou gourmand en mémoire *pour vos besoins*, il peut être nécessaire de l'optimiser. Le :ref:`profilage ` permet de déterminer le temps passé dans chacune des sous-fonctions d'un code (ou ligne par ligne: :pypi:`line profiler `, ou selon l'utilisation de la mémoire: :pypi:`memory profiler `), afin d'y identifier les parties qui gagneront à être optimisées. - `python -O`, `__debug__`, `assert` Il existe un mode « optimisé » de python (option `-O`), qui pour l'instant ne fait pas grand chose (et n'est donc guère utilisé....): * la variable interne `__debug__` passe de `True` à `False`; * les instructions `assert` ne sont pas évaluées. - :mod:`timeit` et :samp:`%timeit {statement}` sous :program:`ipython`:: In [1]: def t1(n): ...: l = [] ...: for i in range(n): ...: l.append(i**2) ...: return l ...: ...: def t2(n): ...: return [ i**2 for i in range(n) ] ...: ...: def t3(n): ...: return np.arange(n)**2 In [2]: %timeit t1(10000) 2.7 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [3]: %timeit t2(10000) 2.29 ms ± 13.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [4]: %timeit t3(10000) 15.9 µs ± 120 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) - :mod:`cProfile` et :mod:`pstats`, et :samp:`%prun {statement}` sous :program:`ipython`:: $ python -m cProfile calc_pi.py 3.1415925580959025 10000005 function calls in 4.594 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 1.612 1.612 4.594 4.594 calc_pi.py:10(approx_pi) 1 0.000 0.000 4.594 4.594 calc_pi.py:5() 10000000 2.982 0.000 2.982 0.000 calc_pi.py:5(recip_square) 1 0.000 0.000 4.594 4.594 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.print} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} - :pypi:`scalene`, a high-resolution, low-overhead CPU, GPU, and memory profiler for Python - `Tutoriel de profilage `_ Une fois identifiée la partie du code à optimiser, quelques conseils généraux: * en cas de doute, favoriser la lisibilité aux performances; * utiliser des opérations sur les tableaux, plutôt que sur des éléments individuels (vectorisation): listes en compréhension, tableaux :mod:`numpy` (qui ont eux-mêmes été optimisés); * `cython `_ est un langage de programmation **compilé** très similaire à python. Il permet d'écrire des extensions en C avec la facilité de python (voir notamment `Working with Numpy `_); * `numba `_ permet *automagiquement* de compiler *à la volée* (:abbr:`JIT(Just In Time)`) du pur code python via le compilateur `LLVM `_, avec une optimisation selon le CPU (éventuellement le GPU) utilisé, p.ex.:: from numba import jit # compilation à la volée (seulement au 1e appel) @jit def crible(n): ... ou:: from numba import guvectorize # ufunc numpy compilée @guvectorize(['void(float64[:], intp[:], float64[:])'], '(n),()->(n)') def move_mean(a, window_arr, out): ... * à l'avenir, l'interpréteur CPython actuel sera éventuellement remplacé par `pypy `_, basé sur une compilation :abbr:`JIT(Just In Time)`. .. rubric:: Lien: `Performance tips `_ Documentation ------------- Voir Section :doc:`documentation`. * Conventions de documentation: - `NumpyDoc Style guide `_, - `Sample doc `_ (matplotlib). .. rubric:: Lien: * `Documentation Tools `_ Outils de *packaging* --------------------- Le packaging Python continue d'être un point délicat en perpétuel reconstruction (voir :doc:`packaging`)... - :rtfd:`cookiecutter` est un générateur de squelettes de projet via des *templates* (pas uniquement pour Python); - :rtfd:`cx-freeze`, pour générer un exécutable à partir d'un script. .. rubric:: Voir également: * :pypi:`flit`: *Simple packaging tool for simple packages* (outil pyPA), * :pypi:`hatch`: *Python project manager* (outil pyPA), * :pypi:`poetry`: *Dependency Management for Python*, * `Which Python Dependency Manager Should I Choose? `_. Intégration continue -------------------- L'intégration continue est un ensemble de pratiques de développement logiciel visant à s'assurer de façon systématique que chaque modification du code n'induit aucune *régression*, et passe l'ensemble des tests. Cela passe généralement par la mise en place d'un système de gestion des sources, auquel est accolé un mécanisme automatique de compilation (*build*), de déploiement sur les différentes infrastructures, d'éxecution des tests (unitaires, intégration, fonctionnels, etc.) et de mise à disposition des résultats, de mise en ligne de la documentation, etc. .. rubric:: Liens: * `A beginner's guide to gitlab continuous integration `_ La plupart des développements des logiciels *open source* majeurs se fait maintenant sous intégration continue en utilisant des services en ligne directement connectés au dépôt source. Exemple sur `Astropy`: * `Travis CI `_ intégration continue; * `Coveralls `_ taux de couverture des tests unitaires; * :rtfd:`Read The Docs ` documentation en ligne; * `Depsy `_ mise en valeur du développement logiciel dans le monde académique (*measure the value of software that powers science*, **non maintenu**). .. rubric:: Notes de bas de page .. [#zen] Dont seulement 19 ont été écrits. .. |fr| image:: ../_static/france_flag_icon.png :alt: Fr .. |en| image:: ../_static/uk_flag_icon.png :alt: En