diff --git a/profiling/python_profiling.ipynb b/profiling/python_profiling.ipynb index bdb89b7..1f282ae 100644 --- a/profiling/python_profiling.ipynb +++ b/profiling/python_profiling.ipynb @@ -18,31 +18,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "> Les boutons *Solution* empêchent le bon rendu de ce notebook par *github* ou *nbviewer*, vous pouvez utiliser ce lien [MyBinder](https://mybinder.org/v2/gh/MordicusEtCubitus/CoursPython/master?filepath=profiling%2Fpython_profiling.ipynb) pour une bonne visualisation." + "> Les boutons *Solution* empêchent le bon rendu de ce notebook par **github** ou *nbviewer*, vous êtes invités à utiliser ce lien **[MyBinder](https://mybinder.org/v2/gh/MordicusEtCubitus/CoursPython/master?filepath=profiling%2Fpython_profiling.ipynb)** pour bénéficier d'une meilleure visualisation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Ce TP propose des exercices présentant quelques librairies Python permettant de profiler votre code source afin d'essayer d'identifier les sources de ralentissements de vos programmes.\n", + "Ce TP propose des exercices présentant quelques librairies Python permettant de profiler votre code source afin d'essayer d'identifier les sources de ralentissement de vos programmes.\n", "\n", "En matière de [profilage](https://fr.wikipedia.org/wiki/Profilage_de_code), Python propose plusieurs librairies en standard:\n", "\n", - "* timeit \n", + "* **timeit** \n", " https://docs.python.org/3/library/timeit.html \n", - "* cProfile \n", + "* **cProfile** \n", " https://docs.python.org/3/library/profile.html\n", - "* Trace \n", + "* **Trace** \n", " https://docs.python.org/3/library/trace.html#module-trace \n", - "* FaultHandler \n", + "* **FaultHandler** \n", " https://docs.python.org/3/library/faulthandler.html\n", " \n", "D'autres librairies issues de la communauté sont aussi disponibles, dont:\n", "\n", - "* [PyCallGraph](http://pycallgraph.slowchop.com/en/master/) et son fork [PyCallGraph2](https://github.com/daneads/pycallgraph2#readme#readme) qu'il est conseillé car la première version n'est plus vraiment maintenue.\n", + "* **[PyCallGraph](http://pycallgraph.slowchop.com/en/master/)** et son fork **[PyCallGraph2](https://github.com/daneads/pycallgraph2#readme#readme)** qu'il est conseillé car la première version n'est plus vraiment maintenue.\n", "\n", - "* [pyprof2calltree](https://github.com/pwaller/pyprof2calltree/#readme) qui permet de collecter des traces pStats de *cProfile* pour les visualiser avec [KCachegrind](https://kcachegrind.github.io/html/Home.html)\n", + "* **[pyprof2calltree](https://github.com/pwaller/pyprof2calltree/#readme)** qui permet de collecter des traces pStats de **cProfile** pour les visualiser avec [**KCachegrind**](https://kcachegrind.github.io/html/Home.html)\n", "\n", "* Et nous en verrons quelques autres" ] @@ -51,15 +51,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Types de profiler\n", + "## Types de Profiler\n", "\n", "On dénote en général 2 types de profileurs:\n", "\n", - "* Les profileurs événementiels ou déterministes (tracing profiler), qui enregistrent toutes les actions qui se passent dans le programme et peuvent fournir beaucoup de statistiques très précises. \n", - "Ils présentent l'inconvénient d'être très gourmands en ressources et peuvent ralentir considérablement le programme principal, ce qui les rend parfois inutilisables.\n", + "* Les profileurs événementiels ou déterministes (ou *Tracing Profiler*) : leur rôle consiste à enregistrer tout ou partie des actions se déroulant dans un programme. Offrant des statistiques précises au prix d'une consommation élevée des ressources, ce type de profileur risque de ralentir considérablement le programme principal et, à ce titre, se révèle parfois inutilisable.\n", "\n", - "* Les profileurs statistiques qui prélèvent à intervalles réguliers des informations sur votre programme. \n", - " Beaucoup moins gourmands en ressources ils ne fournissent pas une vue complète du programme et peuvent manquer des éléments si ceux-ci se produisent en dehors de leurs intervalles de mesures." + "* Les profileurs statistiques prélèvent, à intervalle de temps régulier, des informations sur le programme. Nettement moins gourmand en ressources que son homologue déterministe, ce type de profileur possède le défaut d'ignorer les événements qui se sont produits en dehors de l'intervalle de mesure configuré. Il n'offre donc pas véritablement une vue complète du programme." ] }, { @@ -68,19 +66,19 @@ "source": [ "## Time It\n", "\n", - "La librairie timeit est relativement facile à utiliser: vous lui passer sous forme de texte, le code à exécuter, plus quelques pramètres comme le nombre de fois que ce code doit être lancé ou encore les variables de départ, et elle exécute votre code pour vous retourner le temps mesuré." + "La librairie **timeit** est relativement facile à utiliser: vous lui passez (sous forme de chaine de caractères) le code à exécuter, ainsi que quelques paramètres comme le nombre de fois où le code doit être lancé et les variables de départ. Elle, se charge d'exécuter le code et de vous retourner le temps mesuré. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Un exemple valant mieux qu'un long discours, essayons avec la [suite de Padovan](https://fr.wikipedia.org/wiki/Suite_de_Padovan) en version récursive, histoire de ne pas reprendre l'exemple plus classique qu'est *Fibonacci* !" + "Un exemple vaut mieux qu'un long discours, essayons avec la [suite de Padovan](https://fr.wikipedia.org/wiki/Suite_de_Padovan) en version récursive, histoire de ne pas reprendre l'exemple plus classique qu'est Fibonacci !" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -108,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -117,7 +115,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -162,21 +160,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "La fonction *timeit* ne connaît pas les fonctions de votre programme, il convient de les lui transmettre via le paramètre **globals**. Ce paramètre attend un dictionnaire dont les clefs sont les noms de vos objets. \n" + "La fonction **timeit** ne connaît pas les fonctions de votre programme. Il convient de les lui transmettre via le paramètre `globals`. Ce paramètre attend un dictionnaire dont les clefs sont les noms de vos objets. \n" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "24.584196265001083" + "68.54979309700138" ] }, - "execution_count": 4, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -189,20 +187,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Ce temps peut sembler bien long, en effet, *timeit* exécute par défaut un million de fois le code demandé. Et retourne le temps total." + "Ce temps peut sembler bien long, en effet, **timeit** exécute par défaut un million de fois le code demandé. Et retourne le temps total." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 29, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Durée totale en secondes pour 100000 exécutions : 2.419350\n", - "Durée moyenne en secondes: 0.000024\n" + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 4)", + "output_type": "error", + "traceback": [ + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m4\u001b[0m\n\u001b[0;31m print( f\"Durée moyenne en secondes: {total_time/repeat:08.06f}\")\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], @@ -217,34 +215,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**NB** : Dans l'exemple ci-dessus nous utilisons la fonction python [globals](https://docs.python.org/3.7/library/functions.html#globals) qui retourne le dictionnaire des objets globaux pour initialiser le paramètre de *timeit* du même nom, c'est bien plus pratique" + "**NB** : Dans l'exemple ci-dessus nous utilisons la fonction python [`globals`](https://docs.python.org/3.7/library/functions.html#globals) qui retourne le dictionnaire des objets globaux dans l'optique d'initialiser le paramètre de **timeit** du même nom, c'est bien plus pratique" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Le temps moyen n'est pas forcément un bon critère: lors de multiples exécutions la durée peut varier d'une fois à l'autre de manière significative selon la charge de votre système, surtout sur de petits délais comme celui-ci.\n", + "Le temps moyen d'exécution n'est pas forcément le critère le plus pertinent pour juger des performances d'un programme. En effet, lors de multiples exécutions la durée peut varier une fois sur l'autre et fausser les résultats de manière significative. En pratique cela dépend de la charge de votre système et s'aggrave lorsque les délais entre deux exécutions sont très courts comme ici.\n", "\n", - "Dans ce cas la fonction repeat, qui répète plusieurs fois les *number* mesures et pour chaque répétition retourne le meilleur temps de ces mesures peut s'avérer être une meilleure option en ne conservant que la valeur minimale de la liste résultat:" + "Dans le cas présent la fonction repeat, qui répète plusieurs fois les *number* mesures et, pour chaque répétition, retourne le meilleur temps de ces mesures peut s'avérer être une meilleure option en ne conservant que la valeur minimale de la liste des résultats." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Meilleurs temps des 10 séries de 100 mesures: \n", - "[0.002551789002609439, 0.00268129599862732, 0.002631343995744828, 0.002532539001549594, 0.002732001004915219, 0.0028593390015885234, 0.0025432030015508644, 0.0027570079982979223, 0.0026723789997049607, 0.0026331900007789955]\n", - "\n", - "Meilleur temps pour les 100 mesures: 0.002532539001549594\n" - ] - } - ], + "outputs": [], "source": [ "times = timeit.repeat(stmt=\"padovan(20)\", globals=globals(), repeat=10, number=100)\n", "print(\"Meilleurs temps des 10 séries de 100 mesures: \")\n", @@ -263,164 +250,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Pourquoi de nombreux exemples de profiling utilisent des suites numériques à double récursivité ? \n", + "Pourquoi de nombreux exemples de profiling utilisent-ils des suites numériques à double récursivité ? \n", "\n", - "Parce que leur code est très consommateur de temps : chaque appel lance 2 autres appels, qui à leur tour en lancent 2 autres, et ainsi de suite. Pour calculer padovan(7), 8 appels de sous fonctions sont réalisés.\n", - "\n" + "Parce que leur code est très consommateur de temps (ce qui est l'effet recherché). Lorsqu'il y a double récursivité, chaque appel lance 2 autres appels, qui à leur tour en lancent 2 autres, et ainsi de suite. Pour calculer `padovan(7)`, 8 appels de sous fonctions sont nécessaires.\n" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "1 - pado(7)\n", - "\n", - "\n", - "\n", - "1.1\n", - "\n", - "1.1 - pado(5)\n", - "\n", - "\n", - "\n", - "1->1.1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.2\n", - "\n", - "1.2 - pado(4)\n", - "\n", - "\n", - "\n", - "1->1.2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.1.1\n", - "\n", - "1.1.1 - pado(3)\n", - "\n", - "\n", - "\n", - "1.1->1.1.1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.1.2\n", - "\n", - "1.1.2 - pado(2)\n", - "\n", - "\n", - "\n", - "1.1->1.1.2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.2.1\n", - "\n", - "1.2.1 - pado(2)\n", - "\n", - "\n", - "\n", - "1.2->1.2.1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.2.2\n", - "\n", - "1.2.2 - pado(1)\n", - "\n", - "\n", - "\n", - "1.2->1.2.2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.1.1.1\n", - "\n", - "1.1.1.1 - pado(1)\n", - "\n", - "\n", - "\n", - "1.1.1->1.1.1.1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1.1.1.2\n", - "\n", - "1.1.1.2 - pado(0)\n", - "\n", - "\n", - "\n", - "1.1.1->1.1.1.2\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from graphviz import Digraph\n", "\"\"\"\n", - "To install graphviz\n", + "Pour installer graphviz :\n", "\n", "bash$ pip install graphviz\n", "\n", - "or\n", + "ou\n", "\n", "bash$ conda install graphviz\n", "\n", - "Check that the file 'dot' is in your path\n", + "Vérifiez que le programme 'dot' est dans votre path\n", "\n", "bash$ which dot\n", "/home/user/anaconda/bin/dot\n", "\n", + "Sous Windows ou Mac vous pourriez avoir besoin d'installer directement le binaire du logiciel\n", + "Vous le trouverez à cette adresse : http://graphviz.org/download/\n", "\n", - "Under Windows or Mac you may need to install the graphviz binary from http://graphviz.org/download/\n", - "And be sure to set dot.exe in your path\n", + "Sous Windows, vérifiez que dot.exe est dans votre path\n", "\n", "C:> set PATH=%PATH%;c:/path/to/dot.exe\n", "\"\"\"\n", @@ -460,144 +319,16 @@ "metadata": {}, "source": [ "L'ordre de grandeur du nombre d'appels imbriqués pour `padovan(n)` est `2**(n-1)`. \n", - "Cette implémentation utilisant une double récursivité est particulièrement mauvaise d'un point de vue performances puisqu'elle génère des temps de calculs exponentiels.\n", + "Utilisant une double récursivité cette implémentation est particulièrement mauvaise du point de vue des performances puisqu'elle génère des temps de calculs exponentiels.\n", "\n", - "Juste pour le plaisir, voici une autre manière un peu plus générique de générer le graphe des appels en utilisant les modules *traceback* et *inspect* de Python:" + "Juste pour le plaisir, voici une autre manière un peu plus générique de générer le graphe des appels en utilisant les modules **traceback** et **inspect** de Python:" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "%3\n", - "\n", - "\n", - "\n", - "140425383584792\n", - "\n", - "pado(7)\n", - "\n", - "\n", - "\n", - "140425366664216\n", - "\n", - "pado(4)\n", - "\n", - "\n", - "\n", - "140425383584792->140425366664216\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365636608\n", - "\n", - "pado(5)\n", - "\n", - "\n", - "\n", - "140425383584792->140425365636608\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365635632\n", - "\n", - "pado(1)\n", - "\n", - "\n", - "\n", - "140425366664216->140425365635632\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365636120\n", - "\n", - "pado(2)\n", - "\n", - "\n", - "\n", - "140425366664216->140425365636120\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365637096\n", - "\n", - "pado(2)\n", - "\n", - "\n", - "\n", - "140425365636608->140425365637096\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365637584\n", - "\n", - "pado(3)\n", - "\n", - "\n", - "\n", - "140425365636608->140425365637584\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365638072\n", - "\n", - "pado(0)\n", - "\n", - "\n", - "\n", - "140425365637584->140425365638072\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "140425365638560\n", - "\n", - "pado(1)\n", - "\n", - "\n", - "\n", - "140425365637584->140425365638560\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import traceback\n", "import inspect \n", @@ -607,7 +338,7 @@ " # Current frame\n", " cf = inspect.currentframe()\n", " \n", - " # Parent and current frame\n", + " # Traceback parent frame, Traceback current frame\n", " tb_pf, tb_cf = traceback.extract_stack(cf, limit=2)\n", " \n", " pf = cf.f_back\n", @@ -615,10 +346,10 @@ " if first:\n", " pado.graph = Digraph(format=\"svg\")\n", " \n", - " # Here we are lucky, current frame id is not reused once the function exists\n", - " # So 2 distinct calls have unique frame id and graph is properly constructed\n", - " # But I don't really understand why it works well here and ids are not reused \n", - " # If reused, graph would be badly broken\n", + " # Ici nous avons de la chance, l'identifiant de la frame current n'est pas réutilisé une fois que la fonction existe\n", + " # Ainsi, 2 appels distincts ont un identifiant de trame unique et le graphique est correctement construit\n", + " # Mais je ne comprends pas vraiment pourquoi ça marche bien ici et les ids ne sont pas réutilisés. \n", + " # S'il était réutilisé, le graphique serait gravement cassé.\n", " pado.graph.node(str(id(cf)), \"%s(%s)\" % (tb_cf.name, n))\n", " \n", " if not first:\n", @@ -648,22 +379,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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JqHlF5TSQQ6T0jb2XVyAVQrGGJPQ/JVCarsmXimISJqIfC4iX4rIehF5xCOmtYi8JSJbRWS5iGTFKh9jjGmK8gt8dJQjJDWDw0dlwNmqOhwYAVwoIuNCYm4CDqnqScBvgcdjmE/MfPPNN5x11lkMHDiQwYMH89RTT1WbPmvWLHJycqrdrew17q677mLJkvp1jfvss89y0kknISLs378/MP7dd99l5syZnvOoKjk5ObXe7LZq1Sp+8IMf1CuXRLF48WI++eSTwPCLL77I66+/HpVln3vuuRw6dCgqyzImEvmHjpBBSaPcowAxrBTUUeIOprqv0JsipgKvue/fBs6RJvgIq5SUFJ588kk2bNjAZ599xnPPPcdXX31FXl4eN910Ezt37mTp0qXccsstnuPA6Y/os88+Y+LEifVa94QJE1i4cCF9+/atNn7y5MnMnz+fI0eO1JjnlltuYenSpezcuZObbrqJvLy8GjG/+MUvuOOOO+qVS334u9yIhdBK4ZZbbuHaa6+NyrKvueYann/++agsy5hIHCo8RDJVkNY4Vx+hqjF7AcnAaqAEeNxj+jqgV9Dw10CmR9wMYAWwok+fPhrqq6++qjEunqZMmaIffPCBqqru3r1b+/btq5MnT9bKysqw437/+9/rzJkzA8vo27evPvTQQzpy5EgdMmSIbtiwodZ19u3bV/ft21dt3F133aVvvfVWjdjKykqdPHmy9u3bV/fs2VNjelFRkZ5yyimB4eXLl+v48eN1xIgROn78eN24caOqqo4dO1bXrVsXiDvzzDN1xYoVWlJSojfccINmZ2friBEjdN68eaqq+sc//lGnTZumF110kZ511llaXFysZ599dqCM/jhV1UcffVT79++v5557rk6fPl1/9atfqarq1q1b9YILLtBRo0bp6aefXuNz2b59ux5//PHao0cPHT58uC5ZskRnzpwZmP/MM8/Uu+66S8844wwdMGCAfv755/q9731PTzrpJH3ggQcCy3njjTd0zJgxOnz4cJ0xY4ZWVFSoqurBgwd18ODBnv8HibYdmqavorJKz7j/NdWZGaorZh3TsoAVGsF+O6b3KahqJTBCRDoCc0VkiKoGX8zv1SqocYu1qr4EvATOHc21rvSfP4PdaxuetJduQ+E7kT1sJicnh1WrVnHqqaeSn5/PzJkzufHGG+nXrx+33347Dz74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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", @@ -679,7 +397,7 @@ " \n", "plt.plot(data, times, label=\"padovan(n)\")\n", "\n", - "# Is it really exponential ?\n", + "# Est-ce réellement exponentiel?\n", "average_by_call = times[-1] / 2**61\n", "plt.plot(data, [(2**(n-1)*average_by_call) for n in data], label=\"2**(n-1) * (average time)\")\n", "\n", @@ -697,13 +415,13 @@ "### Exercice\n", "\n", "\n", - "Le [jour de dépassement](https://fr.wikipedia.org/wiki/Jour_du_d%C3%A9passement) (Earth Overshoot Day ou EOD) est cette année 2019, le 29 juillet.\n", + "Le [jour de dépassement](https://fr.wikipedia.org/wiki/Jour_du_d%C3%A9passement) (*Earth Overshoot Day* ou EOD) est cette année 2019, le 29 juillet.\n", "\n", - "Nous savons que les Data Centers du monde entier consomment 10% de la production électrique mondiale.\n", + "Nous savons que les *Data Centers* du monde entier consomment 10% de la production électrique mondiale.\n", "\n", "Si la consommation électrique était proportionnelle à la durée d'exécution d'un programme - ce qui est une assertion loin d'être exacte - en parallélisant à outrance nous pourrions économiser beaucoup de ressources.\n", "\n", - "Mesurer avec *timeit* le temps de téléchargement en série ou en \"parallèle\" avec des threads des URL suivantes:\n", + "Mesurer avec **timeit** le temps de téléchargement en série, ou en \"parallèle\" avec des threads, des URL suivantes:\n", "\n", "* https://fr.wikipedia.org/wiki/Jour_du_d%C3%A9passement \n", "* https://www.franceculture.fr/emissions/le-choix-de-la-redaction-13-14/data-centers-les-ogres-energivores-dinternet \n", @@ -711,7 +429,7 @@ "\n", "Dans cet exemple, le programme prendra peut-être trois fois moins de temps pour le téléchargement total, mais 3 processeurs/coeurs fonctionneront au lieu d'un seul, la consommation électrique risque donc d'être 3 fois supérieure sur ce temps réduit.\n", "\n", - "Vous pouvez utiliser la librairie [requests](https://3.python-requests.org/) pour télécharger simplement les URL:\n", + "Vous pouvez utiliser la librairie **[requests](https://3.python-requests.org/)** pour télécharger simplement les URL:\n", "\n", "```python\n", "import requests\n", @@ -723,38 +441,30 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Done\n" - ] - } - ], + "outputs": [], "source": [ - "# Example showing how to use a thread\n", + "# Thread, mode d'emploi\n", "from threading import Thread\n", "\n", "def square(x):\n", " return x**2\n", "\n", - "# Thread creation\n", + "# Création du thread\n", "t = Thread(target=square, args=(10,))\n", "\n", - "# Starting the thread (the function in parallel of main code)\n", + "# Démarrage du thread (la fonction s'éxécute parallèlement au code principal)\n", "t.start()\n", "\n", - "# Waiting thread termination\n", + "# Attente de la fin d'exécution du thread\n", "t.join()\n", "print(\"Done\")" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -764,9 +474,9 @@ "\n", "urls = ['https://fr.wikipedia.org/wiki/Jour_du_d%C3%A9passement'\n", " , 'https://www.franceculture.fr/emissions/le-choix-de-la-redaction-13-14/data-centers-les-ogres-energivores-dinternet'\n", - " , 'https://www.python.org'] # use python.org first, then try replacing Python.org with PDF file above and see how the gain change\n", + " , 'https://www.python.org'] # Dans l'exercice, commencez par utiliser python.org à la place du fichier PDF, puis utilisez le fichier PDF.\n", "\n", - "# \n" + "# \n" ] }, { @@ -787,12 +497,13 @@ " \n", " for url in urls:\n", " t = Thread(target=requests.get, args=(url,))\n", - " t.start() # starting the function\n", - " # do not join here, else it is like in serial, join is blocking until thread is blocking\n", - " tasks.append(t) # keeping track of thread\n", + " t.start() # démarrage du thread\n", + " # Ne pas join() directement ici sinon l'exécution se fera en série, or\n", + " # join() est un processus bloquant pour la suite du programme\n", + " tasks.append(t) # nous conservons une référence menant au thread\n", " \n", " for t in tasks:\n", - " t.join() # waiting threads to terminate\n", + " t.join() # attente de la fin d'exécution du thread\n", " \n", " \n", "r1 = timeit.timeit(stmt=\"download_serial(urls)\", globals=globals(), number=1)\n", @@ -811,22 +522,14 @@ "source": [ "### Jupyter %timeit\n", "\n", - "Les notebooks jupyter ont aussi un mot clef magique *timeit* basé sur la librairie python\n" + "Les notebooks **jupyter** ont aussi un mot clef magique `%timeit` basé sur la librairie python\n" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "288 ns ± 5.58 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit padovan(5)" ] @@ -840,22 +543,14 @@ "* `-r` : nombre de répétitions/séries de mesures\n", "* `-n` : nombre de mesures par série\n", "\n", - "Les opérations sont exécutées dans une autre frame, vos variables ne sont pas forcément modifiées après coup" + "Les opérations sont exécutées dans une autre *Frame*, vos variables ne sont pas forcément modifiées après coup" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.24 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit -r 1 -n 1 padovan(36)" ] @@ -864,23 +559,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Par exemple, ici, la variable `r` ne contiendra pas la dernière valeur calculée, car dans l'autre frame/bloc, une variable `r` locale est créée." + "Par exemple, ici, la variable `r` ne contiendra pas la dernière valeur calculée, car dans l'autre *Frame*/*Bloc*, une variable `r` locale est créée." ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "20.1 ns ± 0.308 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n", - "5\n" - ] - } - ], + "outputs": [], "source": [ "a = 10\n", "r = 5\n", @@ -894,23 +580,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Alors qu'ici le code modifie une variable existante, sans qu'il y ait affectation, donc création d'une variable locale à la frame. Dans ce cas la variable `a` sera modifiée" + "Alors qu'ici le code modifie une variable existante, sans qu'il y ait affectation, donc création d'une variable locale à la *Frame*. Dans ce cas la variable `a` sera modifiée" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "206 ns ± 0 ns per loop (mean ± std. dev. of 1 run, 3 loops each)\n", - "[10, 10, 10, 10]\n" - ] - } - ], + "outputs": [], "source": [ "a = [10]\n", "\n", @@ -921,7 +598,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -938,7 +615,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -955,24 +632,16 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "94.2 ms ± 4.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "%%timeit\n", "a = range(2000)\n", "b = range(1000, 3000)\n", "intersection(a, b)\n", - "# do not execute print in cell having %%timeit \n", - "# else it may be displayed too too much times, and your kernel may need to be restarted" + "# N'exécutez pas print dans une cellule contenant %%timeit\n", + "# car print va s'exécuter tant de fois que vous pourriez avoir besoin de redémarrer votre machine" ] }, { @@ -988,29 +657,16 @@ "source": [ "### Jupyter %time\n", "\n", - "Le mot clef magique `%time` se comporte comme la commande time Unix: Il mesure le temps d'exécution de votre commande et affice 3 temps : système, utilisateur et réel.\n", + "Le mot clef magique `%time` se comporte comme la commande `time` Unix: Il mesure le temps d'exécution de votre commande et affiche 3 temps : système, utilisateur et réel.\n", "\n", "Il n'exécute qu'une seule fois la commande." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 2 µs, sys: 0 ns, total: 2 µs\n", - "Wall time: 3.34 µs\n", - "11\n", - "CPU times: user 2 µs, sys: 0 ns, total: 2 µs\n", - "Wall time: 3.34 µs\n", - "[4]\n" - ] - } - ], + "outputs": [], "source": [ "a = 10\n", "%time a = a+ 1\n", @@ -1029,20 +685,9 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "padovan(36) is 17991\n", - "Done\n", - "CPU times: user 2.24 s, sys: 17 µs, total: 2.24 s\n", - "Wall time: 2.25 s\n" - ] - } - ], + "outputs": [], "source": [ "%%time\n", "r = padovan(36)\n", @@ -1064,44 +709,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**cProfile** (basée sur l'ancienne librairie lsprof) est elle aussi disponible en standard avec Python. Cette librairie permet de profiler tout ou partie de votre code et génère des statistiques sur les fonctions, comme leur nombre d'appels ou leur temps d'exécution. \n", + "**cProfile** (basée sur l'ancienne librairie **lsprof**) est elle aussi disponible en standard avec Python. Cette librairie permet de profiler tout ou partie de votre code et génère des statistiques sur les fonctions, comme leur nombre d'appels ou leur temps d'exécution. \n", "Elle ne permet cependant pas de mesurer l'usage de la mémoire.\n", "\n", - "*cProfile* propose un profileur déterministe/événementiel. Mais il reste tout à fait utilisable car la conception de Python fournit déjà toute la mécanique nécessaire pour intercepter tous les appels de fonctions et autres objets. De ce fait l'implémentation de tels profileurs est simple et reste modérément gourmande en ressources." + "**cProfile** propose un profileur déterministe/événementiel. Mais il reste tout à fait utilisable car la conception de Python fournit déjà la mécanique nécessaire pour intercepter tous les appels de fonctions et autres objets. De ce fait l'implémentation de tels profileurs est simple et reste modérément gourmande en ressources." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "La librairie possède une fonction *run* qui exécute comme *timeit* le code passé sous forme de chaîne de caractères. Mais dans ce cas, il n'est pas nécessaire de fournir le dictionnaire des objets globaux nécessaires à l'exécution de la fonction.\n", + "La librairie possède une fonction `run` qui exécute, comme **timeit**, le code passé sous forme de chaîne de caractères. Mais dans ce cas, il n'est pas nécessaire de fournir le dictionnaire des objets globaux nécessaires à l'exécution de la fonction.\n", "\n", "Le résultat peut être visualisé sous forme de tableau." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 83650 function calls (4 primitive calls) in 0.011 seconds\n", - "\n", - " Ordered by: standard name\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 83647/1 0.011 0.000 0.011 0.011 :1(padovan)\n", - " 1 0.000 0.000 0.011 0.011 :1()\n", - " 1 0.000 0.000 0.011 0.011 {built-in method builtins.exec}\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "import cProfile\n", "cProfile.run(\"padovan(39)\")" @@ -1113,12 +740,12 @@ "source": [ "Les paramètres ont la signification suivante:\n", "\n", - "* **ncalls** : Le nombre d'appels. Si 2 nombres sont présents cela signifie que la fonction est récursive. Le premier nombre correspond au nombre total d'appels, le second correspond au nombre d'appels de premiers niveau\n", - "* **tottime** : La durée totale d'exécution de la fonction, excluant les appels de sous-fonctions\n", - "* **percall** : La durée moyenne: Correspond à *ncalls / tottime*\n", - "* **cumtime** : Le temps cumulé de la fonction et de toutes ses sous fonctions\n", - "* **percall** : Le temps moyen cumulé (appels de premier niveau): Correspond à *cumtime / ncalls*\n", - "* **filename:lineno(function)** : comme indiqué: nom de fichier, ligne et nom de fonction\n" + "* `ncalls` : Le nombre d'appels. Si 2 nombres sont présents cela signifie que la fonction est récursive. Le premier nombre correspond au nombre total d'appels, le second correspond au nombre d'appels de premiers niveau\n", + "* `tottime` : La durée totale d'exécution de la fonction, excluant les appels de sous-fonctions\n", + "* `percall` : La durée moyenne: correspond à *ncalls / tottime*\n", + "* `cumtime` : Le temps cumulé de la fonction et de toutes ses sous-fonctions\n", + "* `percall` : Le temps moyen cumulé (appels de premier niveau): Correspond à *cumtime / ncalls*\n", + "* `filename:lineno(function)` : comme indiqué: nom de fichier, ligne et nom de fonction\n" ] }, { @@ -1127,7 +754,7 @@ "source": [ "Mais mesurer le temps d'exécution d'un programme en utilisant des chaînes de caractères n'est pas vraiement exploitable pour analyser l'ensemble d'un programme.\n", "\n", - "Pour cela la librairie permet d'analyser une partie de votre code en instanciant un objet de la classe **Profile** puis en appelant ses méthodes *enable* et *disable* \n", + "Pour cela la librairie permet d'analyser une partie de votre code en instanciant un objet de la classe `Profile` puis en appelant ses méthodes `enable` et `disable` \n", "\n", "L'intégralité du code compris entre ces méthodes sera monitoré:\n", "\n" @@ -1135,20 +762,9 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1000" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import time\n", "\n", @@ -1161,12 +777,12 @@ "def cube(x):\n", " return x ** 3\n", "\n", - "pr = cProfile.Profile() # Create the profiler\n", - "pr.enable() # Activate the profiler\n", + "pr = cProfile.Profile() # Création du profiler\n", + "pr.enable() # Activation du profiler\n", "\n", - "padovan(20) # is profiled\n", + "padovan(20) # est profilé\n", "for x in range(1000, 100000, 1000):\n", - " countdown(x) # is profiled\n", + " countdown(x) # est profilé\n", "\n", " \n", "a = range(2000)\n", @@ -1174,56 +790,23 @@ "\n", "intersection(a, b)\n", " \n", - "pr.disable() # Deactivate the profiler\n", + "pr.disable() # Désactivation du profiler\n", "\n", - "cube(10) # is not profiled\n" + "cube(10) # n'est pas profilé\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "La méthode *print_stats* permet quant-à elle d'afficher les statistiques. Il est aussi possible de les sauvegarder dans un fichier avec *dump_stats*" + "La méthode `print_stats` permet quant à elle d'afficher les statistiques. Il est aussi possible de les sauvegarder dans un fichier avec `dump_stats`" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 1554 function calls (1156 primitive calls) in 0.259 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 6 0.000 0.000 0.259 0.043 interactiveshell.py:3230(run_code)\n", - " 6 0.000 0.000 0.259 0.043 {built-in method builtins.exec}\n", - " 1 0.000 0.000 0.156 0.156 :16()\n", - " 99 0.156 0.002 0.156 0.002 :3(countdown)\n", - " 1 0.000 0.000 0.103 0.103 :23()\n", - " 1 0.103 0.103 0.103 0.103 :1(intersection)\n", - " 6 0.000 0.000 0.000 0.000 codeop.py:132(__call__)\n", - " 6 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", - " 1 0.000 0.000 0.000 0.000 :15()\n", - " 399/1 0.000 0.000 0.000 0.000 :1(padovan)\n", - " 1000 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}\n", - " 6 0.000 0.000 0.000 0.000 hooks.py:142(__call__)\n", - " 6 0.000 0.000 0.000 0.000 ipstruct.py:125(__getattr__)\n", - " 1 0.000 0.000 0.000 0.000 :25()\n", - " 1 0.000 0.000 0.000 0.000 :21()\n", - " 1 0.000 0.000 0.000 0.000 :20()\n", - " 6 0.000 0.000 0.000 0.000 interactiveshell.py:1258(user_global_ns)\n", - " 6 0.000 0.000 0.000 0.000 hooks.py:207(pre_run_code_hook)\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "pr.print_stats(sort='cumulative')" ] @@ -1232,50 +815,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Enfin, la classe **[Stats](https://docs.python.org/3/library/profile.html#module-pstats)** permet de personnaliser l'affichage et le calcul des statistiques:" + "Enfin, la classe [`Stats`](https://docs.python.org/3/library/profile.html#module-pstats) permet de personnaliser l'affichage et le calcul des statistiques:" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 428 function calls (30 primitive calls) in 0.032 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 3 0.000 0.000 0.032 0.011 interactiveshell.py:3230(run_code)\n", - " 3 0.000 0.000 0.032 0.011 {built-in method builtins.exec}\n", - " 1 0.000 0.000 0.032 0.032 :8()\n", - " 1 0.032 0.032 0.032 0.032 :3(countdown)\n", - " 1 0.000 0.000 0.000 0.000 :7()\n", - " 399/1 0.000 0.000 0.000 0.000 :1(padovan)\n", - " 3 0.000 0.000 0.000 0.000 codeop.py:132(__call__)\n", - " 3 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", - " 3 0.000 0.000 0.000 0.000 hooks.py:142(__call__)\n", - " 1 0.000 0.000 0.000 0.000 :10()\n", - " 3 0.000 0.000 0.000 0.000 ipstruct.py:125(__getattr__)\n", - " 3 0.000 0.000 0.000 0.000 interactiveshell.py:1258(user_global_ns)\n", - " 3 0.000 0.000 0.000 0.000 hooks.py:207(pre_run_code_hook)\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - "\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "import cProfile, pstats, io\n", "\n", "pr = cProfile.Profile()\n", "pr.enable()\n", "\n", - "# ... doing nice stuff ...\n", + "# ... fantastique code ...\n", "padovan(20)\n", "countdown(10**6)\n", "\n", @@ -1284,8 +838,8 @@ "s = io.StringIO()\n", "\n", "ps = pstats.Stats(pr, stream=s)\n", - "ps.strip_dirs() # remove full path name to file/function\n", - "ps.sort_stats('cumtime') #sort by cumulative time descending\n", + "ps.strip_dirs() # supprime le nom complet du chemin d'accès au fichier/à la fonction\n", + "ps.sort_stats('cumtime') # trier par temps cumulé décroissant\n", "ps.print_stats()\n", "\n", "print(s.getvalue())" @@ -1299,13 +853,13 @@ "\n", "Jupyter propose 2 autres mots clefs magiques permettant de profiler votre code:\n", "\n", - "* **prun**: Semblable à `%run -p`, cette commande exécute le code en activant le profiler cProfile\n", - "* **lprun**: Mesure le temps d'exécution de chaque ligne de code" + "* *%prun*: Semblable à `%run -p`, cette commande exécute le code en activant le profiler cProfile\n", + "* *%lprun*: Mesure le temps d'exécution de chaque ligne de code" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1314,58 +868,22 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " " - ] - } - ], + "outputs": [], "source": [ "%prun -s calls intersection(range(1000,2000), range(2000, 3000))" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 8 function calls (4 primitive calls) in 0.000 seconds\n", - "\n", - " Ordered by: cumulative time\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - " 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}\n", - " 1 0.000 0.000 0.000 0.000 :1()\n", - " 5/1 0.000 0.000 0.000 0.000 :1(padovan)\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - "\n", - "\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "ps = %prun -r -s calls padovan(5)\n", - "ps.strip_dirs() # remove full path name to file/function\n", - "ps.sort_stats('cumtime') #sort by cumulative time descending\n", + "ps.strip_dirs() # ps.strip_dirs() # supprime le nom complet du chemin d'accès au fichier/à la fonction\n", + "ps.sort_stats('cumtime') #trier par temps cumulé décroissant\n", "ps.print_stats()" ] }, @@ -1373,22 +891,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Le mot clef magique `%lprun` n'est pas fourni en standard. C'est un plugin de la librairie [line profiler](https://github.com/rkern/line_profiler)\n", + "Le mot clef magique *%lprun* n'est pas fourni en standard. C'est un plugin de la librairie **[line profiler](https://github.com/rkern/line_profiler)**\n", "\n", - "Il convient d'installer la librairie puis de charger l'extension dans Jupyter\n", + "Il convient d'installer la librairie puis de charger l'extension dans **Jupyter**\n", "\n", "```bash\n", "bash ou C:> pip install line_profiler\n", "```\n", "\n", - "Ensuite il reste à charger l'extension dans Jupyter:\n", + "Ensuite il reste à charger l'extension dans **Jupyter**:\n", "\n", "`%load_ext line_profiler`" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1397,7 +915,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1409,12 +927,12 @@ "metadata": {}, "source": [ "Il convient d'indiquer quelles sont les fonctions que l'on souhaite analyser dans la commande saisie... \n", - "Ce qui peut paraître déroutant quand il n'y en a qu'une, mais cette option permet de ne profiler que des sous fonctions" + "Ce qui peut paraître déroutant quand il n'y en a qu'une, mais cette option permet de ne profiler que des sous-fonctions" ] }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1450,7 +968,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Nous commençons à entrevoir la cause des temps relativement élevés de la fonction *intersection* : le test *if* est exécuté 1 million de fois dans cet exemple." + "Nous commençons à entrevoir la cause des temps relativement élevés de la fonction `intersection` : le test *if* est exécuté 1 million de fois dans cet exemple." ] }, { @@ -1459,39 +977,31 @@ "source": [ "## Définir votre propre profiler\n", "\n", - "Le module **sys** possède une chouette fonction [*setprofile*](https://docs.python.org/3/library/sys.html#sys.setprofile) qui vous permet de définir une fonction qui sera automatiquement appelée dès que certains évènements se produiront, comme:\n", + "Le module **sys** possède une chouette fonction [`setprofile`](https://docs.python.org/3/library/sys.html#sys.setprofile) qui vous permet de définir une fonction qui sera automatiquement appelée dès que certains événements se produiront, comme:\n", "\n", - "* **call** : L'exécution d'une fonction\n", - "* **return** : Le retour d'une fonction\n", + "* `call` : L'exécution d'une fonction\n", + "* `return` : Le retour d'une fonction\n", "* autres...\n", "\n", "La fonction d'analyse de code recevra 3 paramètres:\n", "\n", - "* **frame** : La frame qui va être exécutée\n", - "* **event** : le nom de l'évènement, cf ci-dessus\n", - "* **arg** : dépend du type de l'évènement" + "* `frame` : La frame qui va être exécutée\n", + "* `event` : le nom de l'évènement (cf. ci-dessus)\n", + "* `arg` : dépend du type de l'évènement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Voici un petit exemple qui ne sera pas exécuté au travers de jupyter car ce dernier génère beaucoup trop d'appels de fonctions lorsqu'il exécute une cellule et cela va perturber la trace attendue:" + "Voici un petit exemple qui ne sera pas exécuté au travers de **jupyter** car ce dernier génère beaucoup trop d'appels de fonctions lorsqu'il exécute une cellule et cela va perturber la trace attendue:" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing my_profiler_example.py\n" - ] - } - ], + "outputs": [], "source": [ "%%file my_profiler_example.py\n", "# Exemple \n", @@ -1508,30 +1018,20 @@ "a = list(range(10))\n", "b = list(range(5,15))\n", "\n", - "# Setting the profiler\n", + "# Réglage du profileur\n", "sys.setprofile(my_profiler)\n", "\n", "square(10)\n", "\n", - "# Restoring the previous profiler\n", + "# Restauration du profileur précédent\n", "sys.setprofile(old_profiler)" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "frame=, event=call, arg=\r\n", - "frame=, event=return, arg=\r\n", - "frame=>, event=c_call, arg=\r\n" - ] - } - ], + "outputs": [], "source": [ "!python my_profiler_example.py" ] @@ -1542,9 +1042,9 @@ "source": [ "Notre fonction de supervision est appelée 3 fois:\n", "\n", - "* Au démarrage de square\n", - "* Au moment du return de square\n", - "* Au début de *sys.setprofile*\n", + "* Au démarrage de `square`\n", + "* Au moment du `return` de `square`\n", + "* Au début de `sys.setprofile`\n", "\n", "Grâce à cette fonction et aux modules d'instrospection de Python, il devient possible de créer ses propres sondes de monitoring de code." ] @@ -1554,7 +1054,7 @@ "metadata": {}, "source": [ "## Exercice relativement difficile\n", - "En vous inpirant de la seconde version du code générant le graphe de la suite de Padovan, créer une fonction d'analyse générique qui crée le graphe d'appel des fonctions exécutées entre les 2 instructions *sys.setprofile* ci-dessus.\n", + "En vous inpirant de la seconde version du code générant le graphe de la suite de Padovan, créer une fonction d'analyse générique qui crée le graphe d'appel des fonctions exécutées entre les 2 instructions `sys.setprofile` ci-dessus.\n", "\n", "Il vous sera beaucoup plus facile de définir cette fonction d'analyse comme une méthode de classe, ce qui permettra de manipuler le graphe et les autres paramètres dont vous pourriez avoir besoin comme attributs des instances de la classe.\n", "\n", @@ -1582,7 +1082,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Si vous ne connaissez pas les context manager, introduits par la [PEP343](https://www.python.org/dev/peps/pep-0343/), voici une présentation de leur fonctionnement:\n", + "Si vous ne connaissez pas les Context Manager, introduits par la [PEP343](https://www.python.org/dev/peps/pep-0343/), voici une présentation de leur fonctionnement:\n", "\n", "Quand vous écrivez:\n", "\n", @@ -1605,28 +1105,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Ce qui permet de réaliser de belles choses comme cet exemple [très inspiré d'une autre version trouvée sur stackoverflow](https://stackoverflow.com/questions/16571150/how-to-capture-stdout-output-from-a-python-function-call):\n" + "Ce qui permet de réaliser de belles choses comme cet exemple [très inspiré d'une autre version trouvée sur **stackoverflow**](https://stackoverflow.com/questions/16571150/how-to-capture-stdout-output-from-a-python-function-call):\n" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting...\n", - "Ending...\n", - "Captured text is:\n", - "One\n", - "Two\n", - "Three\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "import sys\n", "from io import StringIO\n", @@ -1638,7 +1124,7 @@ " self.new_stdout = None\n", "\n", " def __enter__(self):\n", - " # Replacing sys.stdout with a file in memory\n", + " # Remplace sys.stdout par un fichier en mémoire\n", " self.old_stdout = sys.stdout\n", " self.new_stdout = StringIO()\n", " sys.stdout = self.new_stdout\n", @@ -1646,23 +1132,23 @@ " return self\n", "\n", " def __exit__(self, exc_type, exc_val, exc_tb):\n", - " # restoring previous sys.stdout\n", + " # restauration du fichier sys.stdout précédent\n", " sys.stdout = self.old_stdout\n", "\n", " def get(self):\n", " return self.new_stdout.getvalue()\n", "\n", "\n", - "print(\"Starting...\")\n", + "print(\"Démarrage...\")\n", "\n", "with CaptureOutput() as co:\n", - " print(\"One\")\n", - " print(\"Two\")\n", - " print(\"Three\")\n", + " print(\"Un\")\n", + " print(\"Deux\")\n", + " print(\"Trois\")\n", "\n", - "print(\"Ending...\")\n", + "print(\"Extinction...\")\n", "\n", - "print(\"Captured text is:\")\n", + "print(\"Texte capturé:\")\n", "print(co.get())" ] }, @@ -1676,8 +1162,9 @@ "```python\n", "\n", "\"\"\"\n", - "GraphMe class is a context manager allowing to generate the call graph of functions\n", - "It is a very basic implementation used to demonstrate Python/inspection potential\n", + "La classe GraphMe est un gestionnaire de contexte permettant de générer le graphique d'appel des fonctions.\n", + "Il s'agit d'une implémentation très basique utilisée dans le but de démontrer le potentiel de Python/inspection.\n", + "\n", "\n", " from graphme import GraphMe\n", "\n", @@ -1700,12 +1187,12 @@ " \"\"\"\n", " GrapĥMe context manager\n", "\n", - " When writing:\n", + " Lorsque vous écrivez:\n", "\n", " with MyObject() as var:\n", " actions()\n", "\n", - " Python executes something like:\n", + " Python exécute quelque chose comme:\n", "\n", " try:\n", " var = MyObject().__enter__()\n", @@ -1713,30 +1200,31 @@ " finally:\n", " var.__exit__()\n", "\n", - " This is called context Manager, see PEP343 for more details\n", + " C'est ce qu'on appelle le Gestionnaire de Contexte, voir PEP343 pour plus de détails.\n", " https://www.python.org/dev/peps/pep-0343/\n", " \"\"\"\n", "\n", " def __init__(self, filename='graphme', format='svg'):\n", - " self.old_profile = None # Keep trace of previous profiler if any\n", - " self.graph = None # graphviz object\n", - " self.call_number = 0 # number of functions called\n", - " self.frame_ids = {} # keep trace of functions ids to manage unique identifiers and parent link\n", + " self.old_profile = None # Garde la trace du profileur précédent, s'il y en a un.\n", + " self.graph = None # objet graphviz\n", + " self.call_number = 0 # nombre de fonctions appelées\n", + " self.frame_ids = {} # garde la trace des identifiants des fonctions \n", + " # pour gérer les identifiants uniques et le lien parent\n", " self.filename = filename\n", " self.format = format\n", "\n", " def __enter__(self):\n", "\n", - " self.old_profile = sys.getprofile() # Keep profiler to restore it, if any already set\n", + " self.old_profile = sys.getprofile() # Conserver le profiler pour le restaurer, s'il y en a déjà un.\n", "\n", - " # Build the graph\n", + " # Construit le graphique\n", " self.graph = Digraph(filename=self.filename, format=self.format)\n", "\n", - " # Reset values\n", + " # Réinitialise les valeurs\n", " self.call_number = 0\n", " self.frame_ids = {}\n", "\n", - " # Set caller function used to build graph call\n", + " # Définit la fonction utilisée pour construire le graphique\n", " sys.setprofile(self.caller)\n", "\n", " return self\n", @@ -1747,22 +1235,22 @@ "\n", " def caller(self, frame, event, arg):\n", "\n", - " # Only worry about creating graph call for functions\n", + " # Seul le souci de créer des graphiques est pris en compte\n", " if event != 'call':\n", " return\n", "\n", - " # The call number is used to help identifying functions\n", + " # L'id visant à identifier les fonctions\n", " self.call_number += 1\n", "\n", - " # We cannot use frame ids to identify uniquely functions calls\n", - " # Because when a function terminates, its frame id may be reused for new functions\n", - " # generating wrong graph reference, so we use a custom id\n", + " # Impossible d'utiliser les id pour identifier de façon unique les appels de fonctions.\n", + " # Car lorsqu'une fonction se termine, son id peut être réutilisé pour de nouvelles fonctions.\n", + " # Ce qui générerait un graphique erroné, donc nous utilisons un identifiant personnalisé\n", "\n", - " # If id already exists, this is a reused one, we can overwrite it\n", + " # Si id existe déjà, il s'agit d'une version réutilisée, nous pouvons l'écraser sans problème\n", " key = id(frame)\n", " self.frame_ids[key] = str(self.call_number)\n", "\n", - " # Build frame id and parent frame id\n", + " # Création d'un id et d'un id parent\n", " frame_id = self.frame_ids[key]\n", " parent_frame = frame.f_back\n", " p_key = id(parent_frame)\n", @@ -1782,7 +1270,7 @@ " if self not in frame.f_locals.values():\n", " self.graph.node(frame_id, \"%s(%s)\" % (tb_frame.name, attrs))\n", "\n", - " # Do not add link to parent if not created in the with statement\n", + " # Ne pas ajouter de lien vers le parent si ce dernier n'est pas créé dans l'instruction\n", " if parent_frame_id is not None:\n", " self.graph.edge(parent_frame_id, frame_id)\n", "\n", @@ -1794,7 +1282,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1823,7 +1311,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1845,7 +1333,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1929,7 +1417,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1965,7 +1453,7 @@ "\n", "Après cela vous pouvez vous demander : *Mais n'est-il pas possible de faire plus simple ?* Il n'y a-t-il pas des librairies déjà toutes prêtes pour cela ?\n", "\n", - "La réponse est oui. PyCallGraph est une librairie permettant de réaliser automatiquement ce type de graphiques:\n", + "La réponse est oui. **PyCallGraph** est une librairie permettant de réaliser automatiquement ce type de graphiques:\n", "\n", "\n", "```bash\n", @@ -1977,7 +1465,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2003,7 +1491,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2030,11 +1518,11 @@ "source": [ "## KCachegrind et pyprof2calltree\n", "\n", - "[KCachegrind](https://kcachegrind.github.io) est un outil de la suite KDE permettant de visualiser des données de profilage issues de différents formats.\n", + "[**KCachegrind**](https://kcachegrind.github.io) est un outil de la suite **KDE** permettant de visualiser des données de profilage issues de différents formats.\n", "\n", - "![KCachegrind](https://kcachegrind.github.io/images/KcgShot3.gif)\n", + "![**KCachegrind**](https://kcachegrind.github.io/images/KcgShot3.gif)\n", "\n", - "[pyprof2calltree](https://github.com/pwaller/pyprof2calltree#readme) est une librairie Python qui génère des statistiques de types pStat, qu'il est ensuite possible d'afficher avec *KCachegrind*\n", + "[**pyprof2calltree**](https://github.com/pwaller/pyprof2calltree#readme) est une librairie Python qui génère des statistiques de types *pStat*, qu'il est ensuite possible d'afficher avec **KCachegrind**\n", "\n", "\n", "Il convient d'abord d'installer ces logiciels:\n", @@ -2045,26 +1533,18 @@ "bash$ pip install pyprof2calltree\n", "```\n", "\n", - "Ensuite vous lancez le profiling de votre programme avec *pyprof2calltree* et vous consultez les statistiques générées avec *kcachegrind*\n", + "Ensuite vous lancez le profiling de votre programme avec **pyprof2calltree** et vous consultez les statistiques générées avec **KCachegrind**\n", "\n", - "Reprenons l'exemple avec l'analyseur d'expressions bien parenthèsées utilisé lors de l'exercice *GraphMe*. Il possède un sympathique graphe d'exécution qui sera plus démonstratif que les fonctions padovan ou intersection précédemment manipulées.\n", + "Reprenons l'exemple avec l'analyseur d'expressions bien parenthèsées utilisé lors de l'exercice **GraphMe**. Il possède un sympathique graphe d'exécution qui sera plus démonstratif que les fonctions padovan ou intersection précédemment manipulées.\n", "\n", - "Sous Windows vous devriez pouvoir installer [qcachegrind](https://sourceforge.net/projects/qcachegrindwin/) à la place de *Kcachegrind*" + "Sous Windows vous devriez pouvoir installer [**qcachegrind**](https://sourceforge.net/projects/qcachegrindwin/) à la place de **KCachegrind**" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "True\n" - ] - } - ], + "outputs": [], "source": [ "from cProfile import Profile\n", "from pyprof2calltree import convert, visualize\n", @@ -2082,12 +1562,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Nous pouvons maintenant générer les statistiques *pStats* pour les afficher avec *kcachegrind*" + "Nous pouvons maintenant générer les statistiques *pStats* pour les afficher avec **KCachegrind**" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2107,7 +1587,7 @@ "metadata": {}, "source": [ "* La liste de gauche vous permet de visualiser la liste des fonctions appelées et leur temps d'exécution\n", - "* Les onglets en haut à droite vous permettent de visualiser la carte des fonctions appellées, avec leur imbrication et temps d'utilisation\n", + "* Les onglets en haut à droite vous permettent de visualiser la carte des fonctions appelées, avec leur imbrication et temps d'utilisation\n", "* Les onglets en bas permettent de visualiser d'autres éléments, notamment le graphe des appels pour la fonction sélectionnée." ] }, @@ -2122,25 +1602,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Le module sys de Python propose une fonction *getsizeof* qui retourne l'occupation mémoire d'une variable:" + "Le module **sys** de Python propose une fonction `getsizeof` qui retourne l'occupation mémoire d'une variable:" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "24\n", - "132\n", - "64\n", - "8000064\n" - ] - } - ], + "outputs": [], "source": [ "import sys\n", "\n", @@ -2152,23 +1621,15 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8697464\n" - ] - } - ], + "outputs": [], "source": [ "def my_range(start, stop, step=1):\n", " \"\"\"\n", - " Reproduce a simple version of Python 2 *range* function\n", + " Reproduit une version simple de la fonction *range* de Python 2\n", " \n", - " Examples:\n", + " Exemples:\n", " \n", " >>> my_range(10, 20, 2)\n", " [10, 12, 14, 16, 18]\n", @@ -2195,7 +1656,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Pour aller un peu plus loin, la librairie [Memory profiler](https://pypi.org/project/memory-profiler/) permet de calculer l'occupation mémoire de chaque ligne de code des fonctions de votre programme, un peu comme *line profiler*.\n", + "Pour aller un peu plus loin, la librairie [**Memory profiler**](https://pypi.org/project/memory-profiler/) permet de calculer l'occupation mémoire de chaque ligne de code des fonctions de votre programme, un peu comme **line profiler**.\n", "\n", "```bash\n", "bash ou C:> pip install memory_profiler\n", @@ -2206,17 +1667,9 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing my_range_memory.py\n" - ] - } - ], + "outputs": [], "source": [ "%%file my_range_memory.py\n", "\n", @@ -2225,9 +1678,9 @@ "@profile(precision=4)\n", "def my_range(start, stop, step=1):\n", " \"\"\"\n", - " Reproduce a simple version of Python 2 *range* function\n", + " Reproduire une version simple de la fonction *range* de Python 2\n", " \n", - " Examples:\n", + " Exemples:\n", " \n", " >>> my_range(10, 20, 2)\n", " [10, 12, 14, 16, 18]\n", @@ -2253,47 +1706,11 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Filename: my_range_memory.py\r\n", - "\r\n", - "Line # Mem usage Increment Line Contents\r\n", - "================================================\r\n", - " 4 36.9414 MiB 36.9414 MiB @profile(precision=4)\r\n", - " 5 def my_range(start, stop, step=1):\r\n", - " 6 \"\"\"\r\n", - " 7 Reproduce a simple version of Python 2 *range* function\r\n", - " 8 \r\n", - " 9 Examples:\r\n", - " 10 \r\n", - " 11 >>> my_range(10, 20, 2)\r\n", - " 12 [10, 12, 14, 16, 18]\r\n", - " 13 \r\n", - " 14 >>> my_range(20, 10, -2)\r\n", - " 15 [20, 18, 16, 14, 12]\r\n", - " 16 \r\n", - " 17 >>> my_range(10, 20, -1)\r\n", - " 18 []\r\n", - " 19 \"\"\"\r\n", - " 20 36.9414 MiB 0.0000 MiB result = []\r\n", - " 21 \r\n", - " 22 76.0156 MiB 0.0000 MiB while start * step < stop * step:\r\n", - " 23 76.0156 MiB 0.5117 MiB result.append(start)\r\n", - " 24 76.0156 MiB 0.2539 MiB start += step\r\n", - " 25 \r\n", - " 26 76.0156 MiB 0.0000 MiB return result\r\n", - "\r\n", - "\r\n" - ] - } - ], + "outputs": [], "source": [ "!python my_range_memory.py" ] @@ -2302,7 +1719,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Ici la démonstration n'est pas très concluante car la ligne append est répétée et l'incrément correspond à celui de la dernière exécution, mais on observe tout de même que la mémoire a doublé entre le début et la fin de la fonction." + "Ici la démonstration n'est pas très concluante car la ligne *append* est répétée et l'incrément correspond à celui de la dernière exécution. Toutefois on observe tout de même que la mémoire a doublé entre le début et la fin de la fonction." ] }, { @@ -2315,17 +1732,9 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Overwriting my_range_memory.py\n" - ] - } - ], + "outputs": [], "source": [ "%%file my_range_memory.py\n", "\n", @@ -2333,9 +1742,9 @@ "\n", "def my_range(start, stop, step=1):\n", " \"\"\"\n", - " Reproduce a simple version of Python 2 *range* function\n", + " Reproduire une version simple de la fonction *range* de Python 2\n", " \n", - " Examples:\n", + " Exemples:\n", " \n", " >>> my_range(10, 20, 2)\n", " [10, 12, 14, 16, 18]\n", @@ -2365,26 +1774,9 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Filename: my_range_memory.py\r\n", - "\r\n", - "Line # Mem usage Increment Line Contents\r\n", - "================================================\r\n", - " 27 36.8 MiB 36.8 MiB @profile\r\n", - " 28 def memtest():\r\n", - " 29 75.8 MiB 39.0 MiB a = my_range(0, 10**6)\r\n", - " 30 114.4 MiB 38.6 MiB b = my_range(0, 10**6)\r\n", - "\r\n", - "\r\n" - ] - } - ], + "outputs": [], "source": [ "!python my_range_memory.py" ] @@ -2393,7 +1785,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Il est intéressant de voir que la mémoire a augmenté de 39Mib lors de la création de la variable `a`, alors que la taille de `my_range` pour le premier million d'entiers est de 8mo environ, comme vu précédemment. En fait, la fonction append alloue plus qu'un élément à chaque appel: si la liste n'a plus de place, le tableau est incrémenté de 10 éléments la première fois que la limite de stockage est atteinte, puis 100, puis 1000 etc... \n", + "Il est intéressant de voir que la mémoire a augmenté de 39Mib lors de la création de la variable `a`, alors que la taille de `my_range` pour le premier million d'entiers est de 8mo environ, comme vu précédemment. En fait, la fonction *append* alloue plus qu'un élément à chaque appel: si la liste n'a plus de place, le tableau est incrémenté de 10 éléments la première fois que la limite de stockage est atteinte, puis 100, puis 1000 etc... \n", "\n" ] }, @@ -2406,28 +1798,9 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "mprof: Sampling memory every 0.1s\n", - "running as a Python program...\n", - "Filename: my_range_memory.py\n", - "\n", - "Line # Mem usage Increment Line Contents\n", - "================================================\n", - " 27 37.0 MiB 37.0 MiB @profile\n", - " 28 def memtest():\n", - " 29 75.7 MiB 38.7 MiB a = my_range(0, 10**6)\n", - " 30 115.0 MiB 39.3 MiB b = my_range(0, 10**6)\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "!mprof run my_range_memory.py" ] @@ -2440,7 +1813,7 @@ "bash ou c:> mprof plot\n", "```\n", "\n", - "La librairie matplotlib doit être installée pour cela." + "La librairie **matplotlib** doit être installée pour cela." ] }, { @@ -2456,15 +1829,15 @@ "source": [ "### Jupyter memit et mprun\n", "\n", - "La librairie *memory_profiler* propose aussi des mots clefs magiques pour Jupyter:\n", + "La librairie **memory_profiler** propose aussi des mots clefs magiques pour **Jupyter**:\n", "\n", - "* **memit** : Mesure l'occupation mémoire d'une instruction ou cellule\n", - "* **mprun** : Mesure l'encombrement mémoire d'un fonction ligne par ligne" + "* *%memit* : Mesure l'occupation mémoire d'une instruction ou cellule\n", + "* *%mprun* : Mesure l'encombrement mémoire d'un fonction ligne par ligne" ] }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2473,18 +1846,9 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "peak memory: 167.82 MiB, increment: 35.37 MiB\n", - "peak memory: 140.32 MiB, increment: 0.00 MiB\n" - ] - } - ], + "outputs": [], "source": [ "%memit my_range(0, 10**6)\n", "%memit range(0, 10**6)" @@ -2492,17 +1856,9 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n" - ] - } - ], + "outputs": [], "source": [ "from my_range_memory import my_range\n", "%mprun -f my_range my_range(0, 10**4)" @@ -2530,7 +1886,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2542,22 +1898,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**timeit** nous montre que la fonction est lente: près de 2 secondes pour padovan de 60" + "**timeit** nous montre que la fonction est lente: près de 2 secondes pour `padovan` de 60" ] }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.93 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit -n 1 -r 1 padovan(60)" ] @@ -2566,40 +1914,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**cProfile** nous permet de comprendre pourquoi : padovan(60) appelle 30 693 571 fois la fonction !" + "**cProfile** nous permet de comprendre pourquoi : `padovan(60)` appelle 30 693 571 fois la fonction !" ] }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 30693590 function calls (20 primitive calls) in 4.089 seconds\n", - "\n", - " Ordered by: standard name\n", - "\n", - " ncalls tottime percall cumtime percall filename:lineno(function)\n", - "30693571/1 4.089 0.000 4.089 4.089 :1(padovan)\n", - " 1 0.000 0.000 4.089 4.089 :4()\n", - " 1 0.000 0.000 0.000 0.000 :5()\n", - " 2 0.000 0.000 0.000 0.000 codeop.py:132(__call__)\n", - " 2 0.000 0.000 0.000 0.000 hooks.py:142(__call__)\n", - " 2 0.000 0.000 0.000 0.000 hooks.py:207(pre_run_code_hook)\n", - " 2 0.000 0.000 0.000 0.000 interactiveshell.py:1258(user_global_ns)\n", - " 2 0.000 0.000 4.089 2.045 interactiveshell.py:3230(run_code)\n", - " 2 0.000 0.000 0.000 0.000 ipstruct.py:125(__getattr__)\n", - " 2 0.000 0.000 0.000 0.000 {built-in method builtins.compile}\n", - " 2 0.000 0.000 4.089 2.045 {built-in method builtins.exec}\n", - " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "from cProfile import Profile\n", "pr = Profile()\n", @@ -2618,7 +1940,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2648,27 +1970,19 @@ "Il y a différentes manières d'optimiser cette fonction :\n", "\n", "* Eviter la récursivité avec une version itérative\n", - "* Eviter de recalculer les valeurs déjà calculées avec un mécanisme de cache" + "* Eviter de recalculer les valeurs déjà calculées à l'aide d'un mécanisme de cache" ] }, { "cell_type": "code", - "execution_count": 56, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.72 µs ± 28 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "def padovan(n):\n", " \"\"\"\n", - " Python web site has a nice implementation of Fibonacci on its home page\n", - " Here is an adjustment for Padovan\n", + " Le site web de Python montre une belle implémentation de Fibonacci sur sa page d'accueil\n", + " Voici une adaptation pour Padovan\n", " \"\"\"\n", " a, b, c = 1, 1, 1\n", " \n", @@ -2682,7 +1996,7 @@ "\n", "for n, value in enumerate([1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12]):\n", " r = padovan(n)\n", - " assert r == value, f'Bad value for n={n} expected {value} got {r}'\n", + " assert r == value, f'Mauvaise valeur pour n={n} valeur attendue:{value} valeur obtenue:{r}'\n", " \n", "%timeit padovan(60)" ] @@ -2691,9 +2005,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Mais toutes les fonctions ne peuvent pas se ré-écrire aussi facilement. Dans ce cas, lorsqu'une fonction déterministe demande un long temps de calcul, il est possible de mettre en cache les valeurs déjà calculées pour ne pas les recalculer aux appels suivants si la fonction est rappelée avec les mêmes paramètres.\n", + "Reste que toutes les fonctions ne peuvent pas se ré-écrire aussi facilement. Dans ce cas, lorsqu'une fonction déterministe demande un long temps de calcul, il est possible de mettre en cache les valeurs déjà calculées afin de ne pas les recalculer aux appels suivants, si la fonction est rappelée avec les mêmes paramètres.\n", "\n", - "Nous entendons ici par fonction déterministe une fonction qui retourne toujours la même valeur si on l'appelle avec les mêmes paramètres. \n", + "Par fonction déterministe nous entendons ici une fonction qui retourne toujours la même valeur si on l'appelle avec les mêmes paramètres. \n", "\n", "* Le calcul du nième nombre premier sera une fonction déterministe\n", "* Le calcul de la nième décimale de PI en sera une autre\n", @@ -2709,33 +2023,23 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "17.7 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "76.6 ns ± 0.937 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n", - "{0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 6: 4, 5: 3, 7: 5, 9: 9, 8: 7, 10: 12, 12: 21, 11: 16, 13: 28, 15: 49, 14: 37, 16: 65, 18: 114, 17: 86, 19: 151, 21: 265, 20: 200, 22: 351, 24: 616, 23: 465, 25: 816, 27: 1432, 26: 1081, 28: 1897, 30: 3329, 29: 2513, 31: 4410, 33: 7739, 32: 5842, 34: 10252, 36: 17991, 35: 13581, 37: 23833, 39: 41824, 38: 31572, 40: 55405, 42: 97229, 41: 73396, 43: 128801, 45: 226030, 44: 170625, 46: 299426, 48: 525456, 47: 396655, 49: 696081, 51: 1221537, 50: 922111, 52: 1618192, 54: 2839729, 53: 2143648, 55: 3761840, 57: 6601569, 56: 4983377, 58: 8745217, 60: 15346786}\n" - ] - } - ], + "outputs": [], "source": [ "cache_pado = {0 : 1, 1 : 1, 2 : 1}\n", "\n", "def padovan(n):\n", " \n", - " # check if n is a key of cache, if not, compute it and store it in cache\n", + " # vérifier si n est une clé de cache, sinon calculer n et la stocker dans le cache\n", " if n not in cache_pado:\n", " cache_pado[n] = padovan(n - 3) + padovan(n - 2)\n", " \n", " return cache_pado[n]\n", "\n", - "%timeit -n 1 -r 1 padovan(60) # First call is a bit longer \n", - " # because all 60 first values need to be computed\n", - "%timeit padovan(60) # already in cache, so very fast\n", + "%timeit -n 1 -r 1 padovan(60) # Le premier appel est un peu plus long \n", + " # parce que les 60 premières valeurs doivent être calculées\n", + "%timeit padovan(60) # déjà en cache, donc très rapide\n", "print(cache_pado)" ] }, @@ -2743,37 +2047,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Cette implémentation est sympathique mais a l'inconvénient de nécessiter une variable globale. Ce n'est pas très efficace ni élégant. On peut rendre cela un brin plus propre." + "Cette implémentation est sympathique mais a l'inconvénient de nécessiter une variable globale `cache_pado`. Ce n'est pas très efficace ni élégant. On peut rendre cela un brin plus propre." ] }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "24.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "104 ns ± 1.73 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n", - "{0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 6: 4, 5: 3, 7: 5, 9: 9, 8: 7, 10: 12, 12: 21, 11: 16, 13: 28, 15: 49, 14: 37, 16: 65, 18: 114, 17: 86, 19: 151, 21: 265, 20: 200, 22: 351, 24: 616, 23: 465, 25: 816, 27: 1432, 26: 1081, 28: 1897, 30: 3329, 29: 2513, 31: 4410, 33: 7739, 32: 5842, 34: 10252, 36: 17991, 35: 13581, 37: 23833, 39: 41824, 38: 31572, 40: 55405, 42: 97229, 41: 73396, 43: 128801, 45: 226030, 44: 170625, 46: 299426, 48: 525456, 47: 396655, 49: 696081, 51: 1221537, 50: 922111, 52: 1618192, 54: 2839729, 53: 2143648, 55: 3761840, 57: 6601569, 56: 4983377, 58: 8745217, 60: 15346786}\n" - ] - } - ], + "outputs": [], "source": [ "def padovan(n):\n", " \n", - " # check if n is a key of cache, if not, compute it and store it in cache\n", + " # vérifier si n est une clé de cache, sinon calculer n et la stocker dans le cache\n", " if n not in padovan.cache:\n", " padovan.cache[n] = padovan(n - 3) + padovan(n - 2)\n", " \n", " return padovan.cache[n]\n", "\n", "padovan.cache = {0 : 1, 1 : 1, 2 : 1}\n", - "%timeit -n 1 -r 1 padovan(60) # First call is a bit longer \n", - " # because all 60 first values need to be computed\n", - "%timeit padovan(60) # already in cache, so very fast\n", + "%timeit -n 1 -r 1 padovan(60) # Le premier appel est un peu plus long \n", + " # parce que les 60 premières valeurs doivent être calculées\n", + "%timeit padovan(60) # déjà en cache, donc très rapide\n", "\n", "print(padovan.cache)" ] @@ -2787,32 +2081,21 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "62.9 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "118 ns ± 2.04 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n", - "1 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "116 ns ± 0.812 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n" - ] - } - ], + "outputs": [], "source": [ "from functools import wraps\n", "from time import sleep\n", "\n", "def cache(func_to_patch):\n", " \n", - " cache = {} # local variable caught using closure\n", - " # We also could keep the cache as an attribute of patch, just as before\n", + " cache = {} # variable locale capturée à l'aide d'une [Fermeture](https://fr.wikipedia.org/wiki/Fermeture_(informatique))\n", + " # Nous pourrions aussi garder le cache comme attribut de patch, tout comme avant\n", " def patch(*args):\n", " \"\"\"patch will replace func_to_patch but keep reference to original func_to_patch\"\"\"\n", " if args not in cache:\n", - " cache[args] = func_to_patch(*args) # call original function\n", + " cache[args] = func_to_patch(*args) # Appeler la fonction d'origine\n", " \n", " return cache[args]\n", " \n", @@ -2822,15 +2105,15 @@ "@cache\n", "def padovan(n):\n", " \"\"\"\n", - " when writing @something before a function name, python creates the function, then executes\n", + " en écrivant @something avant le nom d'une fonction, python crée la fonction, puis l'exécute\n", "\n", " function = something(function)\n", " \n", - " here, this means\n", + " ici, cela signifie que\n", " \n", " padovan = cache(padovan)\n", " \n", - " So padovan is replaced by the result of cache: the patch function\n", + " Ainsi padovan est remplacé par le résultat du cache : la fonction patch\n", " \n", " \"\"\"\n", " return 1 if n <= 2 else padovan(n - 3) + padovan(n - 2)\n", @@ -2851,92 +2134,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Un décorateur peut aussi s'écrire en version objet." + "Un décorateur peut aussi s'écrire en version *objet*." ] }, { "cell_type": "code", - "execution_count": 60, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "33.6 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "176 ns ± 1.48 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n" - ] - }, - { - "data": { - "text/plain": [ - "{(0,): 1,\n", - " (1,): 1,\n", - " (3,): 2,\n", - " (2,): 1,\n", - " (4,): 2,\n", - " (6,): 4,\n", - " (5,): 3,\n", - " (7,): 5,\n", - " (9,): 9,\n", - " (8,): 7,\n", - " (10,): 12,\n", - " (12,): 21,\n", - " (11,): 16,\n", - " (13,): 28,\n", - " (15,): 49,\n", - " (14,): 37,\n", - " (16,): 65,\n", - " (18,): 114,\n", - " (17,): 86,\n", - " (19,): 151,\n", - " (21,): 265,\n", - " (20,): 200,\n", - " (22,): 351,\n", - " (24,): 616,\n", - " (23,): 465,\n", - " (25,): 816,\n", - " (27,): 1432,\n", - " (26,): 1081,\n", - " (28,): 1897,\n", - " (30,): 3329,\n", - " (29,): 2513,\n", - " (31,): 4410,\n", - " (33,): 7739,\n", - " (32,): 5842,\n", - " (34,): 10252,\n", - " (36,): 17991,\n", - " (35,): 13581,\n", - " (37,): 23833,\n", - " (39,): 41824,\n", - " (38,): 31572,\n", - " (40,): 55405,\n", - " (42,): 97229,\n", - " (41,): 73396,\n", - " (43,): 128801,\n", - " (45,): 226030,\n", - " (44,): 170625,\n", - " (46,): 299426,\n", - " (48,): 525456,\n", - " (47,): 396655,\n", - " (49,): 696081,\n", - " (51,): 1221537,\n", - " (50,): 922111,\n", - " (52,): 1618192,\n", - " (54,): 2839729,\n", - " (53,): 2143648,\n", - " (55,): 3761840,\n", - " (57,): 6601569,\n", - " (56,): 4983377,\n", - " (58,): 8745217,\n", - " (60,): 15346786}" - ] - }, - "execution_count": 60, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "class Cache:\n", " \n", @@ -2946,9 +2151,9 @@ " \n", " def __call__(self, *args):\n", " \"\"\"\n", - " When you write:\n", + " Quand vous écrivez :\n", " a = Cache(func)\n", - " a(x, y, z) # here, python executes Cache.__call__(a, x, y, z)\n", + " a(x, y, z) # here, python exécute Cache.__call__(a, x, y, z)\n", " \"\"\"\n", " if args not in self.cache:\n", " self.cache[args] = self.func(*args)\n", @@ -2958,12 +2163,12 @@ "@Cache\n", "def padovan(n):\n", " \"\"\"\n", - " Here Python executes\n", + " Ici Python exécute\n", " \n", " padovan = Cache(padovan)\n", " \n", - " So, padovan is an instance of Cache\n", - " and padovan.func = \n", + " Donc, padovan est une instance de Cache\n", + " et padovan.func = \n", " \"\"\"\n", " return 1 if n <= 2 else padovan(n - 3) + padovan(n - 2)\n", " \n", @@ -2977,12 +2182,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Finalement, Python propose déjà un tel décorateur dans le module functools" + "Finalement, Python propose déjà un tel décorateur dans le module **functools**" ] }, { "cell_type": "code", - "execution_count": 61, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -2991,28 +2196,9 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "13.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "42.6 ns ± 0.775 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)\n" - ] - }, - { - "data": { - "text/plain": [ - "CacheInfo(hits=81111166, misses=60, maxsize=None, currsize=60)" - ] - }, - "execution_count": 62, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "@lru_cache(maxsize=None)\n", "def padovan(n):\n", @@ -3032,23 +2218,14 @@ "\n", "> Le plus important dans un programme ce n'est pas l'algorithme, mais la structure de données\n", "\n", - "*Peut-être [Donald Knuth](https://fr.wikipedia.org/wiki/Donald_Knuth)*, mais je n'ai pas réussi à retrouver la citation exacte" + "*Peut-être [Donald Knuth](https://fr.wikipedia.org/wiki/Donald_Knuth), mais je n'ai pas réussi à retrouver la citation exacte*" ] }, { "cell_type": "code", - "execution_count": 63, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "87.8 ms ± 1.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", - "8.86 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " \n", @@ -3070,13 +2247,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "*timeit* nous montre que lorsque l'on augmente par 10 le nombre d'éléments, le temps d'exécution passe de 0.093 sec à 9.29 sec. \n", + "**timeit** nous montre que lorsque l'on augmente par 10 le nombre d'éléments, le temps d'exécution passe de 0.093 sec à 9.29 sec. \n", "Il est multiplié par 100 !" ] }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -3084,10 +2261,10 @@ " \n", " result = []\n", " \n", - " for e1 in l1: # If M elements in l1\n", - " for e2 in l2: # If N elements in l2\n", - " if e1 == e2: # This test is executed M*N times\n", - " # If M=N, complexity is N*N = N**2 opérations \n", + " for e1 in l1: # Si M élements dans l1\n", + " for e2 in l2: # Si N élements dans l2\n", + " if e1 == e2: # Si test est exécuté M*N fois\n", + " # Si M=N, la complexité est N*N = N**2 opérations \n", " result.append(e1)\n", " \n", " return result" @@ -3104,7 +2281,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -3154,18 +2331,9 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "54.6 ms ± 614 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", - "5.42 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " \n", @@ -3199,26 +2367,17 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "17.5 ms ± 106 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", - "1.74 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " \n", " result = []\n", " \n", - " for e1 in l1: # if M éléments in l1\n", - " if e1 in l2: # M tests are executed\n", - " # But does complexity still the same... ?\n", + " for e1 in l1: # Si M éléments dans l1\n", + " if e1 in l2: # M tests sont exécutés\n", + " # Est-ce-que la complexité reste la même?\n", " result.append(e1)\n", " \n", " return result\n", @@ -3238,41 +2397,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "En fait l'opérateur *in* masque une boucle imbriquée, il compare `e1` à tous les éléments de `l2` et s'arrête lorsqu'il trouve une correspondance ou s'il arrive à la fin de `l2`. Cela reste plus rapide car cet opérateur est implémenté en langage `C` et donc plus performant qu'une boucle Python. \n", + "En fait l'opérateur *in* masque une boucle imbriquée : il compare `e1` à tous les éléments de `l2` et s'arrête lorsqu'il trouve une correspondance ou s'il arrive à la fin de `l2`. Cela reste plus rapide car cet opérateur est implémenté en langage C et donc plus performant qu'une boucle Python. \n", "\n", "Cette complexité est confirmée par ce [tableau des complexités des instructions du wiki Python](https://wiki.python.org/moin/TimeComplexity) \n", "\n", - "L'opérateur `in` a une complexité de $O(N)$, autrement dit, si la liste possède `N` éléments, `N` opérations seront exécutées." + "L'opérateur `in` a une complexité de $O(N)$, autrement dit si la liste possède `N` éléments, `N` opérations seront exécutées." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Ce tableau montre aussi que les types *set* et *dict* possèdent un opérateur *in* ayant une complexité d'ordre 1, autrement dit, si l'objet set/dict possède N éléments, l'opérateur *in* ne demandera qu'une instruction !" + "Ce tableau montre aussi que les types *set* et *dict* possèdent un opérateur *in* ayant une complexité d'ordre 1, autrement dit, si l'objet *set/dict* possède N éléments, l'opérateur *in* ne demandera qu'une instruction !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Si vous avez été observateurs, vous aurez remarqué que le code a été quelque peu transformé dans l'exemple précédent: la fonction intersection a reçu des listes et non des objets *range*" + "Si vous avez été observateurs, vous aurez remarqué que le code a été quelque peu transformé dans l'exemple précédent: la fonction `intersection` a reçu des listes et non des objets `range`" ] }, { "cell_type": "code", - "execution_count": 68, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "134 µs ± 1.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", - "1.4 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "%timeit intersection(range(1, 2000), range(1000, 3000))\n", "%timeit -n 1 -r 1 intersection(range(1, 20000), range(10000, 30000))" @@ -3282,7 +2432,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "En effet, en Python 2, la fonction range retourne une liste, mais en Python 3 cette fonction est un type à part entière qui implémente son propre opérateur *in* qui calcule si les éléments sont présents ou non. De ce fait il est extrêmement rapide et sa complexité est d'ordre $O(1)$" + "En effet, en Python 2, la fonction `range` retourne une liste, mais en Python 3 cette fonction est un type à part entière qui implémente son propre opérateur *in* qui calcule si les éléments sont présents ou non. De ce fait il est extrêmement rapide et sa complexité est d'ordre $O(1)$" ] }, { @@ -3294,32 +2444,23 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "155 µs ± 2.07 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", - "1.83 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " \n", " result = []\n", - " # We create a dictionary having l1 items as keys\n", + " # Nous créons un dictionnaire ayant l1 items comme clés\n", " d1 = {}\n", " for e1 in l1:\n", - " d1[e1] = None # We do not care about the values, just keys are important here\n", + " d1[e1] = None # Nous ne nous soucions pas des valeurs, seules les clés sont importantes ici.\n", "\n", - " # Can also be written as\n", + " # Peut aussi s'écrire comme ceci:\n", " # d1 = { e1 : None for e1 in l1 }\n", " # d1 = dict.fromkeys(l1)\n", "\n", - " # Now we check if items of l2 are keys of d1\n", + " # Vérifions maintenant si les éléments de l2 sont des clés de d1\n", " for e2 in l2:\n", " if e2 in d1:\n", " result.append(e2)\n", @@ -3342,26 +2483,26 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def intersection(l1, l2):\n", " \n", " result = []\n", - " # We create a dictionary having l1 items as keys\n", + " # Nous créons un dictionnaire ayant l1 items comme clés\n", " d1 = {}\n", " for e1 in l1:\n", - " d1[e1] = None # If M items in l1, M affectations are executed\n", + " d1[e1] = None # Si M postes en l1, M affectations sont exécutées\n", "\n", - " # Now we check if items of l2 are keys of d1\n", + " # Vérifions maintenant si les éléments de l2 sont des clés de d1\n", " for e2 in l2:\n", - " if e2 in d1: # If N items in l2, N tests are executed\n", - " # But the *in* operator computes if e2 belongs to d1 keys, it does not compare it\n", - " # to each key, so this is really fast !\n", + " if e2 in d1: # Si N points en l2, N tests sont exécutés\n", + " # Mais l'opérateur *in* calcule si e2 appartient aux touches d1, il ne le compare pas.\n", + " # à chaque touche, donc c'est très rapide !\n", " result.append(e2)\n", " \n", - " # Finally we've got N+M opérations, if N=M, 2N opérations, complexity is O(2N)\n", + " # Enfin, nous avons N+M opérations, si N=M, 2N opérations, la complexité est O(2N)\n", " \n", " return result" ] @@ -3372,12 +2513,12 @@ "source": [ "La création du dictionnaire requiert `M` affectations si `l1` possède `M` éléments. \n", "La recherche de `e2` dans les clefs de `d1` est exécutée `N` fois si `l2` possède `N` éléments. \n", - "Comme la complexité de l'opérateur `in` du dictionnaire est $O(1)$, il n'y a pas de parcours masqué et au final nous avons `M+N` opérations, soit `2N` opérations si M=N. \n", + "Comme la complexité de l'opérateur `in` du dictionnaire est $O(1)$, il n'y a pas de parcours masqué et au final nous avons `M+N` opérations, soit `2N` opérations si `M=N`. \n", "La complexité est devenue linéaire grâce à une structure de données performante. \n", "\n", "Pourquoi l'opérateur `in` dans un dictionnaire est aussi efficace ? Parce qu'il utilise une structure de données très adaptée et l'algorithme [Open Adressing](https://en.wikipedia.org/wiki/Open_addressing) pour rechercher ses clefs. \n", "\n", - "En quelque mots, un dictionnaire est un tableau indicé, où à un indice donné sont stockés et la clef et sa valeur. Quand on veut accéder à une clef, une fonction de hachage caclule l'indice de cette clef via l'algorithme précédent. Si l'indice retourné est inférieur au nombre d'éléments du tableau : la clef existe, sinon c'est une nouvelle clef. \n", + "En quelque mots, un dictionnaire est un tableau indicé, où à un indice donné sont stockés et la clef et sa valeur. Quand on veut accéder à une clef, une fonction de hachage calcule l'indice de cette clef via l'algorithme précédent. Si l'indice retourné est inférieur au nombre d'éléments du tableau : la clef existe, sinon c'est une nouvelle clef. \n", "\n", "Si vous êtes curieux, cet excellent article de *Laurent Luce* explique plus en détails [l'implémentation des dictionnaires](https://www.laurentluce.com/posts/python-dictionary-implementation/)." ] @@ -3387,28 +2528,19 @@ "metadata": {}, "source": [ "Dans cet exercice nous avons utilisé un dictionnaire ne possédant que des clefs, les valeurs n'ont pas été utilisées. \n", - "Ce type de dictionnaire est appelé un **set** en Python et constitue un type à part entière avec de véritables opérations ensemblistes. \n", + "Ce type de dictionnaire est appelé un *set* en Python et constitue un type à part entière avec de véritables opérations ensemblistes. \n", "\n", - "* Un set ne peut avoir qu'une seule occurrence d'une même valeur\n", - "* Un set ne peut contenir que des types non modifiables (non mutable)\n", + "* Un *set* ne peut avoir qu'une seule occurrence d'une même valeur\n", + "* Un *set* ne peut contenir que des types non modifiables (non mutable)\n", "\n", "Son opérateur d'intersection `&` est implémenté grosso-modo comme dans l'exemple précédent avec le dictionnaire." ] }, { "cell_type": "code", - "execution_count": 71, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "87.1 µs ± 1.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n", - "2.28 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " s1 = set(l1)\n", @@ -3434,18 +2566,9 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "249 µs ± 5.45 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n", - "2.76 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", " \"\"\"Suppose that l1 and l2 are ordered\"\"\"\n", @@ -3460,13 +2583,13 @@ " e1 = l1[ind1]\n", " e2 = l2[ind2]\n", " \n", - " if e1 == e2: # match found\n", + " if e1 == e2: # résultat trouvé\n", " result.append(e1)\n", - " # increment both indices\n", + " # incrémenter les deux indices\n", " ind1 += 1\n", " ind2 += 1\n", " elif e1 < e2:\n", - " # e1 is lower than e2, search next element in l1\n", + " # e1 est inférieur à e2, rechercher l'élément suivant dans l1\n", " ind1 += 1\n", " else:\n", " ind2 += 1\n", @@ -3488,21 +2611,12 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "261 µs ± 5.53 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n", - "3 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" - ] - } - ], + "outputs": [], "source": [ "def intersection(l1, l2):\n", - " \"\"\"l1 and l1 are sorted after the function: dangerous\"\"\"\n", + " \"\"\"l1 et l1 sont triés après la fonction : dangereux\"\"\"\n", " \n", " l1.sort()\n", " l2.sort() \n", @@ -3517,13 +2631,13 @@ " e1 = l1[ind1]\n", " e2 = l2[ind2]\n", " \n", - " if e1 == e2: # match found\n", + " if e1 == e2: # résultat trouvé\n", " result.append(e1)\n", - " # increment both indices\n", + " # incrémenter les deux indices\n", " ind1 += 1\n", " ind2 += 1\n", " elif e1 < e2:\n", - " # e1 is lower than e2, search next element in l1\n", + " # e1 est inférieur à e2, rechercher l'élément suivant dans l1\n", " ind1 += 1\n", " else:\n", " ind2 += 1\n", @@ -3547,8 +2661,8 @@ "source": [ "Pour creuser le sujet voici quelques ouvrages :\n", "\n", - "* Le livre [Problem Solving with Algorithms and Data Structures using Python](https://runestone.academy/runestone/books/published/pythonds/index.html) en license creative commons. \n", - "* Le site PacktPublishing propose aussi plusieurs livre sur le theme [Python Data structures and algorithms](https://subscription.packtpub.com/search?query=python%20data%20structure&released=Available) \n", + "* Le livre [Problem Solving with Algorithms and Data Structures using Python](https://runestone.academy/runestone/books/published/pythonds/index.html) en *License Creative Commons*. \n", + "* Le site **PacktPublishing** propose aussi plusieurs livre sur le theme [Python Data structures and algorithms](https://subscription.packtpub.com/search?query=python%20data%20structure&released=Available) \n", "\n" ] }, @@ -3563,7 +2677,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Nous avons vu que notre fonction *my_range* n'est pas intéressante dans sa version actuelle: elle retourne une liste d'éléments. \n", + "Nous avons vu que notre fonction `my_range` n'est pas intéressante dans sa version actuelle: elle retourne une liste d'éléments. \n", "Si vous voulez l'utiliser pour compter de 1 à 1 million dans une boucle *for*, vous allez allouer 1 million d'entiers :\n", "\n", "* Cela demandera de la mémoire\n", @@ -3574,39 +2688,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "C'est la réflexion que se sont faits les développeurs de Python 3. En Python 2, beaucoup de fonctions qui retournaient de véritables listes, comme *range*, *filter*, *map*, *zip* sont devenues des générateurs/itérateurs ou types à part entière pour contourner ce problème." + "C'est la réflexion que se sont faits les développeurs de Python 3. En Python 2, beaucoup de fonctions qui retournaient de véritables listes, comme `range`, `filter`, `map`, `zip` sont devenues des générateurs/itérateurs ou types à part entière pour contourner ce problème." ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\r\n" - ] - } - ], + "outputs": [], "source": [ "!python2 -c \"a = range(10); print type(a), a\"" ] }, { "cell_type": "code", - "execution_count": 75, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " range(0, 10)\r\n" - ] - } - ], + "outputs": [], "source": [ "!python3 -c \"a = range(10); print(type(a), a)\"" ] @@ -3619,9 +2717,9 @@ "\n", "Un générateur est une fonction qui au lieu de retourner une liste de valeurs, retourne la première valeur au premier appel, puis la seconde au second appel et ainsi de suite.\n", "\n", - "Pour permettre ce comportement votre fonction ne doit pas retourner de valeur avec le mot clef *return* mais avec un nouveau mot clef **yield** qui retourne une valeur et met la fonction en pause. \n", + "Pour permettre ce comportement votre fonction ne doit pas retourner de valeur avec le mot clef `return` mais avec un nouveau mot clef **`yield`** qui retourne une valeur et met la fonction en pause. \n", "\n", - "A l'appel suivant, la fonction reprend juste après l'instruction *yield*\n", + "A l'appel suivant, la fonction reprend juste après l'instruction `yield`\n", "\n" ] }, @@ -3640,33 +2738,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Si nous reprenons la version initiale de *my_range*" + "Si nous reprenons la version initiale de `my_range`" ] }, { "cell_type": "code", - "execution_count": 76, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5\n", - "4\n", - "3\n", - "2\n", - "1\n", - "0\n" - ] - } - ], + "outputs": [], "source": [ "def my_range(start, stop, step=1):\n", " \"\"\"\n", - " Reproduce a simple version of Python 2 *range* function\n", + " Reproduire une version simple de la fonction *range* de Python 2\n", " \n", - " Examples:\n", + " Exemples:\n", " \n", " >>> my_range(10, 20, 2)\n", " [10, 12, 14, 16, 18]\n", @@ -3698,26 +2783,13 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5\n", - "4\n", - "3\n", - "2\n", - "1\n", - "0\n" - ] - } - ], + "outputs": [], "source": [ "def my_range_gen(start, stop, step=1):\n", " \"\"\"\n", - " generator version of my_range\n", + " version générateur de my_range\n", " \"\"\"\n", " # result = [] # no more requires\n", " \n", @@ -3741,17 +2813,9 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "8697464 120\n" - ] - } - ], + "outputs": [], "source": [ "import sys\n", "print(sys.getsizeof(my_range(0, 10**6)), sys.getsizeof(my_range_gen(0, 10**6)))" @@ -3766,37 +2830,18 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Before yield, start= 3\n", - "3\n", - "After yield, start= 3\n", - "Before yield, start= 2\n", - "2\n", - "After yield, start= 2\n", - "Before yield, start= 1\n", - "1\n", - "After yield, start= 1\n", - "Before yield, start= 0\n", - "0\n", - "After yield, start= 0\n" - ] - } - ], + "outputs": [], "source": [ "def my_range_gen(start, stop, step=1):\n", " \"\"\"\n", - " generator version of my_range\n", + " version générateur de my_range\n", " \"\"\"\n", " while start * step < stop * step:\n", - " print(\"Before yield, start=\", start)\n", + " print(\"Avant yield, start=\", start)\n", " yield start\n", - " print(\"After yield, start=\", start)\n", + " print(\"Après yield, start=\", start)\n", " start += step\n", "\n", " \n", @@ -3809,7 +2854,7 @@ "metadata": {}, "source": [ "Au premier appel de la fonction par le *for* la fonction s'exécute et affiche `Before yield, start= 3` puis retourne `3` et se met en pause. \n", - "La boucle *for* reprend la main, affiche la valeur `3` puis rappelle la fonction, qui se réveille et continue juste ou elle s'était arrêtée, c'est à dire après le mot clef *yield* et affiche `After yield, start= 3` \n", + "La boucle *for* reprend la main, affiche la valeur `3` puis rappelle la fonction, qui se réveille, continue juste où elle s'était arrêtée, c'est-à-dire après le mot clef `yield` et affiche `After yield, start= 3` \n", "Et ainsi de suite." ] }, @@ -3822,32 +2867,13 @@ }, { "cell_type": "code", - "execution_count": 80, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3\n", - " 10\n", - " 15\n", - "2\n", - " 10\n", - " 15\n", - "1\n", - " 10\n", - " 15\n", - "0\n", - " 10\n", - " 15\n" - ] - } - ], + "outputs": [], "source": [ "def my_range_gen(start, stop, step=1):\n", " \"\"\"\n", - " generator version of my_range\n", + " version générateur de my_range\n", " \"\"\"\n", " \n", " while start * step < stop * step:\n", @@ -3865,7 +2891,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Comment la fonction fait-elle pour ne pas se mélanger les pinceaux entre les appels imbriqués, les valeurs de *start, stop* et les incréments ?" + "Comment la fonction fait-elle pour ne pas se mélanger les pinceaux entre les appels imbriqués, les valeurs de `start`, `stop` et les incréments ?" ] }, { @@ -3874,27 +2900,27 @@ "source": [ "En fait notre première explication de la mécanique des générateurs a été quelque peu simplifiée pour faciliter la compréhension globale.\n", "\n", - "* Quand une fonction possède le mot clef *yield* Python la transforme\n", + "* Quand une fonction possède le mot clef `yield` Python la transforme\n", "* Lors de son appel elle ne s'exécute pas mais retourne une copie de la fonction originale avec ses paramètres\n", - "* Pour la démarrer il faut utiliser la fonction **next**" + "* Pour la démarrer il faut utiliser la fonction `next`" ] }, { "cell_type": "code", - "execution_count": 81, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def my_range_gen(start, stop, step=1):\n", " \"\"\"\n", - " generator version of my_range\n", + " version générateur de my_range\n", " \"\"\"\n", - " print(\"Running with values\", start, stop, step)\n", + " print(\"valeurs reçues\", start, stop, step)\n", " \n", " while start * step < stop * step:\n", - " print(\"Before yield, start=\", start)\n", + " print(\"Avant yield, start=\", start)\n", " yield start\n", - " print(\"After yield, start=\", start)\n", + " print(\"Après yield, start=\", start)\n", " start += step\n" ] }, @@ -3907,7 +2933,7 @@ }, { "cell_type": "code", - "execution_count": 82, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -3923,20 +2949,9 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 83, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "g" ] @@ -3945,24 +2960,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Il convient de démarrer `g` avec la fonction *next*" + "Il convient de démarrer `g` avec la fonction `next`" ] }, { "cell_type": "code", - "execution_count": 84, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Running with values 10 14 2\n", - "Before yield, start= 10\n", - "10\n" - ] - } - ], + "outputs": [], "source": [ "v = next(g)\n", "print(v)" @@ -3977,19 +2982,9 @@ }, { "cell_type": "code", - "execution_count": 85, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "After yield, start= 10\n", - "Before yield, start= 12\n", - "12\n" - ] - } - ], + "outputs": [], "source": [ "v = next(g)\n", "print(v)" @@ -3999,7 +2994,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Quand il n'y a plus rien à retourner, un nouvel appel à *next* génère une exception **StopIteration**" + "Quand il n'y a plus rien à retourner, un nouvel appel à `next` génère une exception **`StopIteration`**" ] }, { @@ -4030,7 +3025,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Dans une boucle *for* ceci est transparent, c'est la boucle qui exécute de manière transparente les appels à *next* et intercepte l'exception *StopIteration*" + "Dans une boucle *for* ceci est transparent: c'est la boucle qui exécute les appels à `next` et intercepte l'exception **`StopIteration`**" ] }, { @@ -4044,12 +3039,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Il y aurait beaucoup de choses à dire sur les générateurs. Nous aborderons ici uniquement quelques unes de ses limitations afin de savoir jusqu'où l'on peut aller et comment faire mieux avec les *itérateurs*" + "Il y aurait beaucoup de choses à dire sur les générateurs. Nous aborderons ici uniquement quelques-unes de ses limitations afin de savoir jusqu'où l'on peut aller et comment faire mieux avec les *itérateurs*" ] }, { "cell_type": "code", - "execution_count": 86, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -4069,7 +3064,7 @@ }, { "cell_type": "code", - "execution_count": 87, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -4079,50 +3074,27 @@ }, { "cell_type": "code", - "execution_count": 88, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "1\n", - "2\n", - "0\n", - "1\n", - "2\n" - ] - } - ], + "outputs": [], "source": [ "for v in r:\n", " print(v)\n", " \n", - "for v in r: # range is clever and allow multiple iterations\n", + "for v in r: # range est intelligent et permet des itérations multiples\n", " print(v)" ] }, { "cell_type": "code", - "execution_count": 89, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "1\n", - "2\n" - ] - } - ], + "outputs": [], "source": [ "for v in g:\n", " print(v)\n", " \n", - "for v in g: # already reached the end of the function\n", + "for v in g: # fin de la fonction déjà atteint\n", " print(v)" ] }, @@ -4135,7 +3107,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -4149,7 +3121,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -4159,49 +3131,18 @@ }, { "cell_type": "code", - "execution_count": 92, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(True, False, True, False)" - ] - }, - "execution_count": 92, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "2 in r, 10 in r, 2 in r, 10 in r" ] }, { "cell_type": "code", - "execution_count": 93, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "start= 0\n", - "start= 1\n", - "start= 2\n" - ] - }, - { - "data": { - "text/plain": [ - "(True, False, False, False)" - ] - }, - "execution_count": 93, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "2 in g, 10 in g, 2 in g, 10 in g" ] @@ -4217,7 +3158,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Ils ont aussi d'autres limites, comme l'absence des opérateurs `[]` pour lire un élément ou encore de la fonction *len*" + "Ils ont aussi d'autres limites, comme l'absence des opérateurs `[]` pour lire un élément ou encore de la fonction `len`" ] }, { @@ -4229,26 +3170,15 @@ "Pour cela 2 méthodes spéciales sont requises:\n", "\n", "* `__iter__` qui retourne l'itérateur, en général l'instance elle-même sauf si vous voulez implémenter des itérateurs *à la Java*\n", - "* `__next__` qui retourne la valeur suivante et génère *StopIteration* s'il n'y a plus rien à retourner\n", + "* `__next__` qui retourne la valeur suivante et génère **`StopIteration`** s'il n'y a plus rien à retourner\n", "\n" ] }, { "cell_type": "code", - "execution_count": 94, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3\n", - "2\n", - "1\n", - "0\n" - ] - } - ], + "outputs": [], "source": [ "class MyRange:\n", " \n", @@ -4263,14 +3193,14 @@ " \n", " def __next__(self):\n", " \n", - " # keep current value to be returned\n", + " # conserve la valeur actuelle à retourner\n", " cur = self.current\n", " \n", - " # raise exception if end is reached\n", + " # lève une exception si la fin est atteinte\n", " if cur * self.step >= self.stop * self.step:\n", " raise StopIteration()\n", " \n", - " # increment current for next call\n", + " # incrémente current pour le prochain appel\n", " self.current += self.step\n", " \n", " return cur\n", @@ -4295,18 +3225,18 @@ "```python\n", "class MyRange:\n", " \n", - " # your code here\n", + " # votre code ici\n", " pass\n", "\n", " \n", " \n", "g = MyRange(3, -1, -1)\n", "assert list(g) == [3, 2, 1, 0]\n", - "assert list(g) == [3, 2, 1, 0] # Twice use should not be empty\n", - "print(\"Twice use implemented !\")\n", + "assert list(g) == [3, 2, 1, 0] # Ne doit pas être vide à la seconde utilisation\n", + "print(\"Seconde utilisation implémentée!\")\n", "\n", "assert (2 in g, 10 in g, 2 in g) == (True, False, True)\n", - "print(\"in correctly implemented !\")\n", + "print(\"in correctement implémenté !\")\n", "```" ] }, @@ -4331,30 +3261,29 @@ " \n", " def __next__(self):\n", " \n", - " # keep current value to be returned\n", + " # conserve la valeur actuelle à retourner\n", " cur = self.current\n", " \n", - " # raise exception if end is reached\n", + " # lève une exception si la fin est atteinte\n", " if cur * self.step >= self.stop * self.step:\n", - " self.current = self.start # reset current for next iteration\n", + " self.current = self.start # réinitialise current pour la prochaine itération\n", " raise StopIteration()\n", " \n", - " # increment current for next call\n", + " # incrémente current pour le prochain appel\n", " self.current += self.step\n", " \n", " return cur\n", " \n", " def __contains__(self, value):\n", " \n", - " # Check if outside interval\n", + " # Vérifie s'il y a un intervalle extérieur\n", " if value * self.step < self.start * self.step:\n", " return False\n", "\n", - " # Check if outside interval\n", " if value * self.step >= self.stop * self.step:\n", " return False\n", " \n", - " # Value is between start and stop, check if mutiple of step minus start\n", + " # La valeur se situe entre le démarrage et l'arrêt.\n", " return (value - self.start) % self.step == 0\n", "```" ] @@ -4363,7 +3292,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Vous pouvez maintenant utiliser la version d'intersection avec une seule boucle *for* et l'opérateur *in* avec votre itérateur: elle sera très efficace puisque votre version de `__contains__` calcule (si bien implémentée) si la valeur demandée existe, plutôt que de la comparer avec tous les éléments de la liste..." + "Vous pouvez maintenant utiliser la version d'`intersection` avec une seule boucle *for* et l'opérateur *in* avec votre itérateur: elle sera très efficace puisque votre version de `__contains__` calcule (à condition d'être bien implémentée) si la valeur demandée existe, plutôt que de la comparer avec tous les éléments de la liste..." ] }, { @@ -4398,7 +3327,7 @@ "source": [ "Sa complexité sera linéaire. \n", "\n", - "Et évidemment, *intersection* mérite d'être ré-écrite avec un générateur !" + "Et évidemment, `intersection` mérite d'être ré-écrite avec un générateur !" ] }, { @@ -4420,22 +3349,22 @@ "source": [ "## Pour aller plus loin\n", "\n", - "La documentation Python de la librairie *cProfile* mérite une lecture attentive. \n", + "La documentation Python de la librairie **cProfile** mérite une lecture attentive. \n", "\n", "Il existe beaucoup d'autres librairies Python pour profiler vos programmes, dont:\n", "\n", - "* [ox_profile](https://github.com/emin63/ox_profile) : Un profileur de type statistique qui a le mérite d'offrir une intégration avec Flask.\n", - "* [statprof](https://github.com/bos/statprof.py) : Un autre profileur statistique plus complet mais qui ne fonctionne pas sous Windows\n", + "* [**ox_profile**](https://github.com/emin63/ox_profile) : Un profileur de type statistique qui a le mérite d'offrir une intégration avec **Flask**.\n", + "* [**statprof**](https://github.com/bos/statprof.py) : Un autre profileur statistique plus complet mais qui ne fonctionne pas sous Windows\n", " \n", " \n", - "Une recherche sur Pypi en présentera beaucoup d'autres.\n", + "Une recherche sur **Pypi** en présentera beaucoup d'autres.\n", "\n", "\n", - "Il existe aussi de nombreux tutoriels en ligne comme [How to Use Python Profilers: Learn the Basics](https://stackify.com/how-to-use-python-profilers-learn-the-basics/) qui présente d'autres librairies internes à Python comme *Trace* ou *FaultHandler*. Ce tutoriel a aussi l'avantage de présenter des outils de monitoring de performances comme [Zipkin](https://zipkin.io/) et [Zaeger](https://github.com/jaegertracing/jaeger). \n", + "Il existe aussi de nombreux tutoriels en ligne comme [How to Use Python Profilers: Learn the Basics](https://stackify.com/how-to-use-python-profilers-learn-the-basics/) qui présente d'autres librairies internes à Python comme **Trace** ou **FaultHandler**. Ce tutoriel a aussi l'avantage de présenter des outils de monitoring de performances comme [**Zipkin**](https://zipkin.io/) et [**Zaeger**](https://github.com/jaegertracing/jaeger). \n", "\n", - "[StackImpact](https://github.com/stackimpact/stackimpact-python) est un autre APM disposant d'une librairie Python.\n", + "[**StackImpact**](https://github.com/stackimpact/stackimpact-python) est un autre outil de type *Application Performance Management* (APM) disposant d'une librairie Python.\n", "\n", - "La série de livres [Python High Performance](https://www.packtpub.com/eu/catalogsearch/result/?q=python%20high%20performance) sur PacktPub est une très bonne source de documentation et d'approfondissement du sujet.\n", + "La série de livres [Python High Performance](https://www.packtpub.com/eu/catalogsearch/result/?q=python%20high%20performance) sur **PacktPub** est une très bonne source de documentation et d'approfondissement du sujet.\n", "\n", "Le livre [Mastering Python High Performance](https://www.packtpub.com/application-development/mastering-python-high-performance) est un excellent livre sur le profiling avec Python, il vous fera découvrir bien d'autres outils et vous plonge vraiment au coeur du sujet. Une lecture que les débutants et plus aguerris apprécieront !\n", "\n", @@ -4466,7 +3395,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.5.3" } }, "nbformat": 4, diff --git a/profiling/relecture-reference.md b/profiling/relecture-reference.md new file mode 100644 index 0000000..2bdb009 --- /dev/null +++ b/profiling/relecture-reference.md @@ -0,0 +1,39 @@ +# Semantic + +## Bold +- module python +- service externe +- language + +## Code +- variable +- fonction +- méthode +- paramètres +- class +- maths (hors $.*$) +- caractère + +## Italic +- auteur +- liens +- fonctionnalité +- mot clé jupyter +- types +- question au lecteur +- note de l'auteur +- if +- break +- append +- in +- for +- booleen +- expression française +- expression latin + +## **`...`** +- exception + +## Italic + Capitalized with each word +- terme anglais +- license