Il y a d'autres pages qui expliquent les générateurs Python, mais il y en avait beaucoup qui étaient en anglais ou qui ne me semblaient pas bien, alors j'ai expliqué les générateurs à ma manière, également pour organiser mes pensées. Je vais essayer. Si je refuse, je suis fondamentalement confus, alors je pourrais dire quelque chose de mal. Si vous avez des erreurs, veuillez commenter et cela vous aidera grandement à étudier. De plus, l'utilisation du générateur présenté ici n'est que quelques exemples d'utilisation, et cela ne signifie pas qu'il n'y a pas d'autre utilisation.
Il existe d'autres excellents articles pour des explications détaillées et précises, veuillez donc vous y référer: Itérateur et générateur Python
Pour le dire brièvement, pensez-y comme "une fonction normale avec l'instruction return remplacée par yield". (C'est différent, mais veuillez le manquer pour le moment). Si vous écrivez yield au lieu de return, ** l'appelant ne renverra pas de valeur **. Au lieu de cela, il sera appelé avec une instruction for, etc. et ** retournera les valeurs dans l'ordre **. C'est probablement plus facile à comprendre en donnant un exemple.
example.py
def count_up():
x = 0
while True:
yield x
x += 1
Certes, c'est le rendement, pas le retour. Et quelque chose se passe après * yield *. Puisqu'il n'y a pas de traitement après l'instruction return, vous pouvez immédiatement voir que c'est quelque chose de différent du simple remplacement du retour d'une fonction normale. Je vais vous l'expliquer beaucoup. Pour le moment, ce générateur appelle par exemple:
>>> countup()
<generator object countup at 0x101fa0468>
>>> for i in countup():
... print(i)
... if i == 5:
... break
0
1
2
3
4
5
Certes, il semble qu'aucune valeur ne soit renvoyée simplement en appelant. Au lieu de cela, lorsqu'elles sont appelées dans une instruction for, les valeurs sont renvoyées dans l'ordre. Ce qui se passe à ce moment, c'est que count_up renvoie la valeur de x à chaque fois qu'il est appelé, mais il incrémente la valeur de x à chaque fois qu'il est appelé. C'est ce que le processus vient après le rendement. Maintenant que vous savez ce qu'est le générateur (ne vous inquiétez pas, je vais vous donner quelques exemples si vous n'êtes pas sûr). Mais ce à quoi vous pensez probablement ici, c'est ** Où utilisez-vous cela? ** ** Je pense que c'est ce que cela signifie. Donc, cette fois, j'expliquerai le but du générateur à ma manière.
Comme vous pouvez le voir dans l'exemple précédent, le générateur et la fonction sont ** complètement différents **. Les générateurs sont extrêmement plus proches des ** classes ** que des fonctions. Par exemple, dans l'exemple précédent
>>> countup()
Peut être clairement compris en interprétant qu'il créait une instance de la classe. Ainsi, le code suivant fonctionne également:
>>> y = countup()
>>> for i in y:
... print(i)
... if i == 5:
... break
0
1
2
3
4
5
À titre de test, si vous exécutez le processus suivant après cela,
>>> for i in y:
... print(i)
... if i == 10:
... break
6
7
8
9
10
0 à 5 ont disparu! La raison en est que le générateur ** se souvient de ce qui a été fait ** à chaque fois qu'il a été appelé. En d'autres termes, il a un ** état **. C'est la plus grande différence avec la fonction. Dans l'exemple ci-dessus, x = 6 en y lorsque la première instruction for se termine, et cela continue jusqu'à la prochaine instruction for.
Donc pourquoi est-ce important? À propos, considérons, par exemple, la scène du calcul de la séquence de nombres de Fibonacci que tout le monde aime. Une méthode de calcul couramment introduite utilisant la récurrence est la suivante:
fibonacci_recursive.py
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
Ce serait bien si n était petit, mais si n était grand, la pile se remplirait et une erreur serait générée. Par contre, lors de l'utilisation d'un générateur
fibonacci_generator.py
def fibonacci():
a, b = 0, 1
while 1:
yield b
a, b = b, a+b
De cette façon, en conservant les valeurs des deux dernières chaînes comme états, il devient possible de calculer la séquence de Fibonacci sans gaspiller la pile.
En regardant les exemples jusqu'à présent, le lecteur pense probablement ** La liste n'est-elle pas bonne? ** ** Je pense que c'est ce que cela signifie. Si vous souhaitez simplement imprimer à partir de zéro, vous pouvez certainement utiliser une liste distincte. Ensuite, quand ce n'est pas bon pour une liste, c'est ** quand il est difficile de garder la liste entière en mémoire et c'est inutile **. Par exemple, considérons le même exemple de calcul du Nième élément de la séquence de Fibonacci à l'aide d'une liste:
fibonacci_list.py
f = [0, 1]
n = 2
while n < N:
f.append(f[n-1] + f[n-2])
n += 1
Si vous voulez juste obtenir la Nième valeur, les valeurs de la séquence de Fibonacci avant la N-3e sont une perte de mémoire. En d'autres termes, la scène dans laquelle le générateur fonctionne est ** la scène dans laquelle vous devez renvoyer des valeurs en séquence et avoir un état, mais vous n'avez pas besoin de conserver la liste entière **. Je voudrais que vous imaginiez un automate à états finis avec un symbole de sortie. Au contraire, il ne convient pas aux situations où vous n'avez pas besoin d'avoir un état séparé ou vous devez tenir une liste. Le premier exemple sert à calculer une fonction de hachage pour un nombre d'entrée, et le dernier exemple à créer une liste de nombres premiers. Ci-dessous, nous examinerons quelques exemples plus pratiques.
Un exemple où le générateur est réellement utilisé est la bibliothèque standard pour l'analyse des phrases python:
http://docs.python.jp/2/library/tokenize.html
L'analyse des phrases est un processus souvent effectué lors de la compilation d'un programme. Elle examine le programme caractère par caractère et divise la chaîne de caractères du programme en parties importantes (appelées jetons). Par exemple
def f(hoge, foo):
Je veux dire,
def, f, (, hoge, foo, ), :
Il sera probablement divisé en une chaîne de jetons appelée.
Vous pouvez dire à partir de la chaîne def qu'il s'agit d'une définition de fonction, f est le nom de la fonction et les variables séparées par des virgules entre les "(" et ")" après lui sont les arguments. L'analyse de phrase ajoute également ces informations au jeton et transmet la chaîne de jeton au processus suivant (ce qui peut être un peu inexact, mais consultez la documentation du compilateur pour plus d'informations).
Ce qui est important ici, c'est que lorsque vous regardez chaque caractère et que vous l'analysez, par exemple, le traitement lorsque vous voyez le caractère ")" change selon que vous voyez "(" ou non. .. Autrement dit, il doit avoir un ** état **. Mais une fois cette fonction définie, ** les informations sur cette fonction n'ont pas d'importance **. Ce serait une perte de mémoire pour enregistrer les informations de l'ensemble du programme une par une. Par conséquent, l'analyse des phrases est une bonne scène pour que les générateurs jouent un rôle actif.
Les détails sont expliqués sur le site suivant. https://brett.is/writing/about/generator-pipelines-in-python/
En termes simples, un pipeline de générateur est un processus de ** connexion ** de plusieurs générateurs. J'espère que vous pouvez imaginer le traitement de type convoyeur à bande du côté de l'usine. L'exemple présenté est un pipeline qui triple les éléments pairs d'une liste donnée d'entiers, les convertit en chaîne et les renvoie.
pipeline.py
def even_filter(nums):
for num in nums:
if num % 2 == 0:
yield num
def multiply_by_three(nums):
for num in nums:
yield num * 3
def convert_to_string(nums):
for num in nums:
yield 'The Number: %s' % num
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pipeline = convert_to_string(multiply_by_three(even_filter(nums)))
for num in pipeline:
print num
Dans cet exemple, les générateurs individuels n'ont pas d'état, mais ils utilisent toujours les générateurs efficacement. C'est une situation où vous n'avez pas à enregistrer la liste entière, mais vous devez la traiter séquentiellement. Vous pouvez penser que le traitement des fonctions est correct, mais le traitement des valeurs impaires, par exemple, est juste un calcul inutile dans l'exemple ci-dessus. C'est pourquoi un générateur qui peut appliquer un traitement à chaque élément un par un est pratique.
Les sites suivants ont des explications détaillées et des exemples: http://www.unixuser.org/~euske/doc/python/recursion-j.html
Comme je l'ai expliqué plus tôt, le gros avantage des générateurs est qu'ils peuvent traiter de manière séquentielle sans conserver la liste entière. En d'autres termes, il est compatible avec des problèmes qui peuvent être subdivisés et résolus par un traitement itératif. Vous pouvez voir que cela est très proche de l'idée de résoudre les problèmes de récurrence. L'exemple de séquence de Fibonacci était un exemple de remplacement récursif par un générateur, mais bien sûr, vous pouvez également utiliser le générateur lui-même de manière récursive. L'avantage d'utiliser un générateur au lieu d'une fonction est qu'une fois que vous avez généré une valeur, vous pouvez la rejeter à la volée. Par exemple, le code qui utilise le générateur de manière récursive pour parcourir l'arborescence ressemble à ceci:
tree.py
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
def traverse(node):
if node is not None:
for x in traverse(node.left):
yield x
yield t.dat
for x in traverse(node.right):
yield x
Au fait, si vous utilisez l'instruction yield from
nouvellement ajoutée à partir de la série python3.3, la fonction traverse
sera encore plus propre.
def traverse(node):
if node is not None:
yield from traverse(node.left):
yield t.dat
yield from traverse(node.right)
Ce sera.
Les sources suivantes ont des instructions détaillées: http://masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html
Un collout est un sous-programme qui vous permet de suspendre le traitement, puis de reprendre le traitement au milieu. Dans Corroutine, vous pouvez non seulement récupérer la valeur, mais aussi l'envoyer. Par exemple, prenez l'exemple suivant:
coroutine.py
def coro():
hello = yield "Hello"
yield hello
c = coro()
print(next(c))
print(c.send("World"))
Ici, vous pouvez voir que yield est sur le côté droit de l'expression d'affectation. Ensuite, la méthode d'envoi envoie la chaîne «" World "». Vous pouvez voir qu'il tire parti des caractéristiques des générateurs qui ont des états. Si vous voulez élaborer sur Corroutine, ce sera un autre article, donc je vais simplement le présenter ici. Si vous en avez envie, vous pouvez également publier un article sur Corroutine. De plus, puisque le colloutum natif a été implémenté à partir de python3.5, le même traitement peut être réalisé sans utiliser de générateur.
Le générateur est un concept difficile à comprendre, mais je pense qu'il est étonnamment simple de le considérer comme un outil qui a un état et renvoie les valeurs dans l'ordre avec un traitement partiel seulement sans tenir la liste entière. Utilisons tous le générateur et écrivons des programmes sympas! (Bien que je ne l'ai pas encore maîtrisé).
Recommended Posts