Relecture du fichier python
This commit is contained in:
parent
63f2cf23f4
commit
8d7a61aa11
|
@ -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.
|
||||
|
||||
\begin{figure}[!ht]
|
||||
\centering
|
||||
\scalebox{.8}{\includegraphics[max size={\textwidth}{\textheight}]{images/xkcd-353-python.png}}
|
||||
\caption{\url{https://xkcd.com/353/}}
|
||||
\centering
|
||||
\scalebox{.8}{\includegraphics[max size={\textwidth}{\textheight}]{images/xkcd-353-python.png}}
|
||||
\caption{\url{https://xkcd.com/353/}}
|
||||
\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.
|
||||
|
||||
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}}.
|
||||
|
||||
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}
|
||||
"\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}"
|
||||
"\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.}"
|
||||
|
||||
-- Source: \href{http://en.wikipedia.org/wiki/Duck_test}{Wikipedia}.
|
||||
\end{quote}
|
||||
|
||||
En fonction de votre niveau d'apprentissage du langage, plusieurs
|
||||
ressources pourraient vous aider:
|
||||
En fonction de votre niveau d'apprentissage du langage, plusieurs ressources pourraient vous aider :
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
\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, ...
|
||||
A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
|
||||
\item
|
||||
\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.
|
||||
\end{itemize}
|
||||
|
||||
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}.
|
||||
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}.
|
||||
|
||||
\section{Protocoles de langage}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
\begin{listing}[!ht]
|
||||
\begin{minted}{python}
|
||||
|
@ -60,21 +51,21 @@ permet de surcharger l'initialisation d'une instance de classe.
|
|||
\end{minted}
|
||||
\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}.
|
||||
|
||||
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.
|
||||
|
||||
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{tabular}{ c c c }
|
||||
cell1 & cell2 & cell3 \\
|
||||
cell4 & cell5 & cell6 \\
|
||||
cell7 & cell8 & cell9
|
||||
cell1 & cell2 & cell3 \\
|
||||
cell4 & cell5 & cell6 \\
|
||||
cell7 & cell8 & cell9
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{center}
|
||||
|
||||
Les points principaux à présenter ci-dessus:
|
||||
|
||||
|
@ -83,10 +74,10 @@ Les points principaux à présenter ci-dessus:
|
|||
\item item()
|
||||
\item getitem() - pour utiliser les []
|
||||
\item len() - pour connaître la longueur d'un objet
|
||||
\item ...
|
||||
\item \ldots
|
||||
\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}
|
||||
\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 d'identité
|
||||
\item Opérateurs de comparaison bit à bit
|
||||
\item ...
|
||||
\item \ldots
|
||||
\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{*}.
|
||||
|
@ -103,15 +94,15 @@ L'exemple ci-dessous implémente la soustraction de deux matrices:
|
|||
\begin{listing}
|
||||
\begin{minted}[tabsize=4]{python}
|
||||
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])
|
||||
):
|
||||
raise ValueError("Matrix dimensions don't match")
|
||||
|
||||
return Matrix(
|
||||
[
|
||||
[a - b for a, b in zip(a_row, b_row)]
|
||||
for a_row, b_row in zip(self.rows, other.rows)
|
||||
[a - b for a, b in zip(a_row, b_row)]
|
||||
for a_row, b_row in zip(self.rows, other.rows)
|
||||
]
|
||||
)
|
||||
\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.
|
||||
|
||||
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.
|
||||
|
||||
\section{The Zen of Python}
|
||||
|
||||
\begin{listing}[H]
|
||||
\begin{verbatim}
|
||||
>>> import this
|
||||
The Zen of Python, by Tim Peters
|
||||
\begin{verbatim}
|
||||
>>> import this
|
||||
The Zen of Python, by Tim Peters
|
||||
|
||||
Beautiful is better than ugly.
|
||||
Explicit is better than implicit.
|
||||
Simple is better than complex.
|
||||
Complex is better than complicated.
|
||||
Flat is better than nested.
|
||||
Sparse is better than dense.
|
||||
Readability counts.
|
||||
Special cases aren't special enough to break the rules.
|
||||
Although practicality beats purity.
|
||||
Errors should never pass silently.
|
||||
Unless explicitly silenced.
|
||||
In the face of ambiguity, refuse the temptation to guess.
|
||||
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.
|
||||
Now is better than never.
|
||||
Although never is often better than *right* now.
|
||||
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.
|
||||
Namespaces are one honking great idea -- let's do more of those!
|
||||
|
||||
\end{verbatim}
|
||||
Beautiful is better than ugly.
|
||||
Explicit is better than implicit.
|
||||
Simple is better than complex.
|
||||
Complex is better than complicated.
|
||||
Flat is better than nested.
|
||||
Sparse is better than dense.
|
||||
Readability counts.
|
||||
Special cases aren't special enough to break the rules.
|
||||
Although practicality beats purity.
|
||||
Errors should never pass silently.
|
||||
Unless explicitly silenced.
|
||||
In the face of ambiguity, refuse the temptation to guess.
|
||||
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.
|
||||
Now is better than never.
|
||||
Although never is often better than *right* now.
|
||||
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.
|
||||
Namespaces are one honking great idea -- let's do more of those!
|
||||
|
||||
\end{verbatim}
|
||||
\caption{The Zen of Python}
|
||||
\end{listing}
|
||||
|
||||
\section{Guide de style}
|
||||
|
||||
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, ...
|
||||
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, \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é.
|
||||
|
||||
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}, ...).
|
||||
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}, \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).
|
||||
|
||||
\begin{listing}[!ht]
|
||||
\begin{verbatim}
|
||||
$ flake8 . --statistics -qq
|
||||
7 E101 indentation contains mixed spaces and tabs
|
||||
6 E122 continuation line missing indentation or outdented
|
||||
8 E127 continuation line over-indented for visual indent
|
||||
23 E128 continuation line under-indented for visual indent
|
||||
3 E131 continuation line unaligned for hanging indent
|
||||
12 E201 whitespace after '{'
|
||||
13 E202 whitespace before '}'
|
||||
86 E203 whitespace before ':'
|
||||
\end{verbatim}
|
||||
\begin{verbatim}
|
||||
$ flake8 . --statistics -qq
|
||||
7 E101 indentation contains mixed spaces and tabs
|
||||
6 E122 continuation line missing indentation or outdented
|
||||
8 E127 continuation line over-indented for visual indent
|
||||
23 E128 continuation line under-indented for visual indent
|
||||
3 E131 continuation line unaligned for hanging indent
|
||||
12 E201 whitespace after '{'
|
||||
13 E202 whitespace before '}'
|
||||
86 E203 whitespace before ':'
|
||||
\end{verbatim}
|
||||
\caption{Une utilisation de flake8}
|
||||
\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{verbatim}
|
||||
$ pyflakes .
|
||||
...
|
||||
$ pyflakes .
|
||||
...
|
||||
\end{verbatim}
|
||||
\caption{Une utilisation de pyflakes}
|
||||
\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.
|
||||
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.
|
||||
|
||||
\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.
|
||||
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":
|
||||
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 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}
|
||||
\item
|
||||
Inutile d'ajouter des watermarks, auteurs, ...
|
||||
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
|
||||
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
|
||||
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
|
||||
\item
|
||||
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,
|
||||
\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
|
||||
\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
|
||||
\end{itemize}
|
||||
|
||||
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:
|
||||
|
||||
\begin{enumerate}
|
||||
\item RestructuredText
|
||||
\item Numpy
|
||||
\item Google Style (parfois connue sous l'intitulé \texttt{Napoleon})
|
||||
\item RestructuredText
|
||||
\item Numpy
|
||||
\item Google Style (parfois connue sous l'intitulé \texttt{Napoleon})
|
||||
\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}
|
||||
|
||||
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é
|
||||
Elle contient des conventions, pas des règles ou
|
||||
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é
|
||||
Elle contient des conventions, pas des règles ou
|
||||
|
||||
\begin{quote}
|
||||
“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!”
|
||||
“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!”
|
||||
|
||||
-- Tim Peters on comp.lang.python, 2001-06-16
|
||||
-- Tim Peters on comp.lang.python, 2001-06-16
|
||||
\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.
|
||||
|
||||
\subsection{RestructuredText}
|
||||
|
@ -249,7 +240,7 @@ A remplir
|
|||
|
||||
\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:
|
||||
|
||||
|
@ -257,30 +248,30 @@ L'exemple donné dans les guides de style de Google est celui-ci:
|
|||
\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]]:
|
||||
"""Fetches rows from a Smalltable.
|
||||
|
||||
|
||||
Retrieves rows pertaining to the given keys from the Table instance
|
||||
represented by table_handle. String keys will be UTF-8 encoded.
|
||||
|
||||
|
||||
Args:
|
||||
table_handle: An open smalltable.Table instance.
|
||||
keys: A sequence of strings representing the key of each table
|
||||
row to fetch. String keys will be UTF-8 encoded.
|
||||
require_all_keys: Optional; If require_all_keys is True only
|
||||
rows with values set for all keys will be returned.
|
||||
|
||||
|
||||
Returns:
|
||||
A dict mapping keys to the corresponding table row data
|
||||
fetched. Each row is represented as a tuple of strings. For
|
||||
example:
|
||||
|
||||
|
||||
{b'Serak': ('Rigel VII', 'Preparer'),
|
||||
b'Zim': ('Irk', 'Invader'),
|
||||
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
|
||||
|
||||
|
||||
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
|
||||
table (and require_all_keys must have been False).
|
||||
|
||||
|
||||
Raises:
|
||||
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}
|
||||
|
||||
|
||||
C'est-à-dire:
|
||||
C'est-à-dire :
|
||||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
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).
|
||||
\item
|
||||
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.
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
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).
|
||||
\item 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}
|
||||
|
||||
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:
|
||||
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 :
|
||||
|
||||
\begin{figure}[!ht]
|
||||
\centering
|
||||
\scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/napoleon-module-level-docstring.png}}
|
||||
\caption{\url{https://xkcd.com/353/}}
|
||||
\centering
|
||||
\scalebox{1.0}{\includegraphics[max size={\textwidth}{\textheight}]{images/napoleon-module-level-docstring.png}}
|
||||
\caption{\url{https://xkcd.com/353/}}
|
||||
\end{figure}
|
||||
|
||||
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:
|
||||
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 ??)
|
||||
|
||||
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.
|
||||
|
@ -335,49 +311,32 @@ Il existe plusieurs niveaux de \emph{linters}:
|
|||
|
||||
\begin{enumerate}
|
||||
\def\labelenumi{\arabic{enumi}.}
|
||||
\item
|
||||
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.
|
||||
\item
|
||||
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.
|
||||
\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}.
|
||||
\item
|
||||
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.
|
||||
\item
|
||||
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.
|
||||
\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}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Pour vous donner une idée, voici ce que cela pourrait donner avec un
|
||||
code pas très propre et qui ne sert à rien:
|
||||
Pour vous donner une idée, voici ce que cela pourrait donner avec un code pas très propre et qui ne sert à rien :
|
||||
|
||||
\begin{listing}[!ht]
|
||||
\begin{minted}{python}
|
||||
from datetime import datetime
|
||||
"""On stocke la date du jour dans la variable ToD4y"""
|
||||
|
||||
|
||||
ToD4y = datetime.today()
|
||||
|
||||
|
||||
def print_today(ToD4y):
|
||||
today = ToD4y
|
||||
print(ToD4y)
|
||||
|
||||
|
||||
def GetToday():
|
||||
return ToD4y
|
||||
|
||||
|
@ -392,70 +351,57 @@ Avec Flake8, nous obtiendrons ceci:
|
|||
|
||||
\begin{listing}[!ht]
|
||||
\begin{verbatim}
|
||||
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:11:1: E302 expected 2 blank lines, found 1
|
||||
test.py:16:8: E222 multiple spaces after operator
|
||||
test.py:16:11: F821 undefined name 'Get_Today'
|
||||
test.py:18:1: W391 blank line at end of file
|
||||
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:11:1: E302 expected 2 blank lines, found 1
|
||||
test.py:16:8: E222 multiple spaces after operator
|
||||
test.py:16:11: F821 undefined name 'Get_Today'
|
||||
test.py:18:1: W391 blank line at end of file
|
||||
\end{verbatim}
|
||||
\end{listing}
|
||||
|
||||
Nous trouvons des erreurs:
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
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.
|
||||
\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).
|
||||
\item
|
||||
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.
|
||||
\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}
|
||||
|
||||
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}
|
||||
$ pylint test.py
|
||||
************* Module test
|
||||
test.py:16:6: C0326: Exactly one space required after assignment
|
||||
t = Get_Today()
|
||||
^ (bad-whitespace)
|
||||
test.py:18:0: C0305: Trailing newlines (trailing-newlines)
|
||||
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: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: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: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: 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:10: E0602: Undefined variable 'Get_Today' (undefined-variable)
|
||||
$ pylint test.py
|
||||
************* Module test
|
||||
test.py:16:6: C0326: Exactly one space required after assignment
|
||||
t = Get_Today()
|
||||
^ (bad-whitespace)
|
||||
test.py:18:0: C0305: Trailing newlines (trailing-newlines)
|
||||
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: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: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: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: 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: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}
|
||||
|
||||
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:
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
au nommage (C0103) et à la mise en forme (C0305, C0326, W0105)
|
||||
\item
|
||||
à des variables non définies (E0602)
|
||||
\item
|
||||
de la documentation manquante (C0114, C0116)
|
||||
\item
|
||||
de la redéfinition de variables (W0621).
|
||||
\item au nommage (C0103) et à la mise en forme (C0305, C0326, W0105)
|
||||
\item à des variables non définies (E0602)
|
||||
\item de la documentation manquante (C0114, C0116)
|
||||
\item de la redéfinition de variables (W0621).
|
||||
\end{itemize}
|
||||
|
||||
Pour reprendre la
|
||||
|
@ -463,28 +409,23 @@ Pour reprendre la
|
|||
chaque code possède sa signification:
|
||||
|
||||
\begin{itemize}
|
||||
\item
|
||||
\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
|
||||
\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.
|
||||
\item \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 \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}
|
||||
|
||||
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}.
|
||||
|
||||
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:
|
||||
|
||||
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 :
|
||||
\begin{enumerate}
|
||||
\item \textbf{Une ligne de code}
|
||||
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, ...
|
||||
\item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
|
||||
\item \textbf{Une ligne de code}
|
||||
\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
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Ignorer une ligne de code}
|
||||
|
@ -496,10 +437,10 @@ Cet élément peut être:
|
|||
|
||||
\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, ...
|
||||
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.
|
||||
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.
|
||||
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}
|
||||
\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}
|
||||
|
||||
Est-ce que ce formatage est idéal et accepté par tout le monde ?
|
||||
\textbf{Non}.
|
||||
Même Pylint arrivera parfois à râler.
|
||||
\textbf{Non}.
|
||||
Même Pylint arrivera parfois à râler.
|
||||
Mais ce formatage conviendra dans 97,83\% des cas (au moins).
|
||||
|
||||
\begin{quote}
|
||||
By using Black, you agree to cede control over minutiae of
|
||||
hand-formatting. In return, Black gives you speed, determinism, and
|
||||
freedom from pycodestyle nagging about formatting. You will save time
|
||||
and mental energy for more important matters.
|
||||
By using Black, you agree to cede control over minutiae of hand-formatting.
|
||||
In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting.
|
||||
You will save time and mental energy for more important matters.
|
||||
|
||||
Black makes code review faster by producing the smallest diffs possible.
|
||||
Blackened code looks the same regardless of the project you're reading.
|
||||
Formatting becomes transparent after a while and you can focus on the
|
||||
content instead.
|
||||
Black makes code review faster by producing the smallest diffs possible.
|
||||
Blackened code looks the same regardless of the project you're reading.
|
||||
Formatting becomes transparent after a while and you can focus on the content instead.
|
||||
\end{quote}
|
||||
|
||||
Traduit rapidement à partir de la langue de Batman: "\texttt{En utilisant
|
||||
Black, vous cédez le contrôle sur le formatage de votre code. En retour,
|
||||
Black vous fera gagner un max de temps, diminuera votre charge mentale
|
||||
et fera revenir l'être aimé}". 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. L'étape de formatage deviendra transparente, et vous pourrez
|
||||
vous concentrer sur le contenu}".
|
||||
Traduit rapidement à partir de la langue de Batman: "\texttt{En utilisant Black, vous cédez le contrôle sur le formatage de votre code.
|
||||
En retour, Black vous fera gagner un max de temps, diminuera votre charge mentale et fera revenir l'être aimé}".
|
||||
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.
|
||||
L'étape de formatage deviendra transparente, et vous pourrez vous concentrer sur le contenu}".
|
||||
|
||||
\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.
|
||||
|
||||
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{minted}{python}
|
||||
from typing import List
|
||||
|
||||
|
||||
def first_int_elem(l: List[int]) -> int:
|
||||
return l[0] if l else None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(first_int_elem([1, 2, 3]))
|
||||
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{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{verbatim}
|
||||
>>> python mypy-test.py
|
||||
1
|
||||
a
|
||||
>>> python mypy-test.py
|
||||
1
|
||||
a
|
||||
\end{verbatim}
|
||||
\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.
|
||||
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:
|
||||
|
||||
\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)
|
||||
>>> 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
|
||||
fonction \texttt{first\_int\_elem}
|
||||
\item
|
||||
Eviter de lui donner de mauvais paramètres ;-)²
|
||||
\item Importer le type \texttt{Optional} et l'utiliser en sortie de notre fonction \texttt{first\_int\_elem}
|
||||
\item Eviter de lui donner de mauvais paramètres ;-)²
|
||||
\end{enumerate}
|
||||
|
||||
\section{Tests unitaires}
|
||||
|
||||
Comme tout bon \textbf{langage de programmation moderne} qui se respecte, Python embarque tout un environnement facilitant le lancement de tests;
|
||||
Une bonne pratique (parfois discutée) consiste cependant à switcher vers \texttt{pytest}, qui présente quelques avantages par rapport au module \texttt{unittest}:
|
||||
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} :
|
||||
|
||||
\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.
|
||||
\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):
|
||||
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) :
|
||||
|
||||
\begin{listing}
|
||||
\begin{minted}{Python}
|
||||
|
@ -624,8 +546,8 @@ complètement biesse; on est sur la partie théorique ici):
|
|||
\end{minted}
|
||||
\end{listing}
|
||||
|
||||
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}:
|
||||
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} :
|
||||
|
||||
\begin{listing}
|
||||
\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/}.
|
||||
|
||||
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.
|
||||
|
||||
\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é}
|
||||
|
||||
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.
|
||||
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{tabular}{|c|c|c|c|}
|
||||
& py37 & py38 & py39 \\
|
||||
& py37 & py38 & py39 \\
|
||||
\hline
|
||||
lib2 & V & V & V \\
|
||||
lib3 & X & V & V \\
|
||||
lib4 & X & V & V
|
||||
lib4 & X & V & V
|
||||
\end{tabular}
|
||||
\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}
|
||||
\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}.
|
||||
|
||||
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
|
||||
\texttt{ERROR:\ \ \ pyXX:\ InterpreterNotFound:\ pythonX.X}.
|
||||
|
||||
|
@ -716,75 +638,70 @@ Décrire le fichier setup.cfg.
|
|||
|
||||
\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:
|
||||
|
||||
\begin{listing}
|
||||
\begin{verbatim}
|
||||
# Makefile for gwift
|
||||
#
|
||||
|
||||
# User-friendly check for coverage
|
||||
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The 'coverage' command was not found. Make sure you have coverage installed)
|
||||
endif
|
||||
|
||||
.PHONY: help coverage
|
||||
|
||||
help:
|
||||
@echo " coverage to run coverage check of the source files."
|
||||
|
||||
coverage:
|
||||
coverage run --source='.' manage.py test; coverage report; coverage html;
|
||||
@echo "Testing of coverage in the sources finished."
|
||||
\end{verbatim}
|
||||
\caption{Un exemple de fichier Makefile}
|
||||
\begin{verbatim}
|
||||
# Makefile for gwift
|
||||
#
|
||||
|
||||
# User-friendly check for coverage
|
||||
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The 'coverage' command was not found. Make sure you have coverage installed)
|
||||
endif
|
||||
|
||||
.PHONY: help coverage
|
||||
|
||||
help:
|
||||
@echo " coverage to run coverage check of the source files."
|
||||
|
||||
coverage:
|
||||
coverage run --source='.' manage.py test; coverage report; coverage html;
|
||||
@echo "Testing of coverage in the sources finished."
|
||||
\end{verbatim}
|
||||
\caption{Un exemple de fichier Makefile}
|
||||
\end{listing}
|
||||
|
||||
Pour la petite histoire, \texttt{make} peu sembler un peu désuet, mais
|
||||
reste extrêmement efficace.
|
||||
|
||||
|
||||
Pour la petite histoire, \texttt{make} peu sembler un peu désuet, mais reste extrêmement efficace.
|
||||
|
||||
|
||||
\section{Conclusions (et intégration continue)}
|
||||
|
||||
\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}
|
||||
[flake8]
|
||||
max-line-length = 100
|
||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length = 100
|
||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||
|
||||
[mypy]
|
||||
python_version = 3.8
|
||||
check_untyped_defs = True
|
||||
ignore_missing_imports = True
|
||||
warn_unused_ignores = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_configs = True
|
||||
plugins = mypy_django_plugin.main
|
||||
|
||||
[mypy.plugins.django-stubs]
|
||||
django_settings_module = config.settings.test
|
||||
|
||||
[mypy-*.migrations.*]
|
||||
# Django migrations should not produce any errors:
|
||||
ignore_errors = True
|
||||
|
||||
[coverage:run]
|
||||
include = khana/*
|
||||
omit = *migrations*, *tests*
|
||||
plugins =
|
||||
django_coverage_plugin
|
||||
[flake8]
|
||||
max-line-length = 100
|
||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length = 100
|
||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||||
|
||||
[mypy]
|
||||
python_version = 3.8
|
||||
check_untyped_defs = True
|
||||
ignore_missing_imports = True
|
||||
warn_unused_ignores = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_configs = True
|
||||
plugins = mypy_django_plugin.main
|
||||
|
||||
[mypy.plugins.django-stubs]
|
||||
django_settings_module = config.settings.test
|
||||
|
||||
[mypy-*.migrations.*]
|
||||
# Django migrations should not produce any errors:
|
||||
ignore_errors = True
|
||||
|
||||
[coverage:run]
|
||||
include = khana/*
|
||||
omit = *migrations*, *tests*
|
||||
plugins =
|
||||
django_coverage_plugin
|
||||
\end{verbatim}
|
||||
|
||||
Mypy + black + pylint + flake8 + pyflakes + ...
|
||||
Mypy + black + pylint + flake8 + pyflakes + \ldots
|
||||
|
|
Loading…
Reference in New Issue