English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Dans cet article, vous apprendrez à créer des itérations faciles avec les générateurs Python, à ce que cela signifie par rapport aux itérateurs et aux fonctions normales, et pourquoi il est utile de les utiliser.
Avec Pythonde constructionLes itérateursIl y a beaucoup de coûts; nous devons implémenter une classe en utilisant les méthodes __iter__() et __next__(), suivre l'état interne, déclencher StopIteration lorsque il n'y a pas de valeur à retourner, etc.
C'est à la fois long et contre-intuitif. Les générateurs peuvent être utiles dans ce cas.
Les générateurs Python sont une méthode simple pour créer des itérateurs. Tous les coûts mentionnés précédemment sont automatiquement traités par les générateurs Python.
En résumé, un générateur est une fonction qui retourne un objet (itérateur) que nous pouvons itérer (une valeur à la fois).
Il est très simple de créer des générateurs en Python. Cela est aussi facile que de définir une fonction ordinaire en utilisant l'instruction yield plutôt que l'instruction return.
Si une fonction contient au moins une instruction yield (elle peut contenir d'autres instructions yield ou return), alors elle devient une fonction génératrice. yield et return retournent tous deux certaines valeurs de la fonction.
La différence réside en ce que lorsque la instruction return termine complètement une fonction, l'instruction yield met la fonction en pause et sauvegarde tous ses états, puis continue à s'exécuter lors des appels suivants.
Voici la différence entre la fonction génératrice etLes fonctions normalesLes différences.
Les fonctions génératrices contiennent une ou plusieurs instructions yield.
Lors de l'appel, il retourne un objet (itérateur), mais ne commence pas immédiatement à s'exécuter.
Des méthodes telles que __iter__() et __next__() sont automatiquement implémentées. Par conséquent, nous pouvons utiliser next() pour parcourir les éléments.
Une fois que la fonction a produit un résultat, la fonction s'arrête et le contrôle est transféré à l'appelant.
Les variables locales et leur état sont mémorisées entre les appels successifs.
Finalement, lorsque la fonction se termine, StopIteration est automatiquement levée lors de la prochaine appel.
Ceci est un exemple pour illustrer tous les points mentionnés précédemment. Nous avons une fonction génératrice my_gen() nommée par plusieurs instructions yield.
# Une fonction de générateur simple def my_gen(): n = 1 print('C'est la première impression') # La fonction de générateur contient une instruction yield yield n n += 1 print('C'est la deuxième impression') yield n n += 1 print('C'est la dernière impression') yield n
L'exécution interactive dans l'interpréteur est présentée comme suit. Exécutez ces commandes dans le Shell Python pour voir les sorties.
>>> # Il retourne un objet, mais ne commence pas immédiatement à s'exécuter. >>> a = my_gen() >>> # Nous pouvons utiliser next() pour parcourir ces éléments. >>> next(a) C'est la première impression 1 >>> # Une fois que la fonction a produit un résultat, la fonction s'arrête et le contrôle est transféré à l'appelant. >>> # Les variables locales et leur état sont mémorisés entre les appels successifs. >>> next(a) C'est la deuxième impression 2 >>> next(a) C'est la dernière impression 3 >>> # Lorsque la fonction se termine, StopIteration est automatiquement déclenché lors de l'appel suivant. >>> next(a) Traceback (appel le plus récent en dernier) : ... StopIteration >>> next(a) Traceback (appel le plus récent en dernier) : ... StopIteration
Une chose amusante à noter dans l'exemple ci-dessus est que les variables sont mémorisées entre les appels.ndes valeurs.
Différent des fonctions normales, les variables locales ne sont pas détruites lors de la création de la fonction. De plus, l'objet de générateur ne peut être itéré qu'une seule fois.
Pour redémarrer ce processus, nous devons utiliser = my_gen() pour créer un autre objet de générateur.
Attention :Le dernier point à noter est que nous pouvons directement utiliser le générateur avecBoucle forUtilisés ensemble.
Cela est dû au fait que la boucle for accepte un itérateur et l'itére avec la fonction next(). Lorsque StopIteration est déclenché, il se termine automatiquement.Comprendre comment implémenter réellement la boucle for en Python.
# Une fonction de générateur simple def my_gen(): n = 1 print('C'est la première impression') # La fonction de générateur contient une instruction yield yield n n += 1 print('C'est la deuxième impression') yield n n += 1 print('C'est la dernière impression') yield n # Utilisation de la boucle for for item in my_gen(): print(item)
Lorsque le programme est exécuté, la sortie est :
C'est la première impression 1 C'est la deuxième impression 2 C'est la dernière impression 3
L'exemple ci-dessus n'est pas très utile, nous l'étudions juste pour comprendre ce qui se passe en arrière-plan.
Généralement, les fonctions de générateur sont implémentées par un cycle avec une condition de terminaison appropriée.
Laissez-nous prendre un exemple de générateur pour inverser une chaîne de caractères.
def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1) : yield my_str[i] # La boucle for inverse une chaîne de caractères # Sortie: # o # l # l # e # h for char in rev_str("hello"): print(char)
Dans cet exemple, nous utilisons la fonction range() pour obtenir les indices en ordre inverse avec une boucle for.
Il s'avère que cette fonction générateur s'applique non seulement aux chaînes de caractères, mais aussi à d'autres types d'objets itérables, tels quelist,tupleetc.
L'utilisation des expressions générateur permet de créer facilement des générateurs simples de manière dynamique. Cela rend la construction de générateurs facile.
créée par lambdaidentique à une fonction anonymecrée une fonction anonyme générateur.
La syntaxe des expressions générateur est similaire àPythonenCompréhensionLa syntaxe. Mais en remplaçant les crochets par des parenthèses.
La principale différence entre la compréhension de liste et l'expression générateur réside en ce que, bien que la compréhension de liste génère la liste entière, l'expression générateur génère un élément à la fois.
Ils sont un peu paresseux, ils génèrent des éléments uniquement lorsque cela est nécessaire. Pour cette raison, les expressions générateur sont beaucoup plus efficaces en termes de mémoire par rapport à une compréhension de liste équivalente.
# Initialisation de la liste my_list = [1, 3, 6, 10] # Utilisation de la compréhension de liste pour multiplier chaque élément # Sortie: [1, 9, 36, 100] [x**2 for x in my_list] # La même chose peut être réalisée avec une expression générateur # Sortie: <generator object <genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)
Nous pouvons voir ci-dessus que l'expression générateur ne produit pas immédiatement le résultat souhaité. Au lieu de cela, elle retourne un objet générateur qui produit des éléments à la demande.
# Initialisation de la liste my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) # Sortie: 1 print(next(a)) # Sortie: 9 print(next(a)) # Sortie: 36 print(next(a)) # Sortie: 100 print(next(a)) # Sortie: StopIteration next(a)
Les expressions générateurs peuvent être utilisées à l'intérieur des fonctions. Lorsqu'elles sont utilisées de cette manière, les parenthèses peuvent être supprimées.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Il y a plusieurs raisons qui rendent les générateurs une implémentation attrayante.
Les générateurs peuvent être implémentés de manière claire et concise par rapport aux éléments correspondants de la classe itératrice. Voici un exemple d'implémentation en utilisant la classe itératrice2d'exemple de séquence de puissances.
class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n > self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
This code is long. Now, perform the same operation using the generator function.
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
Since generators automatically track details, they are concise and clear, and the implementation is also more concise.
A regular function that returns a sequence creates the entire sequence in memory before returning the result. If the number of items in the sequence is large, it can affect efficiency.
And the generator implementation of this sequence is memory-friendly, so it is the preferred choice because it can only generate one item at a time.
Generators are an excellent medium for representing infinite data streams. Infinite streams cannot be stored in memory, and since generators generate one item at a time, they can represent infinite data streams.
The following example can generate all even numbers (at least in theory).
def all_even(): n = 0 while True: yield n n += 2
Generators can be used for pipelining a series of operations. It is best to illustrate with an example.
Assume we have a log file of a famous fast-food chain. The log file has a column (the4Column), which tracks the number of pizzas sold per hour, we want to sum it up to get5Total number of pizzas sold in the year.
Assume all content is strings, and no available numbers are marked as "N / A. Implementation of generators can be as follows.
with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("Total pizzas sold = ", sum(per_hour))
This pipeline is efficient and easy to read (yes, very cool!).