{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Itérateurs et générateurs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans cette section du cours, nous apprendrons la différence entre les itérateurs et les générateurs en Python et comment construire nos propres générateurs avec l'instruction *yield*. Les générateurs nous permettent de générer à mesure que nous avançons, au lieu de tout conserver en mémoire.\n", "\n", "Est-ce que vous vous rappelez de la fonction range() ?\n", "C'est un exemple de générateur en Python 3, c'est pour cela que nous devons utiliser la fonction list() pour afficher le résultat de son exécution, qui produit un objet qui ne fera l'itération des différentes valeurs que pendant l'exécution.\n", "\n", "Explorons un peu plus loin. Nous avons appris comment créer des fonctions avec les instructions **def** et **return**. Les fonctions de générateur permettent d'écrire une fonction qui peut renvoyer une valeur et ensuite reprendre pour reprendre là où elle s'était arrêtée. Ce type de fonction est un générateur en Python, ce qui nous permet de générer une séquence de valeurs dans le temps. La principale différence dans la syntaxe sera l'utilisation d'une déclaration **yield**.\n", "\n", "Dans la plupart des cas, une fonction générateur apparaîtra très semblable à une fonction normale. La différence principale est à la compilation, une fonction générateur devient un objet qui suit un protocole d'itération. Cela signifie que lorsqu'elles sont appelées dans votre code, elles ne renvoient pas une valeur et quittent, les fonctions générateur se suspendent automatiquement et reprennent leur exécution et l'état au dernier point de génération d'une valeur. Le principal avantage ici est de ne pas avoir à calculer toute une série de valeurs initiales et que les fonctions générateur peuvent être suspendues, cette fonctionnalité est appelée *suspension d'état*.\n", "\n", "\n", "Pour commencer à comprendre comment fonctionnent les générateurs, continuons et voyons comment nous pouvons en créer." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Fonction Générateur pour calculer le cube d'un nombre (puissance de 3)\n", "def gencubes(n):\n", " for num in range(n):\n", " yield num**3" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "8\n", "27\n", "64\n", "125\n", "216\n", "343\n", "512\n", "729\n" ] } ], "source": [ "for x in gencubes(10):\n", " print (x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Parfait !\n", "Maintenant, puisque nous avons une fonction générateur, nous n'avons pas à conserver chaque cube que nous avons créé.\n", "\n", "Les générateurs sont les plus efficaces pour calculer de grands ensembles de résultats (en particulier dans les calculs qui impliquent des boucles elles-mêmes) dans les cas où nous ne voulons pas allouer la mémoire pour tous les résultats en même temps.\n", "\n", "Voyons un autre exemple de générateur qui calcule une [suite de fibonacci](https://fr.wikipedia.org/wiki/Suite_de_Fibonacci) :" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def genfibon(n):\n", " '''\n", " Génère une suite de fibonnaci jusqu'à n\n", " '''\n", " a = 1\n", " b = 1\n", " for i in range(n):\n", " yield a\n", " a,b = b,a+b" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "1\n", "2\n", "3\n", "5\n", "8\n", "13\n", "21\n", "34\n", "55\n" ] } ], "source": [ "for num in genfibon(10):\n", " print (num)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Si c'était une fonction traditionnelle, de quoi aurai-t-elle l'air ?" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def fibon(n):\n", " a = 1\n", " b = 1\n", " sortie = []\n", " \n", " for i in range(n):\n", " sortie.append(a)\n", " a,b = b,a+b\n", " \n", " return sortie" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fibon(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notez que si nous utilisons une valeur énorme de n (comme 100.000) la deuxième fonction devra conserver chaque résultat intermédiaire, alors que dans ce cas, nous ne nous préoccupons du résultat précédent pour générer le suivant !\n", "\n", "## Les fonctions intégrées next () et iter ()\n", "La clef pour comprendre tout à fait les générateurs est la fonction next() et la fonction iter().\n", "\n", "La fonction next() nous permet d'accéder à l'élément suivant dans une séquence. Voyons cela :" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def gen_simple():\n", " for x in range(3):\n", " yield x" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Assigner gen_simple \n", "g = gen_simple()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n" ] } ], "source": [ "print (next(g))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n" ] } ], "source": [ "print (next(g))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print (next(g))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "ename": "StopIteration", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprint\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mStopIteration\u001b[0m: " ] } ], "source": [ "print (next(g))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Après avoir généré toutes les valeurs next() a provoqué une erreur StopIteration. Ce que cette erreur nous dit, c'est que toutes les valeurs ont été générées.\n", "\n", "Vous pourriez vous demander pourquoi n'obtenons-nous pas cette erreur lors de l'utilisation d'une boucle for? C'est que la boucle for détecte automatiquement cette erreur et arrête l'appel avant.\n", "\n", "Continuons pour voir comment utiliser iter(). Vous vous souvenez que les chaines sont iterables:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "b\n", "o\n", "n\n", "j\n", "o\n", "u\n", "r\n" ] } ], "source": [ "s = 'bonjour'\n", "\n", "# Iteration à travers une chaine\n", "for let in s:\n", " print (let)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mais cela ne signifie pas que la chaîne elle-même est un *itérateur* ! Nous pouvons vérifier ceci avec la fonction next():" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'str' object is not an iterator", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ms\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'str' object is not an iterator" ] } ], "source": [ "next(s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Intéressant, cela signifie qu'un objet chaîne supporte l'itération, mais nous ne pouvons pas itérer directement sur celle-ci comme nous le pourrions avec une fonction générateur. La fonction iter() va nous permettre de le faire quand même !" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": true }, "outputs": [], "source": [ "s_iter = iter(s)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'b'" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(s_iter)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'o'" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "next(s_iter)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Bravo ! Maintenant, vous savez comment convertir des objets itérables en itérateurs eux-mêmes !\n", "\n", "Le principal leçon à retenir est que l'utilisation du mot-clé yield dans une fonction va la transformer en générateur. Ce changement peut vous faire économiser beaucoup de mémoire pour des cas d'utilisation importants. Pour plus d'informations sur les générateurs, consultez (en anglais) :\n", "\n", "[Réponse Stack Overflow](http://stackoverflow.com/questions/1756096/understanding-generators-in-python)\n", "\n", "[Une autre réponse StackOverflow](http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.8" } }, "nbformat": 4, "nbformat_minor": 1 }