Résumons l'itérateur et le générateur de Python.
(Ajout 2018.12.25: Complètement remplacé par la grammaire Python 3)
--Iterator: [Interface] qui vous permet de récupérer des éléments de manière itérative (https://docs.python.org/2/library/stdtypes.html#iterator-types) --Generator: Un type d'itérateur qui traite chaque élément lorsqu'il tente de le récupérer et génère l'élément. En Python, il semble qu'il se réfère souvent à l'implémentation à l'aide de l'instruction yield.
Toute collection intégrée Python (liste, tuple, ensemble, dict, etc.) peut être itérée, mais dans les cas suivants, il est nécessaire de mettre une valeur dans la collection à l'avance pour un traitement itératif à l'aide de la collection intégrée. Je pense qu'il y a des cas où vous souhaitez implémenter vous-même un itérateur ou un générateur.
Si vous placez un objet dans un contexte qui attend un itérateur, tel que for
in, la méthode __iter__ ()
de l'objet est d'abord appelée, ce qui l'oblige à renvoyer une implémentation d'itérateur. L'objet obtenu par cette valeur de retour est appelé la méthode «next ()». __next__ ()
sera appelé jusqu'à ce que vous obteniez une exception StopIteration
.
Ce n'est pas différent de list normalement, mais c'est un exemple d'implémentation qui renvoie une liste de nombres donnés au moment de l'instanciation dans l'ordre.
sample1.py
class MyIterator(object):
def __init__(self, *numbers):
self._numbers = numbers
self._i = 0
def __iter__(self):
# __next__()Est implémenté par soi-même, donc il retourne le soi tel qu'il est
return self
def __next__(self): #Suivant pour Python2(self)Défini dans
if self._i == len(self._numbers):
raise StopIteration()
value = self._numbers[self._i]
self._i += 1
return value
my_iterator = MyIterator(10, 20, 30)
for num in my_iterator:
print('hello %d' % num)
Le résultat est
hello 10 hello 20 hello 30
Sera.
Dans cet exemple, __iter__ ()
retourne self, mais lorsque le traitement de l'itération est susceptible d'être compliqué, une autre classe d'implémentation pour l'itération est implémentée pour créer un tel objet. Il est également possible de le retourner.
Si vous utilisez la fonction intégrée ʻiter () `, vous pouvez voir que les types intégrés tels que list sont également implémentés selon cette règle.
>>> hoge = [1, 2, 3]
>>> hoge_iter = iter(hoge)
>>> hoge_iter.__next__()
1
>>> hoge_iter.__next__()
2
>>> hoge_iter.__next__()
3
>>> hoge_iter.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
--La méthode __iter__ ()
est appelée lorsqu'une itération est demandée pour un objet.
__next__ ()
renvoie une nouvelle valeur à chaque fois qu'elle est appelée__next__ ()
lève une exception StopIteration
sur les appels lorsqu'il n'y a plus de valeurs à renvoyer.Il peut être difficile de comprendre le rendement si vous n'y êtes pas habitué, mais le mécanisme est simple. Il n'est pas nécessaire de définir une classe lors de l'implémentation d'un générateur utilisant yield. Définissons la fonction de générateur suivante.
my_generator.py
def my_generator():
yield 1
yield 2
yield 3
Il s'agit d'un générateur qui génère trois valeurs, «1», «2» et «3» dans l'ordre. Notez que ** les instructions return ne peuvent pas être utilisées dans les fonctions du générateur **.
Les générateurs sont souvent utilisés dans les cas suivants où les coûts de calcul sont un problème.
Une fonction de générateur devient un objet itérateur en effectuant un appel de fonction.
gen = my_generator()
gen.__next__() # 1
gen.__next__() # 2
gen.__next__() # 3
gen.__next__() # StopIteration
Yield renvoie le contrôle au côté qui a appelé next (). Vérifions le flux de traitement avec l'instruction d'impression comme indiqué ci-dessous.
generator_sample.py
def my_generator():
print('before yield')
yield 1
print('yielded 1')
yield 2
print('yielded 2')
yield 3
print('yielded 3, finished')
def main():
gen = my_generator()
print('start')
v1 = gen.__next__()
print('called __next__(), v1=%s' % v1)
v2 = gen.__next__()
print('called __next__(), v2=%s' % v2)
v3 = gen.__next__()
print('called __next__(), v3=%s' % v3)
v4 = gen.__next__() # should be exception
main()
Le résultat de l'exécution est le suivant.
start
before yield
called __next__(), v1=1
yielded 1
called __next__(), v2=2
yielded 2
called __next__(), v3=3
yielded 3, finished
Traceback (most recent call last):
File "./generator_sample.py", line 21, in <module>
main()
File "./generator_sample.py", line 19, in main
v4 = gen.__next__() # should be exception
StopIteration
--Utiliser le rendement dans la mise en œuvre du générateur
Les itérables peuvent être facilement liés à des fonctionnalités intégrées telles que la notation d'inclusion de liste / tuple / ensemble / liste.
Par exemple, vous pouvez facilement convertir le générateur simple ci-dessus en un objet liste avec une valeur telle que «[1, 2, 3]» en le passant à la fonction «list ()». La même chose est vraie pour tuple et set.
def my_generator():
yield 1
yield 2
yield 3
def my_generator2():
yield 10
yield 10
yield 20
print(list(my_generator())) # => [1, 2, 3]
print([v * 2 for v in my_generator()]) # => [2, 4, 6]
print(set(my_generator2())) # => set([10, 20])
Bien sûr, non seulement le générateur implémenté avec yield
mais aussi l'itérateur implémenté avec __next__ () ʻet
return` peuvent également être liés aux fonctions intégrées.
itertools
Présentation de Python car il existe une bibliothèque appelée itertools qui vous permet d'effectuer facilement diverses opérations en combinant des objets iter. Je vais le laisser. Je pense que cela est principalement utilisé pour les données intégrées telles que liste / tuple sans implémenter d'itérateur par vous-même, donc c'est pratique même si vous n'implémentez pas d'itérateur.
Par exemple, il est facile de [énumérer toutes les combinaisons] de [1, 2, 3]
et ['a', 'b']
(http://qiita.com/tomotaka_ito/items/5a545423eac654a5b6f5). Je peux le faire.
more_itertools
Il existe également une bibliothèque PyPI non standard appelée more_itertools. Il contient de nombreuses fonctions utiles qui ne sont pas incluses dans itertools. Il y a «chunked» qui regroupe tous les N morceaux et «silen» qui compte le nombre en tournant l'itérateur.
Vous devez l'installer pour l'utiliser.
$ pip install more-itertools
Il y a aussi article Qiita qui présente et explique itertools / more_itertools, donc je pense que ce sera utile.
Une fois que le générateur est tourné dans la boucle «for», les éléments n'apparaîtront pas dans la deuxième boucle «for» et les suivantes.
Si vous voulez pouvoir appeler la fonction générateur autant de fois que vous le souhaitez sans aucun effet secondaire, Il peut être utile d'utiliser la technique décrite dans Je veux réitérer le générateur Python plusieurs fois.
__next__ ()
, et vous pouvez voir qu'il n'y a plus d'éléments dans l'exception StopIteration.
--__iter__ ()
est appelée lorsque l'objet est évalué en tant que contexte d'itérateuryield
Recommended Posts