Relecture du fichier python

This commit is contained in:
Gregory Trullemans 2023-04-22 15:14:11 +02:00
parent 63f2cf23f4
commit 8d7a61aa11
1 changed files with 276 additions and 359 deletions

View File

@ -3,54 +3,45 @@
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. 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.
\begin{figure}[!ht] \begin{figure}[!ht]
\centering \centering
\scalebox{.8}{\includegraphics[max size={\textwidth}{\textheight}]{images/xkcd-353-python.png}} \scalebox{.8}{\includegraphics[max size={\textwidth}{\textheight}]{images/xkcd-353-python.png}}
\caption{\url{https://xkcd.com/353/}} \caption{\url{https://xkcd.com/353/}}
\end{figure} \end{figure}
A première vue, et suivants les autres langages que vous connaitriez ou auriez déjà abordé, certains concepts restent difficiles à aborder: l'indentation définit l'étendue d'un bloc (classe, fonction, méthode, boucle, condition, il n'y a pas de typage fort des variables et 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). A première vue, et suivants les autres langages que vous connaitriez ou auriez déjà abordé, certains concepts restent difficiles à aborder : l'indentation définit l'étendue d'un bloc (classe, fonction, méthode, boucle, condition, il n'y a pas de typage fort des variables et 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).
Et malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et de pouvoir se reposer sur un écosystème stable et fonctionnel. Et malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et de pouvoir se reposer sur un écosystème stable et fonctionnel.
Il fonctionne avec un système d'améliorations basées sur des propositions: les PEP, ou "\textbf{Python Enhancement Proposal}\index{PEP}". Il fonctionne 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}}. 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}}: Le langage Python utilise un typage dynamique appelé \href{https://fr.wikipedia.org/wiki/Duck_typing}{\textbf{duck typing}} :
\begin{quote} \begin{quote}
"\emph{When I see a bird that quacks like a duck, walks like a duck, has "\emph{When I see a bird that quacks like a duck, walks like a duck, has feathers and webbed feet and associates with ducks --- I'm certainly going to assume that he is a duck.}"
feathers and webbed feet and associates with ducks --- I'm certainly
going to assume that he is a duck}"
-- Source: \href{http://en.wikipedia.org/wiki/Duck_test}{Wikipedia}. -- Source: \href{http://en.wikipedia.org/wiki/Duck_test}{Wikipedia}.
\end{quote} \end{quote}
En fonction de votre niveau d'apprentissage du langage, plusieurs En fonction de votre niveau d'apprentissage du langage, plusieurs ressources pourraient vous aider :
ressources pourraient vous aider:
\begin{itemize} \begin{itemize}
\item \item
\textbf{Pour les débutants}, \textbf{Pour les débutants},
\href{https://automatetheboringstuff.com/}{Automate the Boring Stuff \href{https://automatetheboringstuff.com/}{Automate the Boring Stuff
with Python} \cite{boring_stuff}, aka. \emph{Practical with Python} \cite{boring_stuff}, aka. \emph{Practical
Programming for Total Beginners} Programming for Total Beginners}
\item \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, ... \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. A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
\end{itemize} \end{itemize}
En parallèle, si vous avez besoin d'un aide-mémoire ou d'une liste 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}.
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}.
\section{Protocoles de langage} \section{Protocoles de langage}
Le modèle de données du langage spécifie un ensemble de méthodes qui Le modèle de données du langage spécifie un ensemble de méthodes qui peuvent être surchargées.
peuvent être surchargées. Ces méthodes suivent une convention de nommage 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}".
et leur nom est toujours encadré par un double tiret souligné; d'où leur 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.
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.
\begin{listing}[!ht] \begin{listing}[!ht]
\begin{minted}{python} \begin{minted}{python}
@ -60,21 +51,21 @@ permet de surcharger l'initialisation d'une instance de classe.
\end{minted} \end{minted}
\end{listing} \end{listing}
Ces méthodes, utilisées seules ou selon des combinaisons spécifiques, constituent les \emph{protocoles de langage}. Ces méthodes, utilisées seules ou selon des combinaisons spécifiques, constituent les \emph{protocoles de langage}.
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}. 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. 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. 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: Vous trouverez ci-dessous un tableau reprenant les protocoles les plus courants :
\begin{center} \begin{center}
\begin{tabular}{ c c c } \begin{tabular}{ c c c }
cell1 & cell2 & cell3 \\ cell1 & cell2 & cell3 \\
cell4 & cell5 & cell6 \\ cell4 & cell5 & cell6 \\
cell7 & cell8 & cell9 cell7 & cell8 & cell9
\end{tabular} \end{tabular}
\end{center} \end{center}
Les points principaux à présenter ci-dessus: Les points principaux à présenter ci-dessus:
@ -83,10 +74,10 @@ Les points principaux à présenter ci-dessus:
\item item() \item item()
\item getitem() - pour utiliser les [] \item getitem() - pour utiliser les []
\item len() - pour connaître la longueur d'un objet \item len() - pour connaître la longueur d'un objet
\item ... \item \ldots
\end{enumerate} \end{enumerate}
Le langage autorise nativement plus d'une cinquantaine d'opérateurs différents: Le langage autorise nativement plus d'une cinquantaine d'opérateurs différents :
\begin{enumerate} \begin{enumerate}
\item Opérateurs arithmétiques \item Opérateurs arithmétiques
@ -94,7 +85,7 @@ Le langage autorise nativement plus d'une cinquantaine d'opérateurs différents
\item Opérateurs de comparaisons \item Opérateurs de comparaisons
\item Opérateurs d'identité \item Opérateurs d'identité
\item Opérateurs de comparaison bit à bit \item Opérateurs de comparaison bit à bit
\item ... \item \ldots
\end{enumerate} \end{enumerate}
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{*}. 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{*}.
@ -103,15 +94,15 @@ L'exemple ci-dessous implémente la soustraction de deux matrices:
\begin{listing} \begin{listing}
\begin{minted}[tabsize=4]{python} \begin{minted}[tabsize=4]{python}
def __sub__(self, other): def __sub__(self, other):
if (len(self.rows) != len(other.rows) if (len(self.rows) != len(other.rows)
or len(self.rows[0]) != len(other.rows[0]) or len(self.rows[0]) != len(other.rows[0])
): ):
raise ValueError("Matrix dimensions don't match") raise ValueError("Matrix dimensions don't match")
return Matrix( return Matrix(
[ [
[a - b for a, b in zip(a_row, b_row)] [a - b for a, b in zip(a_row, b_row)]
for a_row, b_row in zip(self.rows, other.rows) for a_row, b_row in zip(self.rows, other.rows)
] ]
) )
\end{minted} \end{minted}
@ -119,62 +110,62 @@ 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. 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. 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.
Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-underscores methods}) pour étoffer les protocoles du langage. Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-underscores methods}) pour étoffer les protocoles du langage.
\section{The Zen of Python} \section{The Zen of Python}
\begin{listing}[H] \begin{listing}[H]
\begin{verbatim} \begin{verbatim}
>>> import this >>> import this
The Zen of Python, by Tim Peters The Zen of Python, by Tim Peters
Beautiful is better than ugly. Beautiful is better than ugly.
Explicit is better than implicit. Explicit is better than implicit.
Simple is better than complex. Simple is better than complex.
Complex is better than complicated. Complex is better than complicated.
Flat is better than nested. Flat is better than nested.
Sparse is better than dense. Sparse is better than dense.
Readability counts. Readability counts.
Special cases aren't special enough to break the rules. Special cases aren't special enough to break the rules.
Although practicality beats purity. Although practicality beats purity.
Errors should never pass silently. Errors should never pass silently.
Unless explicitly silenced. Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess. In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it. There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch. Although that way may not be obvious at first unless you're Dutch.
Now is better than never. Now is better than never.
Although never is often better than *right* now. Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea. If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea. If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those! Namespaces are one honking great idea -- let's do more of those!
\end{verbatim} \end{verbatim}
\caption{The Zen of Python} \caption{The Zen of Python}
\end{listing} \end{listing}
\section{Guide de style} \section{Guide de style}
La première PEP qui va nous intéresser est la PEP8. La première PEP qui va nous intéresser est la PEP8.
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, ... 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é. 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é.
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. 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.
Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, ...). Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, \ldots).
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 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).
\begin{listing}[!ht] \begin{listing}[!ht]
\begin{verbatim} \begin{verbatim}
$ flake8 . --statistics -qq $ flake8 . --statistics -qq
7 E101 indentation contains mixed spaces and tabs 7 E101 indentation contains mixed spaces and tabs
6 E122 continuation line missing indentation or outdented 6 E122 continuation line missing indentation or outdented
8 E127 continuation line over-indented for visual indent 8 E127 continuation line over-indented for visual indent
23 E128 continuation line under-indented for visual indent 23 E128 continuation line under-indented for visual indent
3 E131 continuation line unaligned for hanging indent 3 E131 continuation line unaligned for hanging indent
12 E201 whitespace after '{' 12 E201 whitespace after '{'
13 E202 whitespace before '}' 13 E202 whitespace before '}'
86 E203 whitespace before ':' 86 E203 whitespace before ':'
\end{verbatim} \end{verbatim}
\caption{Une utilisation de flake8} \caption{Une utilisation de flake8}
\end{listing} \end{listing}
@ -182,31 +173,31 @@ Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous v
\begin{listing}[!ht] \begin{listing}[!ht]
\begin{verbatim} \begin{verbatim}
$ pyflakes . $ pyflakes .
... ...
\end{verbatim} \end{verbatim}
\caption{Une utilisation de pyflakes} \caption{Une utilisation de pyflakes}
\end{listing} \end{listing}
A noter qu'un greffon pour \texttt{flake8} existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes. A noter qu'un greffon pour \texttt{flake8} existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes.
Installez-le avec \texttt{pip\ install\ mccabe}, et activez-le avec le paramètre \texttt{-\/-max-complexity}. Installez-le avec \texttt{pip\ install\ mccabe}, et activez-le avec le paramètre \texttt{-\/-max-complexity}.
Toute fonction dans la complexité est supérieure à cette valeur sera considérée comme trop complexe. Toute fonction dans la complexité est supérieure à cette valeur sera considérée comme trop complexe.
\section{Conventions de documentation} \section{Conventions de documentation}
Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code. Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code.
Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne. Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne.
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, ... ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter": Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, \ldots~ ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
\begin{itemize} \begin{itemize}
\item \item
Inutile d'ajouter des watermarks, auteurs, ... Inutile d'ajouter des watermarks, auteurs, \ldots
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, 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,
\item \item
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt
\item \item
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 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
\end{itemize} \end{itemize}
Documentation: be obsessed! Mais \textbf{le code reste la référence} Documentation: be obsessed! Mais \textbf{le code reste la référence}
@ -214,27 +205,27 @@ Documentation: be obsessed! Mais \textbf{le code reste la référence}
Il existe plusieurs types de balisages reconnus/approuvés: Il existe plusieurs types de balisages reconnus/approuvés:
\begin{enumerate} \begin{enumerate}
\item RestructuredText \item RestructuredText
\item Numpy \item Numpy
\item Google Style (parfois connue sous l'intitulé \texttt{Napoleon}) \item Google Style (parfois connue sous l'intitulé \texttt{Napoleon})
\end{enumerate} \end{enumerate}
... mais tout système de balisage peut être reconnu, sous réseve de respecter la structure de la PEP257. \ldots~ mais tout système de balisage peut être reconnu, sous réseve de respecter la structure de la PEP257.
\subsection{PEP 257} \subsection{PEP 257}
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. 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. 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é Son objectif est d'arriver à une forme de cohérence lorsqu'un utilisateur souhaitera accéder à la propriété
Elle contient des conventions, pas des règles ou Elle contient des conventions, pas des règles ou
\begin{quote} \begin{quote}
“A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesnt do is insist that you follow it against your will. Thats Python!” “A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesnt do is insist that you follow it against your will. Thats Python!”
-- Tim Peters on comp.lang.python, 2001-06-16 -- Tim Peters on comp.lang.python, 2001-06-16
\end{quote} \end{quote}
Ainsi, les conventions sont décrites; chaque format propose ensuite son propre balisage (ReStructuredText, Numpy, Napoleon, ...). Ainsi, les conventions sont décrites ; chaque format propose ensuite son propre balisage (ReStructuredText, Numpy, Napoleon, \ldots).
A priori, vous pourriez tout à fait utiliser le vôtre, sous réserve que les conventions de la PEP-257 soient respectées. A priori, vous pourriez tout à fait utiliser le vôtre, sous réserve que les conventions de la PEP-257 soient respectées.
\subsection{RestructuredText} \subsection{RestructuredText}
@ -249,7 +240,7 @@ A remplir
\subsection{Napoleon} \subsection{Napoleon}
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). 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).
L'exemple donné dans les guides de style de Google est celui-ci: L'exemple donné dans les guides de style de Google est celui-ci:
@ -257,30 +248,30 @@ L'exemple donné dans les guides de style de Google est celui-ci:
\begin{minted}{python} \begin{minted}{python}
def fetch_smalltable_rows(table_handle: smalltable.Table, keys: Sequence[Union[bytes, str]], require_all_keys: bool = False,) -> Mapping[bytes, Tuple[str]]: def fetch_smalltable_rows(table_handle: smalltable.Table, keys: Sequence[Union[bytes, str]], require_all_keys: bool = False,) -> Mapping[bytes, Tuple[str]]:
"""Fetches rows from a Smalltable. """Fetches rows from a Smalltable.
Retrieves rows pertaining to the given keys from the Table instance Retrieves rows pertaining to the given keys from the Table instance
represented by table_handle. String keys will be UTF-8 encoded. represented by table_handle. String keys will be UTF-8 encoded.
Args: Args:
table_handle: An open smalltable.Table instance. table_handle: An open smalltable.Table instance.
keys: A sequence of strings representing the key of each table keys: A sequence of strings representing the key of each table
row to fetch. String keys will be UTF-8 encoded. row to fetch. String keys will be UTF-8 encoded.
require_all_keys: Optional; If require_all_keys is True only require_all_keys: Optional; If require_all_keys is True only
rows with values set for all keys will be returned. rows with values set for all keys will be returned.
Returns: Returns:
A dict mapping keys to the corresponding table row data A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For fetched. Each row is represented as a tuple of strings. For
example: example:
{b'Serak': ('Rigel VII', 'Preparer'), {b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'), b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')} b'Lrrr': ('Omicron Persei 8', 'Emperor')}
Returned keys are always bytes. If a key from the keys argument is Returned keys are always bytes. If a key from the keys argument is
missing from the dictionary, then that row was not found in the missing from the dictionary, then that row was not found in the
table (and require_all_keys must have been False). table (and require_all_keys must have been False).
Raises: Raises:
IOError: An error occurred accessing the smalltable. IOError: An error occurred accessing the smalltable.
""" """
@ -289,42 +280,27 @@ L'exemple donné dans les guides de style de Google est celui-ci:
\end{listing} \end{listing}
C'est-à-dire: C'est-à-dire :
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Une courte ligne d'introduction, descriptive, indiquant ce que la Une courte ligne d'introduction, descriptive, indiquant ce que la fonction ou la méthode réalise. Attention, la documentation ne doit pas indiquer \emph{comment} la fonction/méthode est implémentée, mais ce qu'elle fait concrètement (et succintement).
fonction ou la méthode réalise. Attention, la documentation ne doit \item Une ligne vide
pas indiquer \emph{comment} la fonction/méthode est implémentée, mais \item Une description plus complète et plus verbeuse, si vous le jugez nécessaire
ce qu'elle fait concrètement (et succintement). \item Une ligne vide
\item \item La description des arguments et paramètres, des valeurs de retour, des exemples et les exceptions qui peuvent être levées.
Une ligne vide
\item
Une description plus complète et plus verbeuse, si vous le jugez
nécessaire
\item
Une ligne vide
\item
La description des arguments et paramètres, des valeurs de retour, des
exemples et les exceptions qui peuvent être levées.
\end{enumerate} \end{enumerate}
Un exemple (encore) plus complet peut être trouvé Un exemple (encore) plus complet peut être trouvé \href{https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html\#example-google}{dans le dépôt sphinxcontrib-napoleon}. Et ici, nous tombons peut-être dans l'excès de zèle :
\href{https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html\#example-google}{dans
le dépôt sphinxcontrib-napoleon}. Et ici, nous tombons peut-être dans
l'excès de zèle:
\begin{figure}[!ht] \begin{figure}[!ht]
\centering \centering
\scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/napoleon-module-level-docstring.png}} \scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/napoleon-module-level-docstring.png}}
\caption{\url{https://xkcd.com/353/}} \caption{\url{https://xkcd.com/353/}}
\end{figure} \end{figure}
Pour ceux que cela pourrait intéresser, il existe Pour ceux que cela pourrait intéresser, il existe \href{https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring}{une extension pour Codium}, comme nous le verrons juste après, qui permet de générer automatiquement le squelette de documentation d'un bloc de code : %(TODO : il manque pas un truc ici ??)
\href{https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring}{une
extension pour Codium}, comme nous le verrons juste après, qui permet de
générer automatiquement le squelette de documentation d'un bloc de code:
Nous le verrons plus loin, Django permet de rendre la documentation immédiatement accessible depuis l'interface d'administration. Nous le verrons plus loin, Django permet de rendre la documentation immédiatement accessible depuis l'interface d'administration.
Toute information pertinente peut donc lier le code à un cas d'utilisation concret, et rien n'est jamais réellement perdu. Toute information pertinente peut donc lier le code à un cas d'utilisation concret, et rien n'est jamais réellement perdu.
@ -335,49 +311,32 @@ Il existe plusieurs niveaux de \emph{linters}:
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Le premier niveau concerne Le premier niveau concerne \href{https://pypi.org/project/pycodestyle/}{pycodestyle} (anciennement, \texttt{pep8} justement\ldots\hspace{0pt}), qui analyse votre code à la recherche d'erreurs de convention.
\href{https://pypi.org/project/pycodestyle/}{pycodestyle} \item
(anciennement, \texttt{pep8} justement\ldots\hspace{0pt}), qui analyse Le deuxième niveau concerne \href{https://pypi.org/project/pyflakes/}{pyflakes}. Pyflakes est un \emph{simple} \footnote{Ce n'est pas moi qui le dit, c'est la doc du projet} programme qui recherchera des erreurs parmi vos fichiers Python.
votre code à la recherche d'erreurs de convention. \item
\item Le troisième niveau est \href{https://pypi.org/project/flake8/}{Flake8}, qui regroupe les deux premiers niveaux, en plus d'y ajouter flexibilité, extensions et une analyse de complexité de McCabe.
Le deuxième niveau concerne \item
\href{https://pypi.org/project/pyflakes/}{pyflakes}. Pyflakes est un Le quatrième niveau \footnote{Oui, en Python, il n'y a que quatre cercles à l'Enfer} est \href{https://pylint.org/}{PyLint}.
\emph{simple} \footnote{Ce n'est pas moi qui le dit, c'est la doc du
projet} programme qui recherchera des erreurs parmi vos fichiers
Python.
\item
Le troisième niveau est
\href{https://pypi.org/project/flake8/}{Flake8}, qui regroupe les deux
premiers niveaux, en plus d'y ajouter flexibilité, extensions et une
analyse de complexité de McCabe.
\item
Le quatrième niveau \footnote{Oui, en Python, il n'y a que quatre
cercles à l'Enfer} est \href{https://pylint.org/}{PyLint}.
\end{enumerate} \end{enumerate}
PyLint est le meilleur ami de votre \emph{moi} futur, un peu comme quand 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.
vous prenez le temps de faire la vaisselle pour ne pas avoir à la faire 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.
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.
Pour vous donner une idée, voici ce que cela pourrait donner avec un Pour vous donner une idée, voici ce que cela pourrait donner avec un code pas très propre et qui ne sert à rien :
code pas très propre et qui ne sert à rien:
\begin{listing}[!ht] \begin{listing}[!ht]
\begin{minted}{python} \begin{minted}{python}
from datetime import datetime from datetime import datetime
"""On stocke la date du jour dans la variable ToD4y""" """On stocke la date du jour dans la variable ToD4y"""
ToD4y = datetime.today() ToD4y = datetime.today()
def print_today(ToD4y): def print_today(ToD4y):
today = ToD4y today = ToD4y
print(ToD4y) print(ToD4y)
def GetToday(): def GetToday():
return ToD4y return ToD4y
@ -392,70 +351,57 @@ Avec Flake8, nous obtiendrons ceci:
\begin{listing}[!ht] \begin{listing}[!ht]
\begin{verbatim} \begin{verbatim}
test.py:7:1: E302 expected 2 blank lines, found 1 test.py:7:1: E302 expected 2 blank lines, found 1
test.py:8:5: F841 local variable 'today' is assigned to but never used test.py:8:5: F841 local variable 'today' is assigned to but never used
test.py:11:1: E302 expected 2 blank lines, found 1 test.py:11:1: E302 expected 2 blank lines, found 1
test.py:16:8: E222 multiple spaces after operator test.py:16:8: E222 multiple spaces after operator
test.py:16:11: F821 undefined name 'Get_Today' test.py:16:11: F821 undefined name 'Get_Today'
test.py:18:1: W391 blank line at end of file test.py:18:1: W391 blank line at end of file
\end{verbatim} \end{verbatim}
\end{listing} \end{listing}
Nous trouvons des erreurs: Nous trouvons des erreurs:
\begin{itemize} \begin{itemize}
\item \item
de \textbf{conventions}: le nombre de lignes qui séparent deux de \textbf{conventions} : le nombre de lignes qui séparent deux fonctions, le nombre d'espace après un opérateur, une ligne vide à la fin du fichier, \ldots\hspace{0pt} Ces \emph{erreurs} n'en sont pas vraiment, elles indiquent juste de potentiels problèmes de communication si le code devait être lu ou compris par une autre personne.
fonctions, le nombre d'espace après un opérateur, une ligne vide à la \item
fin du fichier, \ldots\hspace{0pt} Ces \emph{erreurs} n'en sont pas de \textbf{définition} : une variable assignée mais pas utilisée ou une lexème non trouvé. Cette dernière information indique clairement un bug potentiel. Ne pas en tenir compte nuira sans doute à la santé de votre code (et risque de vous réveiller à cinq heures du mat', quand votre application se prendra méchamment les pieds dans le tapis).
vraiment, elles indiquent juste de potentiels problèmes de
communication si le code devait être lu ou compris par une autre
personne.
\item
de \textbf{définition}: une variable assignée mais pas utilisée ou une
lexème non trouvé. Cette dernière information indique clairement un
bug potentiel. Ne pas en tenir compte nuira sans doute à la santé de
votre code (et risque de vous réveiller à cinq heures du mat', quand
votre application se prendra méchamment les pieds dans le tapis).
\end{itemize} \end{itemize}
L'étape d'après consiste à invoquer pylint. Lui, il est directement moins conciliant: L'étape d'après consiste à invoquer pylint. Lui, il est directement moins conciliantc:
\begin{verbatim} \begin{verbatim}
$ pylint test.py $ pylint test.py
************* Module test ************* Module test
test.py:16:6: C0326: Exactly one space required after assignment test.py:16:6: C0326: Exactly one space required after assignment
t = Get_Today() t = Get_Today()
^ (bad-whitespace) ^ (bad-whitespace)
test.py:18:0: C0305: Trailing newlines (trailing-newlines) test.py:18:0: C0305: Trailing newlines (trailing-newlines)
test.py:1:0: C0114: Missing module docstring (missing-module-docstring) test.py:1:0: C0114: Missing module docstring (missing-module-docstring)
test.py:3:0: W0105: String statement has no effect (pointless-string-statement) test.py:3:0: W0105: String statement has no effect (pointless-string-statement)
test.py:5:0: C0103: Constant name "ToD4y" doesn't conform to UPPER_CASE naming style (invalid-name) test.py:5:0: C0103: Constant name "ToD4y" doesn't conform to UPPER_CASE naming style (invalid-name)
test.py:7:16: W0621: Redefining name 'ToD4y' from outer scope (line 5) (redefined-outer-name) test.py:7:16: W0621: Redefining name 'ToD4y' from outer scope (line 5) (redefined-outer-name)
test.py:7:0: C0103: Argument name "ToD4y" doesn't conform to snake_case naming style (invalid-name) test.py:7:0: C0103: Argument name "ToD4y" doesn't conform to snake_case naming style (invalid-name)
test.py:7:0: C0116: Missing function or method docstring (missing-function-docstring) test.py:7:0: C0116: Missing function or method docstring (missing-function-docstring)
test.py:8:4: W0612: Unused variable 'today' (unused-variable) test.py:8:4: W0612: Unused variable 'today' (unused-variable)
test.py:11:0: C0103: Function name "GetToday" doesn't conform to snake_case naming style (invalid-name) test.py:11:0: C0103: Function name "GetToday" doesn't conform to snake_case naming style (invalid-name)
test.py:11:0: C0116: Missing function or method docstring (missing-function-docstring) test.py:11:0: C0116: Missing function or method docstring (missing-function-docstring)
test.py:16:4: C0103: Constant name "t" doesn't conform to UPPER_CASE naming style (invalid-name) test.py:16:4: C0103: Constant name "t" doesn't conform to UPPER_CASE naming style (invalid-name)
test.py:16:10: E0602: Undefined variable 'Get_Today' (undefined-variable) test.py:16:10: E0602: Undefined variable 'Get_Today' (undefined-variable)
-------------------------------------------------------------------- --------------------------------------------------------------------
Your code has been rated at -5.45/10 Your code has been rated at -5.45/10
\end{verbatim} \end{verbatim}
En gros, j'ai programmé comme une grosse bouse anémique (et oui: le score d'évaluation du code permet d'aller en négatif). En gros, j'ai programmé comme une grosse bouse anémique (et oui: le score d'évaluation du code permet d'aller en négatif).
En vrac, nous trouvons des problèmes liés: En vrac, nous trouvons des problèmes liés:
\begin{itemize} \begin{itemize}
\item \item au nommage (C0103) et à la mise en forme (C0305, C0326, W0105)
au nommage (C0103) et à la mise en forme (C0305, C0326, W0105) \item à des variables non définies (E0602)
\item \item de la documentation manquante (C0114, C0116)
à des variables non définies (E0602) \item de la redéfinition de variables (W0621).
\item
de la documentation manquante (C0114, C0116)
\item
de la redéfinition de variables (W0621).
\end{itemize} \end{itemize}
Pour reprendre la Pour reprendre la
@ -463,28 +409,23 @@ Pour reprendre la
chaque code possède sa signification: chaque code possède sa signification:
\begin{itemize} \begin{itemize}
\item \item \textbf{C}, pour toutes les vérifications liées aux conventions (nommage et mises en forme, que l'on a vues ci-dessus)
\textbf{C}, pour toutes les vérifications liées aux conventions (nommage et mises en forme, que l'on a vues ci-dessus) \item \textbf{R}, pour des propositions de refactoring
\item \item \textbf{W}, pour tout ce qui est en lien avec des avertissements
\textbf{R}, pour des propositions de refactoring \item \textbf{E} pour les erreurs ou des bugs probablement présents dans le code
\item \item \textbf{F} pour les erreurs internes au fonctionnement de pylint, qui font que le traitement n'a pas pu aboutir.
\textbf{W}, pour tout ce qui est en lien avec des avertissements
\item
\textbf{E} pour les erreurs ou des bugs probablement présents dans le code
\item
\textbf{F} pour les erreurs internes au fonctionnement de pylint, qui font que le traitement n'a pas pu aboutir.
\end{itemize} \end{itemize}
Connaissant ceci, il est extrêmement pratique d'intégrer pylint au niveau des processus d'intégration continue, puisque la présence d'une Connaissant ceci, il est extrêmement pratique d'intégrer pylint au niveau des processus d'intégration continue, puisque la présence d'une % TODO: ICI je pense qu'il manque quelque chose... Il semblerait que la conversion de ??? -> TeX ne se soit pas hyper bien passée.
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}. 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. 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.
Cet élément peut être: Cet élément peut être :
\begin{enumerate} \begin{enumerate}
\item \textbf{Une ligne de code} \item \textbf{Une ligne de code}
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, ... \item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, \ldots
\item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient \item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
\end{enumerate} \end{enumerate}
\subsection{Ignorer une ligne de code} \subsection{Ignorer une ligne de code}
@ -496,10 +437,10 @@ Cet élément peut être:
\section{Formatage de code} \section{Formatage de code}
Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, ... Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, \ldots
Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue. Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue.
Heureusement, il existe plusieurs outils pour nous aider au niveau du formatage automatique. Heureusement, il existe plusieurs outils pour nous aider au niveau du formatage automatique.
Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.io/en/stable/}{Black} arrive à un très bon compromis entre Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.io/en/stable/}{Black} arrive à un très bon compromis entre :
\begin{itemize} \begin{itemize}
\item Clarté du code \item Clarté du code
@ -508,47 +449,41 @@ Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.i
\end{itemize} \end{itemize}
Est-ce que ce formatage est idéal et accepté par tout le monde ? Est-ce que ce formatage est idéal et accepté par tout le monde ?
\textbf{Non}. \textbf{Non}.
Même Pylint arrivera parfois à râler. Même Pylint arrivera parfois à râler.
Mais ce formatage conviendra dans 97,83\% des cas (au moins). Mais ce formatage conviendra dans 97,83\% des cas (au moins).
\begin{quote} \begin{quote}
By using Black, you agree to cede control over minutiae of By using Black, you agree to cede control over minutiae of hand-formatting.
hand-formatting. In return, Black gives you speed, determinism, and In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting.
freedom from pycodestyle nagging about formatting. You will save time You will save time and mental energy for more important matters.
and mental energy for more important matters.
Black makes code review faster by producing the smallest diffs possible. Black makes code review faster by producing the smallest diffs possible.
Blackened code looks the same regardless of the project you're reading. Blackened code looks the same regardless of the project you're reading.
Formatting becomes transparent after a while and you can focus on the Formatting becomes transparent after a while and you can focus on the content instead.
content instead.
\end{quote} \end{quote}
Traduit rapidement à partir de la langue de Batman: "\texttt{En utilisant Traduit rapidement à partir de la langue de Batman: "\texttt{En utilisant Black, vous cédez le contrôle sur le formatage de votre code.
Black, vous cédez le contrôle sur le formatage de votre code. En retour, En retour, Black vous fera gagner un max de temps, diminuera votre charge mentale et fera revenir l'être aimé}".
Black vous fera gagner un max de temps, diminuera votre charge mentale Mais la partie réellement intéressante concerne le fait que "\texttt{Tout code qui sera passé par Black aura la même forme, indépendamment du project sur lequel vous serez en train de travailler.
et fera revenir l'être aimé}". Mais la partie réellement intéressante L'étape de formatage deviendra transparente, et vous pourrez vous concentrer sur le contenu}".
concerne le fait que "\texttt{Tout code qui sera passé par Black aura la
même forme, indépendamment du project sur lequel vous serez en train de
travailler. L'étape de formatage deviendra transparente, et vous pourrez
vous concentrer sur le contenu}".
\section{Typage statique \index{PEP585}} \section{Typage statique \index{PEP585}}
Nous vous disions ci-dessus que Python est un langage dynamique interprété. Nous vous disions ci-dessus que Python est un langage dynamique interprété.
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. 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. 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: Ces vérifications se présentent de la manière suivante :
\begin{listing}[H] \begin{listing}[H]
\begin{minted}{python} \begin{minted}{python}
from typing import List from typing import List
def first_int_elem(l: List[int]) -> int: def first_int_elem(l: List[int]) -> int:
return l[0] if l else None return l[0] if l else None
if __name__ == "__main__": if __name__ == "__main__":
print(first_int_elem([1, 2, 3])) print(first_int_elem([1, 2, 3]))
print(first_int_elem(['a', 'b', 'c'])) print(first_int_elem(['a', 'b', 'c']))
@ -556,66 +491,53 @@ Ces vérifications se présentent de la manière suivante:
\end{minted} \end{minted}
\end{listing} \end{listing}
Est-ce que le code ci-dessous fonctionne correctement ? \textbf{Oui}: Est-ce que le code ci-dessous fonctionne correctement ? \textbf{Oui} :
\begin{listing}[H] \begin{listing}[H]
\begin{verbatim} \begin{verbatim}
>>> python mypy-test.py >>> python mypy-test.py
1 1
a a
\end{verbatim} \end{verbatim}
\end{listing} \end{listing}
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. 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. La signature de notre fonction n'est donc pas cohérente avec son comportement.
Est-ce que Mypy va râler ? \textbf{Oui, aussi}. Est-ce que Mypy va râler ? \textbf{Oui, aussi}.
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: 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{listing}[H]
\begin{verbatim} \begin{verbatim}
>>> mypy mypy-test.py >>> mypy mypy-test.py
mypy-test.py:7: error: Incompatible return value type (got "Optional[int]", expected "int") 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 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 1 has incompatible type "str"; expected "int"
mypy-test.py:12: error: List item 2 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) Found 4 errors in 1 file (checked 1 source file)
\end{verbatim} \end{verbatim}
\end{listing} \end{listing}
Pour corriger ceci, nous devons: Pour corriger ceci, nous devons:
\begin{enumerate} \begin{enumerate}
\item \item Importer le type \texttt{Optional} et l'utiliser en sortie de notre fonction \texttt{first\_int\_elem}
Importer le type \texttt{Optional} et l'utiliser en sortie de notre \item Eviter de lui donner de mauvais paramètres ;-)²
fonction \texttt{first\_int\_elem}
\item
Eviter de lui donner de mauvais paramètres ;-)²
\end{enumerate} \end{enumerate}
\section{Tests unitaires} \section{Tests unitaires}
Comme tout bon \textbf{langage de programmation moderne} qui se respecte, Python embarque tout un environnement facilitant le lancement de tests; Comme tout bon \textbf{langage de programmation moderne} qui se respecte, Python embarque tout un environnement facilitant le lancement de tests; % TODO: je me demande si ici aussi il ne manque pas des mots...
Une bonne pratique (parfois discutée) consiste cependant à switcher vers \texttt{pytest}, qui présente quelques avantages par rapport au module \texttt{unittest}: 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} \begin{itemize}
\item \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}).
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 \item Des \emph{fixtures} faciles à réutiliser entre vos différents composants
Une compatibilité avec du code Python "classique" - vous ne devrez \item Une compatibilité avec le reste de l'écosystème, dont la couverture de code présentée ci-dessous.
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} \end{itemize}
Ainsi, après installation, il nous suffit de créer notre module 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) :
\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):
\begin{listing} \begin{listing}
\begin{minted}{Python} \begin{minted}{Python}
@ -624,8 +546,8 @@ complètement biesse; on est sur la partie théorique ici):
\end{minted} \end{minted}
\end{listing} \end{listing}
Forcément, cela va planter. Forcément, cela va planter.
Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de démarrer la commande \texttt{pytest}: Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de démarrer la commande \texttt{pytest} :
\begin{listing} \begin{listing}
\begin{verbatim} \begin{verbatim}
@ -654,7 +576,7 @@ Pour nous en assurer (dès fois que quelqu'un en doute), il nous suffit de déma
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/}. 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}. 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}.
Ajoutez-le dans le fichier \texttt{requirements/base.txt}, et lancez une couverture de code grâce à la commande \texttt{coverage}. Ajoutez-le dans le fichier \texttt{requirements/base.txt}, et lancez une couverture de code grâce à la commande \texttt{coverage}.
La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous placerez à la racine de votre projet, et qui sera lu lors de l'exécution. La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous placerez à la racine de votre projet, et qui sera lu lors de l'exécution.
\section{Gestion des versions de l'interpréteur} \section{Gestion des versions de l'interpréteur}
@ -665,22 +587,22 @@ La configuration peut se faire dans un fichier \texttt{.coveragerc} que vous pla
\section{Matrice de compatibilité} \section{Matrice de compatibilité}
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. 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. 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'objectif consiste à définir un tableau à deux dimensions, dans lequel nous trouverons la compatibilité entre notre application et une version de l'interpréteur.
\begin{center} \begin{center}
\begin{tabular}{|c|c|c|c|} \begin{tabular}{|c|c|c|c|}
& py37 & py38 & py39 \\ & py37 & py38 & py39 \\
\hline \hline
lib2 & V & V & V \\ lib2 & V & V & V \\
lib3 & X & V & V \\ lib3 & X & V & V \\
lib4 & X & V & V lib4 & X & V & V
\end{tabular} \end{tabular}
\end{center} \end{center}
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: 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} \begin{enumerate}
\item De vérifier que votre application s'installe correctement avec différentes versions de Python et d'interpréteurs \item De vérifier que votre application s'installe correctement avec différentes versions de Python et d'interpréteurs
@ -703,7 +625,7 @@ L'outil le plus connu est \href{https://tox.readthedocs.io/en/latest/}{Tox}, qui
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}. 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}.
Pour que la commande ci-dessus fonctionne correctement, il sera nécessaire que vous ayez les différentes versions d'interpréteurs installées. Pour que la commande ci-dessus fonctionne correctement, il sera nécessaire que vous ayez les différentes versions d'interpréteurs installées.
Ci-dessus, la commande retournera une erreur pour chaque version non trouvée, avec une erreur type Ci-dessus, la commande retournera une erreur pour chaque version non trouvée, avec une erreur type
\texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}. \texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}.
@ -716,75 +638,70 @@ Décrire le fichier setup.cfg.
\section{Makefile} \section{Makefile}
Pour gagner un peu de temps, n'hésitez pas à créer un fichier \texttt{Makefile} que vous placerez à la racine du projet. Pour gagner un peu de temps, n'hésitez pas à créer un fichier \texttt{Makefile} que vous placerez à la racine du projet.
L'exemple ci-dessous permettra, grâce à la commande \texttt{make\ coverage}, d'arriver au même résultat que ci-dessus: L'exemple ci-dessous permettra, grâce à la commande \texttt{make\ coverage}, d'arriver au même résultat que ci-dessus:
\begin{listing} \begin{listing}
\begin{verbatim} \begin{verbatim}
# Makefile for gwift # Makefile for gwift
# #
# User-friendly check for coverage # User-friendly check for coverage
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1) ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
$(error The 'coverage' command was not found. Make sure you have coverage installed) $(error The 'coverage' command was not found. Make sure you have coverage installed)
endif endif
.PHONY: help coverage .PHONY: help coverage
help: help:
@echo " coverage to run coverage check of the source files." @echo " coverage to run coverage check of the source files."
coverage: coverage:
coverage run --source='.' manage.py test; coverage report; coverage html; coverage run --source='.' manage.py test; coverage report; coverage html;
@echo "Testing of coverage in the sources finished." @echo "Testing of coverage in the sources finished."
\end{verbatim} \end{verbatim}
\caption{Un exemple de fichier Makefile} \caption{Un exemple de fichier Makefile}
\end{listing} \end{listing}
Pour la petite histoire, \texttt{make} peu sembler un peu désuet, mais Pour la petite histoire, \texttt{make} peu sembler un peu désuet, mais reste extrêmement efficace.
reste extrêmement efficace.
\section{Conclusions (et intégration continue)} \section{Conclusions (et intégration continue)}
\subsection{setup.cfg} \subsection{setup.cfg}
→ Faire le lien avec les settings → Faire le lien avec les douze facteurs → Construction du fichier setup.cfg
→ Faire le lien avec les settings → Faire le lien avec les douze
facteurs → Construction du fichier setup.cfg
\begin{verbatim} \begin{verbatim}
[flake8] [flake8]
max-line-length = 100 max-line-length = 100
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
[pycodestyle] [pycodestyle]
max-line-length = 100 max-line-length = 100
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
[mypy] [mypy]
python_version = 3.8 python_version = 3.8
check_untyped_defs = True check_untyped_defs = True
ignore_missing_imports = True ignore_missing_imports = True
warn_unused_ignores = True warn_unused_ignores = True
warn_redundant_casts = True warn_redundant_casts = True
warn_unused_configs = True warn_unused_configs = True
plugins = mypy_django_plugin.main plugins = mypy_django_plugin.main
[mypy.plugins.django-stubs] [mypy.plugins.django-stubs]
django_settings_module = config.settings.test django_settings_module = config.settings.test
[mypy-*.migrations.*] [mypy-*.migrations.*]
# Django migrations should not produce any errors: # Django migrations should not produce any errors:
ignore_errors = True ignore_errors = True
[coverage:run] [coverage:run]
include = khana/* include = khana/*
omit = *migrations*, *tests* omit = *migrations*, *tests*
plugins = plugins =
django_coverage_plugin django_coverage_plugin
\end{verbatim} \end{verbatim}
Mypy + black + pylint + flake8 + pyflakes + ... Mypy + black + pylint + flake8 + pyflakes + \ldots