Le langage \href{https://www.python.org/}{Python} est un \href{https://docs.python.org/3/faq/general.html\#what-is-python}{langage de programmation} interprété, interactif, amusant, orienté objet (souvent), fonctionnel (parfois), open source, multi-plateformes, flexible, facile à apprendre et difficile à maîtriser.
Il a été développé et publié en 1991 pour Guido van Rossum, qui \textit{cherchait à créer un langage amateur pour s'occuper le week-end et durant les fêtes de Noël}\footnote{\url{https://www.python.org/doc/essays/foreword}}.
Le projet a décollé et Python est maintenant considéré comme l'un des langages de programmation les plus populaires.
Le langage est géré en partie par la \textit{Python Software Foundation}, créée en 2001, et en partie par différents sponsors, dont HPE, Intel et Google. \footnote{De son côté, Django est soutenu par la \href{https://www.djangoproject.com/foundation/}{Django Foundation}. Elle est alimentée par des volontaires et recevait (en 2013) moins de \$50 000 en donations directes. \cite[p. 112]{roads_and_bridges}}
L'indentation définit l'étendue d'un bloc (classe, fonction, méthode, boucle, condition),
\item
Il n'y a pas de typage fort des variables
\item
Le compilateur n'est pas là pour assurer le filet de sécurité avant la mise en production (puisqu'il n'y a pas de compilateur)
\end{itemize}
Malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et peut se reposer sur un écosystème stable et fonctionnel, qui tourne grâce avec un système d'améliorations basées sur des propositions: les PEP, ou "\textbf{Python Enhancement Proposal}\index{PEP}".
Chacune d'entre elles doit être approuvée par le \href{http://fr.wikipedia.org/wiki/Benevolent_Dictator_for_Life}{Benevolent Dictator For Life\index{BDFL}}.
Le langage Python utilise un typage dynamique appelé \href{https://fr.wikipedia.org/wiki/Duck_typing}{\textbf{duck typing}}:
\textbf{Pour les débutants}, \href{https://automatetheboringstuff.com/}{Automate the Boring Stuff with Python}\cite{boring_stuff}, aka. \emph{Practical Programming for Total Beginners}
\item
\textbf{Pour un (gros) niveau au dessus} et pour un état de l'art du langage, nous ne pouvons que vous recommander le livre Expert Python Programming \cite{expert_python}, qui aborde énormément d'aspects du langage en détails (mais pas toujours en profondeur): les différents types d'interpréteurs, les éléments de langage avancés, différents outils de productivité, métaprogrammation, optimisation de code, programmation orientée évènements, multithreading et concurrence, tests, \ldots
A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
En parallèle, si vous avez besoin d'un aide-mémoire ou d'une liste exhaustive des types et structures de données du langage, référez-vous au lien suivant: \href{https://gto76.github.io/python-cheatsheet/}{Python Cheat Sheet}.
Le modèle de données du langage spécifie un ensemble de méthodes qui peuvent être surchargées.
Ces méthodes suivent une convention de nommage et leur nom est toujours encadré par un double tiret souligné; d'où leur nom de "\emph{dunder methods}\index{dunder}" ou "\emph{double-underscore methods}".
La méthode la plus couramment utilisée est la méthode \texttt{init()}, qui permet de surcharger l'initialisation d'une instance de classe.
Une liste complètement des \emph{dunder methods} peut être trouvée dans la section \texttt{Data\ Model} de \href{https://docs.python.org/3/reference/datamodel.html}{la documentation du langage Python}.
Tous les opérateurs sont également exposés comme des fonctions ordinaires du module \texttt{opeartor}, dont la \href{https://docs.python.org/3.9/library/operator.html}{documentation} donne un bon aperçu.
Indiquer qu'un type d'objet implémente un protocole du langage indique simplement qu'il est compatible avec une partie spécifique de la syntaxe du langage Python.
Vous trouverez ci-dessous un tableau reprenant les protocoles les plus courants:
Par exemple, la méthode \texttt{add()} est responsable de l'implémentation de l'opérateur \texttt{+}, la méthode \texttt{sub()} s'occupe de la soustraction ()\texttt{–}), tandis que \texttt{mul()} gère l'opérateur \texttt{*}.
L'exemple ci-dessous implémente la soustraction de deux matrices:
Il suffit dès lors de réaliser la soustraction matricielle entre deux objets de type \texttt{Matrix} pour que la méthode \texttt{sub())} ci-dessous soit appelée.
En fait, l'intérêt concerne surtout la représentation de nos modèles, puisque chaque classe du modèle est représentée par la définition d'un objet Python.
\item Les listes (y inclus les tableaux) et tuples
\item Les dictionnaires (y inclus les \texttt{maps})
\item Les \textit{namedtuples} et \textit{dataclasses}
\item Les classes
\end{itemize}
\subsection{Dictionnaires}
Au hasard des internets multimédias, on est tombé sur un morceau de code à base de \texttt{pop} et de réinitialisation.
Quelque chose comme ceci:
\begin{minted}{python}
width = OPT_CMD_ARGS.pop(width_arg_name)
heigth = OPT_CMD_ARGS.pop(heigth_arg_name)
args_list.append(width)
args_list.append(heigth)
OPT_CMD_ARGS[width_arg_name] = width
OPT_CMD_ARGS[heigth_arg_name] = heigth
\end{minted}
Techniquement, un dictionnaire est mutable et peut donc être modifié.
Dans ce cas-ci, le dictionnaire sert à récupérer l'association entre un nom de paramètre et sa valeur pour la ligne de commande.
Le `pop` est donc utilisé uniquement pour récupérer une valeur: dans ce cas, il est possible d'utiliser la méthode `get`, qui permettra de définir une valeur par défaut:
\begin{minted}{python}
>>> canevas = dict() # on aurait aussi pu déclarer le dictionnaire avec canevas = {}
>>> canevas["a"] # avec un accès direct, si la clé n'existe pas, une exception est levée
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'a'
>>> canevas.get("a", "abracadabra") # par contre, si on utilise get, aucune exception n'est levée
'abracadabra'
>>> canevas
{}
\end{minted}
Y'a une méthode du fieu de dieu: `setdefault` !
en gros, ça te retourne la valeur de la clé si elle existe, et si pas, cela te crée une nouvelle clé et te la retourne:
\begin{minted}{python}
>>> d = {}
>>> d.setdefault(1, "Yolo")
'Yolo'
>>> d
{1: 'Yolo'}
\end{minted}
Ca évite, dans une boucle, de vérifier si la clé existe déjà, et si pas, de la créer
Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, \ldots
En bref, elle décrit comment écrire du code proprement, afin que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
Comme l'indique la PEP20, \textit{Readibility counts}.
Ceci implique de garder une cohérence dans l'écriture du code, dont les principales recommandations concernent:
\begin{enumerate}
\item
\textbf{Le layout général} du code: indentation, longueur de ligne, séparateurs de lignes, encodage des fichiers sources et gestion des imports.
\item
\textbf{La gestion des commentaires}, en fonction de leur emplacement: blocs de commentaires, commentaires embarqués ou documentation.
\item
\textbf{Les conventions de nommage}: les noms à éviter, comment nommer une classe, une fonction ou une variable, un paquet, les exceptions ou les constantes.
\item
\textbf{Des recommandations de programmation}, concernant le typage statique \index{PEP!PEP484}
Dans cet objectif, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: flake8. Pour l'installer, passez par pip.
Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options \texttt{-\/-statistics\ -qq} - l'attribut \texttt{-qq} permettant simplement d'ignorer toute sortie console autre que les statistiques demandées).
Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous voulez juste avoir un retour sur une analyse de votre code, essayez \texttt{pyflakes}: cette librairie analysera vos sources à la recherche de sources d'erreurs possibles (imports inutilisés, méthodes inconnues, etc.).
Il y a une énorme différence entre la documentation du code et les commentaires associés au code:
\begin{enumerate}
\item\textbf{Les commentaires} décrivent comment le code fonctionne
\item\textbf{La documentation du code} décrit comment utiliser le code.
\end{enumerate}
Pour que le code soit utilisé correctement, il est dès lors important de documenter :
\begin{enumerate}
\item
Ce que représente une classe ou une API
\item
Les attentes d'une interface : codes d'erreurs, statuts possibles, etc.
\end{enumerate}
Les commentaires sont cependant utiles lorsque le code n'est pas évident à lire ou lorsque certaines optimisations auraient été appliquées dans un soucis de performances.
Dans la majorité des cas cependant, si des commentaires doivent être rédigés pour que le code devienne lisible, c'est que ce code n'est pas correctement écrit. \cite{clean_code}{53-74}\footnote{\url{https://www.youtube.com/watch?v=Bf7vDBBOBUA}}
Nous pouvons constater un risque de décorrélation entre le code et ses commentaires :
\begin{listing}[!ht]
\begin{minted}[tabsize=4]{python}
# Check that the maximum length of the array if less than 8
Il y a donc une juste mesure à prendre entre "tout documenter" (les modules, les paquets, les classes, les fonctions, méthodes, \ldots) et "bien documenter":
Git ou tout VCS s'en sortira très bien et sera beaucoup plus efficace que n'importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
S'il est nécessaire de décrire un comportement au sein-même d'une fonction, avec un commentaire \emph{inline}, c'est que ce comportement pourrait être extrait dans une nouvelle fonction (qui, elle, pourra être documentée proprement).
La \href{https://peps.python.org/pep-0257/#what-is-a-docstring}{PEP-257} nous donne des recommandations haut-niveau concernant la structure des docstrings: ce qu'elles doivent contenir et comment l'expliciter, sans imposer quelle que mise en forme que ce soit.
de contenu, mais pas de forme, notamment sur la manière de représenter des docstrings ne nécessitant qu'une seule ligne, nécessitant plusieurs lignes ou de gérer l'indentation.
Son objectif est d'arriver à une forme de cohérence lorsqu'un utilisateur souhaitera accéder à la propriété
“A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesn’t do is insist that you follow it against your will. That’s Python!”
\href{https://numpydoc.readthedocs.io/en/latest/format.html}{Numpy} sépare chaque section avec un pseudo-formatage de titre.
Les sections sont définies selon les types suivants:
\begin{itemize}
\item
Une courte description (de la classe, du module, de la fonction, \ldots).
Chaque module devrait avoir une docstring au tout début du fichier.
Cette docstring peut s'étendre sur plusieurs lignes (\textit{Short summary})
\item
Des avertissements liés à la dépréciation: quand la fonctionnalité viendra à disparaitre (dans quelle version), les raisons de sa dépréciation, et les recommandations pour arriver au même résultat. (\textit{Deprecated})
\item
Une description plus précise des fonctionnalités proposées (et pas de la manière dont l'implémentation est réalisée). (\textit{Extended summary})
\item
Les paramètres attendus, leur type et une description (\textit{Parameters})
\item
La ou les valeurs de retour, accompagnées de leur type et d'une description (\textit{Returns})
\item
Les valeurs générées (\textit{yields}) dans le cas de générateurs (\textit{Yields})
\item
Un objet attendu par la méthode \texttt{send()} (toujours dans le cas de générateurs) (\textit{Receives})
\item
D'autres paramètres, notamment dans le cas des paramètres \texttt{*args} et \texttt{**kwargs}. (\textit{Other parameters})
\item
Les exceptions levées (\textit{Raises})
\item
Les avertissements destinés à l'utilisateur (\textit{Warnings})
\item
Une section "Pour continuer\ldots" (\textit{See also})
\item Des notes complémentaires (\textit{Notes})
\item Des références (\textit{References})
\item Des exemples (\textit{Examples})
\end{itemize}
\begin{listing}[!ht]
\begin{minted}[tabsize=4]{python}
"""Docstring for the example.py module.
Modules names should have short, all-lowercase names. The module name may
Les \href{https://google.github.io/styleguide/pyguide.html\#38-comments-and-docstrings}{conventions proposées par Google} nous semblent plus faciles à lire que du RestructuredText, mais sont parfois moins bien intégrées que les docstrings officiellement supportées (par exemple, \href{https://clize.readthedocs.io/en/stable/}{clize} ne reconnait que du RestructuredText; \href{https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/}{l'auto-documentation} de Django également).
Le premier niveau est assuré par \href{https://pypi.org/project/pycodestyle/}{pycodestyle}, qui analyse votre code à la recherche d'erreurs de convention de style.
PyLint est le meilleur ami de votre \emph{moi} futur, un peu comme quand vous prenez le temps de faire la vaisselle pour ne pas avoir à la faire le lendemain: il rendra votre code soyeux et brillant, en posant des affirmations spécifiques.
A vous de les traiter en corrigeant le code ou en apposant un \emph{tag} indiquant que vous avez pris connaissance de la remarque, que vous en avez tenu compte, et que vous choisissez malgré tout de faire autrement.
\caption{Calvin \& Hobbes se rendent dans le futur pour récupérer les devoirs que son moi-du-futur aura fait (et pour ne pas avoir à les faire lui-même).}
Pylint propose également une option particulièrement efficace, qui prend le paramètre \texttt{--errors-only}, et qui n'affiche que les occurrences appartenant à la catégorie \textbf{E}.
Si nous souhaitons ignorer l'une de ces catégories, ce doit être fait explicitement: de cette manière, nous marquons notre approbation pour que pylint ignore consciemment un élément en particulier.
Nous avons parlé ci-dessous de style de codage pour Python (PEP8) \index{PEP!PEP8}, de style de rédaction pour la documentation (PEP257) \index{PEP!PEP257}, d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, \ldots
Concrètement, cela signifie que des erreurs qui auraient pu avoir été détectées lors de la phase de compilation, ne le sont pas avec Python.
Il existe cependant une solution à ce problème, sous la forme de \href{http://mypy-lang.org/}{Mypy}, qui peut vérifier une forme de typage statique de votre code source, grâce à une expressivité du code, basée sur des annotations.
Ces vérifications se présentent de la manière suivante:
Malgré que nos annotations déclarent une liste d'entiers, rien ne nous empêche de lui envoyer une liste de caractères, sans que cela ne lui pose de problèmes.
La signature de notre fonction n'est donc pas cohérente avec son comportement.
Non seulement nous retournons la valeur \texttt{None} si la liste est vide alors que nous lui annoncions un entier en sortie, mais en plus, nous l'appelons avec une liste de caractères, alors que nous nous attendons à une liste d'entiers:
\begin{listing}[H]
\begin{verbatim}
>>> mypy mypy-test.py
mypy-test.py:7: error: Incompatible return value type (got "Optional[int]", expected "int")
mypy-test.py:12: error: List item 0 has incompatible type "str"; expected "int"
mypy-test.py:12: error: List item 1 has incompatible type "str"; expected "int"
mypy-test.py:12: error: List item 2 has incompatible type "str"; expected "int"
Found 4 errors in 1 file (checked 1 source file)
\end{verbatim}
\end{listing}
Pour corriger ceci, nous devons:
\begin{enumerate}
\item
Importer le type \texttt{Optional} et l'utiliser en sortie de notre
Une bonne pratique (parfois discutée) consiste cependant à switcher vers \texttt{pytest}, qui présente quelques avantages par rapport au module \texttt{unittest}:
\begin{itemize}
\item
Une syntaxe plus concise (au prix de \href{https://docs.pytest.org/en/reorganize-docs/new-docs/user/naming_conventions.html}{quelques conventions}, même si elles restent configurables): un test est une fonction, et ne doit pas obligatoirement faire partie d'une classe héritant de \texttt{TestCase} - la seule nécessité étant que cette fonction fasse partie d'un module commençant ou finissant par "test" (\texttt{test\_example.py} ou \texttt{example\_test.py}).
\item
Une compatibilité avec du code Python "classique" - vous ne devrez
donc retenir qu'un seul ensemble de commandes ;-)
\item
Des \emph{fixtures} faciles à réutiliser entre vos différents
composants
\item
Une compatibilité avec le reste de l'écosystème, dont la couverture de
code présentée ci-dessous.
\end{itemize}
Ainsi, après installation, il nous suffit de créer notre module
\texttt{test\_models.py}, dans lequel nous allons simplement tester
l'addition d'un nombre et d'une chaîne de caractères (oui, c'est
complètement biesse; on est sur la partie théorique ici):
Avec \texttt{pytest}, il convient d'utiliser le paquet \href{https://pypi.org/project/pytest-cov/}{\texttt{pytest-cov}}, suivi de la commande \texttt{pytest\ -\/-cov=gwift\ tests/}.
Si vous préférez rester avec le cadre de tests de Django, vous pouvez passer par le paquet\href{https://pypi.org/project/django-coverage-plugin/}{django-coverage-plugin}.
Certains bogues causent des erreurs de mal-fonctionnement sont dus à une mauvaise conception endormie, qui ne se présente que dans certains cas très spécifiques, entourés d'une contexte inhabituel \cite[p. 9]{data_design}.
Il est primordial de gérer correctement ses exceptions, et de faire en sorte que celles qui peuvent être anticipées le soient dès la phase de développement.
Une matrice de compatibilité consiste à spécifier un ensemble de plusieurs versions d'un même interpréteur (ici, Python), afin de s'assurer que votre application continue à fonctionner.
Nous sommes donc un cran plus haut que la spécification des versions des librairies, puisque nous nous situons directement au niveau de l'interpréteur.
L'objectif consiste à définir un tableau à deux dimensions, dans lequel nous trouverons la compatibilité entre notre application et une version de l'interpréteur.
L'outil le plus connu est \href{https://tox.readthedocs.io/en/latest/}{Tox}, qui consiste en un outil basé sur virtualenv et qui permet:
\begin{enumerate}
\item De vérifier que votre application s'installe correctement avec différentes versions de Python et d'interpréteurs
\item De démarrer des tests parmi ces différents environnements
\end{enumerate}
\begin{listing}
\begin{verbatim}
# content of: tox.ini , put in same dir as setup.py
[tox]
envlist = py36,py37,py38,py39
skipsdist = true
[testenv]
deps =
-r requirements/dev.txt
commands =
pytest
\end{verbatim}
\end{listing}
Démarrez ensuite la commande \texttt{tox}, pour démarrer la commande \texttt{pytest} sur les environnements Python 3.6, 3.7, 3.8 et 3.9, après avoir installé nos dépendances présentes dans le fichier \texttt{requirements/dev.txt}.