Comment utiliser les générateurs et les rendements en Python – Real Python

By | septembre 25, 2019

Python pas cher

Avez-vous déjà eu à travailler avec un jeu de données si grand qu’il surchargeait la mémoire de votre machine? Ou peut-être avez-vous une fonction complexe qui doit conserver un état interne à chaque appel, mais la fonction est trop petite pour justifier la création de sa propre classe. Dans ces cas et plus encore, les générateurs et la déclaration de rendement Python sont là pour vous aider.

À la fin de cet article, vous saurez:

  • Quoi générateurs sont et comment les utiliser
  • Comment créer fonctions et expressions du générateur
  • Comment le Rendement en python déclaration fonctionne
  • Comment utiliser plusieurs Instructions de rendement Python dans une fonction génératrice
  • Comment utiliser méthodes avancées de générateur
  • Comment construire des pipelines de données avec plusieurs générateurs

Si vous êtes un Pythonista débutant ou intermédiaire et que vous souhaitez apprendre à travailler avec de grands ensembles de données de manière plus pythonique, ce didacticiel est fait pour vous.

Vous pouvez obtenir une copie de l'ensemble de données utilisé dans ce tutoriel en cliquant sur le lien ci-dessous:

Utiliser des générateurs

Introduit avec PEP 255, fonctions du générateur sont un type spécial de fonction qui renvoie un itérateur paresseux. Ce sont des objets que vous pouvez parcourir comme une liste. Cependant, contrairement aux listes, les itérateurs paresseux ne stockent pas leur contenu en mémoire. Pour avoir une vue d'ensemble des itérateurs en Python, jetez un coup d'œil à Python «pour» les boucles (itération définie).

Maintenant que vous avez une idée approximative de ce que fait un générateur, vous pouvez vous demander à quoi il ressemble en action. Voyons deux exemples. Dans le premier, vous verrez comment les générateurs fonctionnent de la même manière. Vous pourrez ensuite effectuer un zoom avant et examiner chaque exemple de manière plus approfondie.

Exemple 1: lecture de gros fichiers

Un cas d'utilisation courant des générateurs est de travailler avec des flux de données ou des fichiers volumineux, tels que des fichiers CSV. Ces fichiers texte séparent les données en colonnes en utilisant des virgules. Ce format est un moyen courant de partager des données. Maintenant, que se passe-t-il si vous voulez compter le nombre de lignes dans un fichier CSV? Le bloc de code ci-dessous montre une façon de compter ces lignes:

csv_gen = csv_reader("some_csv.txt")
nombre_ligne = 0

pour rangée dans csv_gen:
    nombre_ligne + = 1

impression(F"Le nombre de lignes est row_count")

En regardant cet exemple, vous pourriez vous attendre csv_gen être une liste. Pour remplir cette liste, csv_reader () ouvre un fichier et charge son contenu dans csv_gen. Ensuite, le programme parcourt la liste et incrémente nombre_ligne pour chaque rangée.

Ceci est une explication raisonnable, mais cette conception fonctionnerait-elle encore si le fichier est très volumineux? Que se passe-t-il si le fichier est plus volumineux que la mémoire dont vous disposez? Pour répondre à cette question, supposons que csv_reader () ouvre simplement le fichier et le lit dans un tableau:

def csv_reader(nom de fichier):
    fichier = ouvert(nom de fichier)
    résultat = fichier.lis().Divisé(" n")
    revenir résultat

Cette fonction ouvre un fichier donné et utilise file.read () de même que .Divisé() d'ajouter chaque ligne en tant qu'élément distinct à une liste. Si vous deviez utiliser cette version de csv_reader () dans le bloc de code de comptage de lignes que vous avez vu plus haut, vous obtiendrez le résultat suivant:

>>>

Traceback (dernier appel le plus récent):
  Fichier "ex1_naive.py", ligne 22, dans 
    principale()
  Fichier "ex1_naive.py", ligne 13, dans principale
    csv_gen = csv_reader("fichier.txt")
  Fichier "ex1_naive.py", ligne 6, dans csv_reader
    résultat = fichier.lis().Divisé(" n")
MemoryError

Dans ce cas, ouvert() retourne un objet générateur que vous pouvez parcourir paresseusement ligne par ligne. cependant, fichier.read (). split () charge tout en mémoire en une fois, ce qui provoque la MemoryError.

Avant que cela ne se produise, vous remarquerez probablement que votre ordinateur est très lent. Vous pourriez même avoir besoin de tuer le programme avec un ClavierInterrupt. Alors, comment pouvez-vous gérer ces énormes fichiers de données? Jetez un coup d’œil à une nouvelle définition de csv_reader ():

def csv_reader(nom de fichier):
    pour rangée dans ouvert(nom de fichier, "r"):
        rendement rangée

Dans cette version, vous ouvrez le fichier, parcourez-le et donnez une ligne. Ce code devrait produire la sortie suivante, sans erreur de mémoire:

Qu'est-ce qu'il se passe ici? Eh bien, vous avez essentiellement tourné csv_reader () dans une fonction de générateur. Cette version ouvre un fichier, parcourt chaque ligne et renvoie chaque ligne au lieu de la renvoyer.

Vous pouvez également définir un expression du générateur (aussi appelé un compréhension du générateur), qui a une syntaxe très similaire à celle de la liste des compréhensions. De cette façon, vous pouvez utiliser le générateur sans appeler une fonction:

csv_gen = (rangée pour rangée dans ouvert(nom de fichier))

C'est une façon plus succincte de créer la liste csv_gen. Vous en apprendrez plus sur la déclaration de rendement Python bientôt. Pour l'instant, rappelez-vous cette différence essentielle:

  • En utilisant rendement se traduira par un objet générateur.
  • En utilisant revenir entraînera la première ligne du fichier seulement.

Exemple 2: Générer une séquence infinie

Changeons de sujet et examinons la génération de séquences infinies. En Python, pour obtenir une séquence finie, vous appelez intervalle() et l'évaluer dans un contexte de liste:

>>>

>>> une = intervalle(5)
>>> liste(une)
[0, 1, 2, 3, 4, 5]

Générer un séquence infinieCependant, il faudra utiliser un générateur, car la mémoire de votre ordinateur est finie:

def séquence infinie():
    num = 0
    tandis que Vrai:
        rendement num
        num + = 1

Ce bloc de code est court et agréable. Tout d'abord, vous initialisez la variable num et commencez une boucle infinie. Ensuite, vous immédiatement rendement num afin que vous puissiez capturer l'état initial. Cela imite l'action de intervalle().

Après rendement, vous augmentez num par 1. Si vous essayez cela avec un pour boucle, alors vous verrez que cela semble vraiment infini:

>>>

>>> pour je dans séquence infinie():
...     impression(je, fin="")
...
0 1 2 3 4 5 6 7 8 9 10 11 12 14 14 16 16 18 18 20 20 22 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39 40 41 42
[...]
6157818 6157819 6157820 6157821 6157822 6157823 6157824 6157825 6157826 6157827
6157828 6157829 6157830 6157831 6157832 6157833 6157834 6157835 6157836 6157837
6157838 6157839 6157840 6157841 6157842
ClavierInterrupt
Traceback (dernier appel le plus récent):
  Fichier "", ligne 2, dans 

Le programme continuera à s'exécuter jusqu'à ce que vous l'arrêtiez manuellement.

Au lieu d'utiliser un pour boucle, vous pouvez aussi appeler suivant() sur l'objet générateur directement. Ceci est particulièrement utile pour tester un générateur dans la console:

>>>

>>> gen = séquence infinie()
>>> suivant(gen)
0
>>> suivant(gen)
1
>>> suivant(gen)
2
>>> suivant(gen)
3

Ici, vous avez un générateur appelé gen, que vous parcourez manuellement en appelant à plusieurs reprises suivant(). Cela fonctionne comme un excellent contrôle de cohérence pour vous assurer que vos générateurs produisent le rendement que vous attendez.

Exemple 3: Détection des palindromes

Vous pouvez utiliser des séquences infinies de nombreuses manières, mais leur utilisation pratique réside dans la construction de détecteurs de palindrome. UNE détecteur palindrome localisera toutes les séquences de lettres ou de chiffres qui sont des palindromes. Ce sont des mots ou des chiffres lus de la même manière, en avant et en arrière, comme 121. Définissez d’abord votre détecteur de palindrome numérique:

def is_palindrome(num):
    # Ignorer les entrées à un chiffre
    si num // dix == 0:
        revenir Faux
    temp = num
    reverse_num = 0

    tandis que temp ! = 0:
        reverse_num = (reverse_num * dix) + (temp % dix)
        temp = temp // dix

    si num == reverse_num:
        revenir num
    autre:
        revenir Faux

Ne vous inquiétez pas trop pour comprendre les mathématiques sous-jacentes de ce code. Il suffit de noter que la fonction prend un numéro entré, l'inverse et vérifie si le nombre inversé est identique à l'original. Vous pouvez maintenant utiliser votre générateur de séquence infinie pour obtenir une liste de tous les palindromes numériques:

>>>

>>> pour je dans séquence infinie():
...     copain = is_palindrome(je)
...     si copain:
...         impression(copain)
...
11
22
33
[...]
99799
99899
99999
100001
101101
102201
ClavierInterrupt
Traceback (dernier appel le plus récent):
  Fichier "", ligne 2, dans 
  
  
  
  Fichier "", ligne 5, dans is_palindrome

Dans ce cas, les seuls numéros imprimés sur la console sont ceux qui sont identiques avant ou arrière.

Maintenant que vous avez déjà vu un cas d'utilisation simple pour un générateur de séquence infini, passons à l'analyse du fonctionnement des générateurs.

Comprendre les générateurs

Jusqu'à présent, vous avez découvert les deux méthodes principales de création de générateurs: en utilisant des fonctions de générateur et des expressions de générateur. Vous pourriez même avoir une compréhension intuitive du fonctionnement des générateurs. Prenons un moment pour rendre cette connaissance un peu plus explicite.

Les fonctions de générateur ressemblent et agissent comme des fonctions normales, mais avec une caractéristique déterminante. Les fonctions du générateur utilisent le mot-clé de rendement Python au lieu de revenir. Rappelez-vous la fonction de générateur que vous avez écrite précédemment:

def séquence infinie():
    num = 0
    tandis que Vrai:
        rendement num
        num + = 1

Cela ressemble à une définition de fonction typique, à l'exception de l'instruction de rendement Python et du code qui la suit. rendement indique où une valeur est renvoyée à l'appelant, mais contrairement à revenir, vous ne quittez pas la fonction après.

Au lieu de cela, le Etat de la fonction est rappelé. De cette façon, quand suivant() est appelé sur un objet générateur (explicitement ou implicitement dans un pour boucle), la variable précédemment cédée num est incrémenté, puis cédé à nouveau. Comme les fonctions du générateur ressemblent à d'autres fonctions et agissent de manière très similaire, vous pouvez supposer que les expressions du générateur sont très similaires aux autres compréhensions disponibles dans Python.

Générateurs de bâtiment avec expressions de générateur

Comme les listes, les expressions de générateur vous permettent de créer rapidement un objet générateur en seulement quelques lignes de code. Ils sont également utiles dans les cas où la compréhension de liste est utilisée, avec un avantage supplémentaire: vous pouvez les créer sans créer et conserver l’objet entier en mémoire avant l’itération. En d’autres termes, vous n’aurez aucune pénalité en mémoire lorsque vous utilisez des expressions de générateur. Prenons cet exemple de quadrature des chiffres:

>>>

>>> nums_squared_lc = [[[[num**2 pour num dans intervalle(5)]
>>> nums_squared_gc = (num**2 pour num dans intervalle(5))

Tous les deux nums_squared_lc et nums_squared_gc en gros la même chose, mais il y a une différence essentielle. Peux tu le repérer? Regardez ce qui se passe lorsque vous inspectez chacun de ces objets:

>>>

>>> nums_squared_lc
[0, 1, 4, 9, 16]
>>> nums_squared_gc
<objet générateur  à l'adresse 0x107fbbc78>

Le premier objet utilisait des crochets pour créer une liste, tandis que le second créait une expression génératrice à l'aide de parenthèses. La sortie confirme que vous avez créé un objet générateur et qu’il est distinct de la liste.

Profilage des performances du générateur

Vous avez appris plus tôt que les générateurs sont un excellent moyen d'optimiser la mémoire. Bien qu'un générateur de séquence infini soit un exemple extrême de cette optimisation, développons les exemples de quadrature numérique que vous venez de voir et inspectez la taille des objets résultants. Vous pouvez le faire avec un appel à sys.getsizeof ():

>>>

>>> importation sys
>>> nums_squared_lc = [[[[je * 2 pour je dans intervalle(10000)]
>>> sys.se taille de(nums_squared_lc)
87624
>>> nums_squared_gc = (je ** 2 pour je dans intervalle(10000))
>>> impression(sys.se taille de(nums_squared_gc))
120

Dans ce cas, la liste que vous obtenez de la compréhension de liste est de 87 624 octets, alors que l’objet générateur n’est que de 120. Cela signifie que la liste est plus de 700 fois plus grande que l’objet générateur!

Cependant, il y a une chose à garder à l'esprit. Si la liste est plus petite que la mémoire disponible de la machine en cours d'exécution, la compréhension de la liste peut être plus rapide à évaluer que l'expression du générateur équivalent. Pour explorer cela, résumons les résultats des deux interprétations ci-dessus. Vous pouvez générer une lecture avec cProfile.run ():

>>>

>>> importation cProfile
>>> cProfile.courir('somme([i * 2 for i in range(10000)]) ')
                                    5 appels de fonction en 0.001 secondes

            Commandé par: nom standard

            ncalls tottime percall cumtime percall nom de fichier: lineno (function)
                                1 0.001 0.001 0.001 0.001 :1()
                                1 0.000 0.000 0.001 0.001 :1()
                                1 0.000 0.000 0.001 0.001 méthode intégrée builtins.exec
                                1 0.000 0.000 0.000 0.000 méthode intégrée builtins.sum
                                1 0.000 0.000 0.000 0.000 méthode 'disable' des objets '_lsprof.Profiler'


>>> cProfile.courir('somme ((i * 2 pour i dans l'intervalle (10000)))')
                                    10005 appels de fonction en 0.003 secondes

            Commandé par: nom standard

            ncalls tottime percall cumtime percall nom de fichier: lineno (function)
                10001 0,002 0,000 0,002 0,000 :1()
                                1 0.000 0.000 0.003 0.003 :1()
                                1 0.000 0.000 0.003 0.003 méthode intégrée builtins.exec
                                1 0.001 0.001 0.003 0.003 méthode intégrée builtins.sum
                                1 0.000 0.000 0.000 0.000 méthode 'disable' des objets '_lsprof.Profiler'

Ici, vous pouvez voir que la somme de toutes les valeurs de la liste comprend environ un tiers du temps nécessaire à la somme du générateur. Si la rapidité est un problème et que la mémoire ne l’est pas, une compréhension de la liste est probablement un meilleur outil pour le travail.

N'oubliez pas que les compréhensions de liste renvoient des listes complètes, tandis que les expressions de générateur renvoient des générateurs. Les générateurs fonctionnent de la même manière, qu’ils soient construits à partir d’une fonction ou d’une expression. Utiliser une expression vous permet simplement de définir des générateurs simples sur une seule ligne, avec une rendement à la fin de chaque itération intérieure.

La déclaration de rendement Python est certainement le pivot sur lequel reposent toutes les fonctionnalités des générateurs. Nous allons donc plonger dans la manière dont rendement fonctionne en Python.

Comprendre la déclaration de rendement Python

Dans l'ensemble, rendement est une déclaration assez simple. Son travail principal consiste à contrôler le flux d’une fonction génératrice de la même manière que revenir déclarations. Comme mentionné brièvement ci-dessus, cependant, la déclaration de rendement Python a quelques astuces dans sa manche.

Lorsque vous appelez une fonction de générateur ou utilisez une expression de générateur, vous renvoyez un itérateur spécial appelé générateur. Vous pouvez affecter ce générateur à une variable afin de l'utiliser. Lorsque vous appelez des méthodes spéciales sur le générateur, telles que suivant(), le code dans la fonction est exécuté jusqu'à rendement.

Lorsque l'instruction de rendement Python est atteinte, le programme suspend l'exécution de la fonction et renvoie la valeur renvoyée à l'appelant. (En revanche, revenir arrête complètement l'exécution de la fonction.) Lorsqu'une fonction est suspendue, son état est sauvegardé. Cela inclut toutes les liaisons de variables locales au générateur, le pointeur d'instruction, la pile interne et la gestion des exceptions.

Cela vous permet de reprendre l’exécution de la fonction chaque fois que vous appelez l’une des méthodes du générateur. De cette façon, toutes les évaluations de fonction reprennent juste après rendement. Vous pouvez le voir en action en utilisant plusieurs instructions de rendement Python:

>>>

>>> def multi_yield():
...     rendement_str = "Ceci imprimera la première chaîne"
...     rendement rendement_str
...     rendement_str = "Ceci imprimera la deuxième chaîne"
...     rendement rendement_str
...
>>> multi_obj = multi_yield()
>>> impression(suivant(multi_obj))
Cela va imprimer la première chaîne
>>> impression(suivant(multi_obj))
Cela va imprimer la deuxième chaîne
>>> impression(suivant(multi_obj))
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
StopIteration

Regardez de plus près les derniers appels à suivant(). Vous pouvez voir que l'exécution a explosé avec une trace. En effet, les générateurs, comme tous les itérateurs, peuvent être épuisés. À moins que votre générateur ne soit infini, vous ne pouvez le parcourir qu'une seule fois. Une fois que toutes les valeurs ont été évaluées, l’itération s’arrête et la pour la boucle va sortir. Si vous avez utilisé suivant()vous obtiendrez alors un message explicite StopIteration exception.

rendement peut être utilisé de différentes manières pour contrôler le flux d’exécution de votre générateur. L'utilisation de plusieurs déclarations de rendement Python peut être exploitée dans la mesure de votre créativité.

Utilisation de méthodes de générateur avancées

Vous avez vu les utilisations et les constructions les plus courantes des générateurs, mais il reste encore quelques astuces à couvrir. En plus de rendement, les objets générateurs peuvent utiliser les méthodes suivantes:

  • .envoyer()
  • .jeter()
  • .proche()

Comment utiliser .envoyer()

Dans cette section, vous allez créer un programme utilisant les trois méthodes. Ce programme imprimera les palindromes numériques comme avant, mais avec quelques ajustements. Lorsque vous rencontrez un palindrome, votre nouveau programme ajoute un chiffre et lance la recherche du prochain. Vous allez également gérer les exceptions avec .jeter() et arrêtez le générateur après un nombre donné de chiffres avec .proche(). Commençons par rappeler le code de votre détecteur de palindrome:

def is_palindrome(num):
    # Ignorer les entrées à un chiffre
    si num // dix == 0:
        revenir Faux
    temp = num
    reverse_num = 0

    tandis que temp ! = 0:
        reverse_num = (reverse_num * dix) + (temp % dix)
        temp = temp // dix

    si num == reverse_num:
        revenir Vrai
    autre:
        revenir Faux

C'est le même code que vous avez vu plus tôt, sauf que maintenant le programme retourne strictement Vrai ou Faux. Vous devrez également modifier votre générateur de séquence infini d’origine, comme suit:

    1 def infinite_palindromes():
    2     num = 0
    3     tandis que Vrai:
    4         si is_palindrome(num):
    5             je = (rendement num)
    6             si je est ne pas Aucun:
    sept                 num = je
    8         num + = 1

Il y a beaucoup de changements ici! Le premier que vous verrez se trouve à la ligne 5, où i = (nombre de rendement). Bien que vous ayez appris plus tôt que rendement est une déclaration, ce n’est pas tout à fait l’histoire.

À partir de Python 2.5 (la même version qui a introduit les méthodes que vous apprenez maintenant), rendement est un expression, plutôt qu'une déclaration. Bien sûr, vous pouvez toujours l'utiliser comme une déclaration. Mais maintenant, vous pouvez également l'utiliser comme vous le voyez dans le bloc de code ci-dessus, où je prend la valeur qui est cédée. Cela vous permet de manipuler la valeur cédée. Plus important encore, il vous permet de .envoyer() une valeur de retour au générateur. Quand l'exécution reprend après rendement, je prendra la valeur qui est envoyée.

Vous allez aussi vérifier si je ne suis pas aucun, ce qui pourrait arriver si suivant() est appelé sur l'objet générateur. (Cela peut aussi arriver lorsque vous parcourez un pour boucle.) Si je a une valeur, alors vous mettez à jour num avec la nouvelle valeur. Mais que ce soit ou non je contient une valeur, vous incrémenterez alors num et recommencez la boucle.

Examinons maintenant le code de la fonction principale, qui renvoie le plus petit numéro avec un autre chiffre au générateur. Par exemple, si le palindrome est 121, il sera alors .envoyer() 1000:

pal_gen = infinite_palindromes()
pour je dans pal_gen:
    chiffres = len(str(je))
    pal_gen.envoyer(dix ** (chiffres))

Avec ce code, vous créez l'objet générateur et parcourez-le. Le programme ne donne une valeur que lorsqu'un palindrome est trouvé. Il utilise len () pour déterminer le nombre de chiffres dans ce palindrome. Ensuite, il envoie 10 ** chiffres au générateur. Ceci ramène l'exécution dans la logique du générateur et attribue 10 ** chiffres à je. Puisque je a maintenant une valeur, le programme met à jour num, incrémente et vérifie à nouveau les palindromes.

Une fois que votre code trouve et donne un autre palindrome, vous pourrez effectuer une itération via le pour boucle. C’est la même chose qu’itérer avec suivant(). Le générateur passe également à la ligne 5 avec i = (nombre de rendement). Cependant, maintenant je est Aucun, car vous n’avez pas envoyé explicitement de valeur.

Ce que vous avez créé ici est un coroutine, ou une fonction génératrice dans laquelle vous pouvez transmettre des données. Celles-ci sont utiles pour la construction de pipelines de données, mais comme vous le verrez bientôt, elles ne sont pas nécessaires pour les construire. (Si vous souhaitez approfondir vos connaissances, ce cours sur les coroutines et la concurrence est l’un des traitements les plus complets disponibles.)

Maintenant que vous avez appris .envoyer(), regardons .jeter().

Comment utiliser .jeter()

.jeter() vous permet de lancer des exceptions avec le générateur. Dans l'exemple ci-dessous, vous déclenchez l'exception à la ligne 6. Ce code jettera un ValueError une fois que chiffres atteint 5:

    1 pal_gen = infinite_palindromes()
    2 pour je dans pal_gen:
    3     impression(je)
    4     chiffres = len(str(je))
    5     si chiffres == 5:
    6         pal_gen.jeter(ValueError("Nous n'aimons pas les grands palindromes"))
    sept     pal_gen.envoyer(dix ** (chiffres))

Ceci est identique au code précédent, mais vous allez maintenant vérifier si chiffres est égal à 5. Si oui, alors vous allez .jeter() une ValueError. Pour vérifier que cela fonctionne comme prévu, consultez la sortie du code:

>>>

11
111
1111
10101
Traceback (dernier appel le plus récent):
  Fichier "advanced_gen.py", ligne 47, dans 
    principale()
  Fichier "advanced_gen.py", ligne 41, dans principale
    pal_gen.jeter(ValueError("Nous n'aimons pas les grands palindromes"))
  Fichier "advanced_gen.py", ligne 26, dans infinite_palindromes
    je = (rendement num)
ValueError: Nous n'aimons pas les grands palindromes

.jeter() est utile dans les zones où vous pourriez avoir besoin d'attraper une exception. Dans cet exemple, vous avez utilisé .jeter() pour contrôler quand vous avez cessé d'itérer à travers le générateur. Vous pouvez le faire plus élégamment avec .proche().

Comment utiliser .proche()

Comme son nom l'indique, .proche() vous permet d'arrêter un générateur. Cela peut être particulièrement utile lors du contrôle d'un générateur de séquence infini. Mettons à jour le code ci-dessus en modifiant .jeter() à .proche() arrêter l'itération:

    1 pal_gen = infinite_palindromes()
    2 pour je dans pal_gen:
    3     impression(je)
    4     chiffres = len(str(je))
    5     si chiffres == 5:
    6         pal_gen.proche()
    sept     pal_gen.envoyer(dix ** (chiffres))

Au lieu d'appeler .jeter(), tu utilises .proche() ligne 6. L’avantage d’utiliser .proche() est-ce qu'il soulève StopIteration, une exception utilisée pour signaler la fin d'un itérateur fini:

>>>

11
111
1111
10101
Traceback (dernier appel le plus récent):
  Fichier "advanced_gen.py", ligne 46, dans 
    principale()
  Fichier "advanced_gen.py", ligne 42, dans principale
    pal_gen.envoyer(dix ** (chiffres))
StopIteration

Maintenant que vous en avez appris plus sur les méthodes spéciales fournies avec les générateurs, parlons de l’utilisation de générateurs pour créer des pipelines de données.

Création de pipelines de données avec des générateurs

Les pipelines de données vous permettent de chaîner du code afin de traiter de grands ensembles de données ou des flux de données sans utiliser la mémoire de votre ordinateur au maximum. Imaginez que vous ayez un gros fichier CSV:

lien permanent,compagnie,numEmps,Catégorie,ville,Etat,FundingDate,surélevé,raisedCurrency,rond
digg,Digg,60,la toile,San Francisco,Californie,1-déc-06,8500000,USD,b
digg,Digg,60,la toile,San Francisco,Californie,1-oct-05,2800000,USD,une
Facebook,Facebook,450,la toile,Palo Alto,Californie,1-SEP-04,500000,USD,ange
Facebook,Facebook,450,la toile,Palo Alto,Californie,1-Mai-05,12700000,USD,une
photobucket,Photobucket,60,la toile,Palo Alto,Californie,1-Mar-05,3000000,USD,une

Cet exemple est tiré de l'ensemble TechCrunch Continental USA, qui décrit les cycles de financement et les montants en dollars de diverses startups basées aux États-Unis. Cliquez sur le lien ci-dessous pour télécharger le jeu de données:

Il est temps de faire quelques traitements en Python! Pour montrer comment créer des pipelines avec des générateurs, vous allez analyser ce fichier pour obtenir le total et la moyenne de tous les tours de la série A de l’ensemble de données.

Pensons à une stratégie:

  1. Lire chaque ligne du fichier.
  2. Divisez chaque ligne en une liste de valeurs.
  3. Extrayez les noms de colonne.
  4. Utilisez les noms de colonnes et les listes pour créer un dictionnaire.
  5. Filtrez les rondes qui ne vous intéressent pas.
  6. Calculez les valeurs totales et moyennes des tours qui vous intéressent.

Normalement, vous pouvez le faire avec un paquet tel que pandas, mais vous pouvez également obtenir cette fonctionnalité avec seulement quelques générateurs. Vous commencerez par lire chaque ligne du fichier avec une expression de générateur:

    1 nom de fichier = "techcrunch.csv"
    2 lignes = (ligne pour ligne dans ouvert(nom de fichier))

Ensuite, vous utiliserez une autre expression génératrice en même temps que la précédente pour scinder chaque ligne en une liste:

    3 liste_ligne = (s.bande de retour().Divisé(",") pour s dans lignes)

Ici, vous avez créé le générateur liste_ligne, qui parcourt le premier générateur lignes. C'est un modèle courant à utiliser lors de la conception de pipelines de générateurs. Ensuite, vous extrairez les noms de colonne de techcrunch.csv. Comme les noms de colonne ont tendance à constituer la première ligne d'un fichier CSV, vous pouvez le saisir avec un court suivant() appel:

Cet appel à suivant() avance l'itérateur sur la liste_ligne générateur une fois. Rassemblez tout, et votre code devrait ressembler à ceci:

    1 nom de fichier = "techcrunch.csv"
    2 lignes = (ligne pour ligne dans ouvert(nom de fichier))
    3 liste_ligne = (s.bande de retour().Divisé(",") pour s dans lignes)
    4 cols = suivant(liste_ligne)

Pour résumer, vous créez d'abord une expression génératrice lignes pour donner chaque ligne dans un fichier. Ensuite, vous parcourez ce générateur dans la définition de un autre expression génératrice appelée liste_ligne, qui transforme chaque ligne en une liste de valeurs. Ensuite, vous avancez l'itération de liste_ligne juste une fois avec suivant() pour obtenir une liste des noms de colonnes de votre fichier CSV.

Pour vous aider à filtrer et à effectuer des opérations sur les données, vous créerez des dictionnaires dans lesquels les clés sont les noms de colonne du fichier CSV:

    5 company_dicts = (dict(Zip *: français(cols, Les données)) pour Les données dans liste_ligne)

Cette expression générée parcourt les listes produites par liste_ligne. Ensuite, il utilise Zip *: français() et dict () pour créer le dictionnaire comme spécifié ci-dessus. Maintenant, vous utiliserez un Quatrième générateur pour filtrer le cycle de financement que vous voulez et tirez surélevé ainsi que:

    6 le financement = (
    sept     int(company_dict[[[["raisedAmt"])
    8     pour company_dict dans company_dicts
    9     si company_dict[[[["rond"] == "une"
dix )

Dans cet extrait de code, votre expression de générateur parcourt les résultats de company_dicts et prend la surélevé pour toute company_dict où le rond la clé est UNE.

N'oubliez pas que vous ne parcourez pas tout cela en même temps dans l'expression du générateur. En fait, vous ne parcourez rien avant d’utiliser réellement un pour boucle ou une fonction qui fonctionne sur les iterables, comme somme(). En fait, appelez somme() maintenant, parcourons les générateurs:

11 total_series_a = somme(le financement)

En réunissant tout cela, vous produirez le script suivant:

    1 nom de fichier = "techcrunch.csv"
    2 lignes = (ligne pour ligne dans ouvert(nom de fichier))
    3 liste_ligne = (s.bande de retour()Divisé(",") pour s dans lignes)
    4 cols = suivant(liste_ligne)
    5 company_dicts = (dict(Zip *: français(cols, Les données)) pour Les données dans liste_ligne)
    6 le financement = (
    sept     int(company_dict[[[["raisedAmt"])
    8     pour company_dict dans company_dicts
    9     si company_dict[[[["rond"] == "UNE"
dix )
11 total_series_a = somme(le financement)
12 impression(F"Levée de fonds totale série A: $total_series_a")

Ce script rassemble tous les générateurs que vous avez construits et fonctionne comme un pipeline de données volumineux. Voici une ventilation ligne par ligne:

  • Ligne 2 lit dans chaque ligne du fichier.
  • Ligne 3 divise chaque ligne en valeurs et les place dans une liste.
  • Ligne 4 les usages suivant() pour stocker les noms de colonne dans une liste.
  • Ligne 5 crée des dictionnaires et les unit avec un Zip *: français() appel:
    • Les clés sont les noms de colonne cols de la ligne 4.
    • Les valeurs sont les lignes sous forme de liste, créées à la ligne 3.
  • Ligne 6 obtient les montants de financement de la série A de chaque société. Il filtre également tout autre montant collecté.
  • Ligne 11 commence le processus d'itération en appelant somme() pour obtenir le montant total du financement de série A indiqué dans le CSV.

Lorsque vous exécutez ce code sur techcrunch.csv, vous devriez trouver un total de 4 376 015 000 $ collectés lors des séries de financement de la série A.

Pour creuser encore plus, essayez de déterminer le montant moyen collecté par entreprise dans une série A round. C'est un peu plus compliqué, alors voici quelques astuces:

  • Les générateurs s'épuisent après une itération complète.
  • Vous aurez toujours besoin du somme() une fonction.

Bonne chance!

Conclusion

Dans ce tutoriel, vous avez appris à propos de fonctions du générateur et expressions du générateur.

Vous savez maintenant:

  • Comment utiliser et écrire des fonctions de générateur et des expressions de générateur
  • Comment l'important Déclaration de rendement Python active les générateurs
  • Comment utiliser plusieurs Instructions de rendement Python dans une fonction génératrice
  • Comment utiliser .envoyer() envoyer des données à un générateur
  • Comment utiliser .jeter() lever des exceptions de générateurs
  • Comment utiliser .proche() arrêter l'itération d'un générateur
  • Comment construire un pipeline de génératrice traiter efficacement de gros fichiers CSV

Vous pouvez obtenir le jeu de données que vous avez utilisé dans ce tutoriel en cliquant sur le lien ci-dessous:

Comment les générateurs vous ont-ils aidé dans votre travail ou vos projets? Si vous venez d’apprendre à leur sujet, comment envisagez-vous de les utiliser à l’avenir? Avez-vous trouvé une bonne solution au problème de pipeline de données? Faites-nous savoir dans les commentaires ci-dessous!