(J'ai essayé de m'inscrire tardivement au calendrier de l'Avent, mais il était déjà plein, donc je l'ai posté comme article régulier)
Je pensais connaître les spécifications du langage autour des itérateurs et des générateurs Python, et il y avait pas mal de fonctionnalités que j'avais ajoutées mais que je ne connaissais pas, donc je les ai résumées ici.
Dans cet article, «it» est traité comme une variable qui pointe vers un itérateur, et «Klass» est traité comme une classe définie par l'utilisateur sans préavis. Traitez «x» comme une variable pointant vers un objet.
La manière de récupérer l'élément suivant de l'itérateur a changé entre Python 2 et 3.
En Python2 c'est ʻit.next () , en Python3 c'est
next (it) . Si vous souhaitez implémenter vous-même une classe de type itérateur, vous pouvez implémenter
Klass.next en Python2 et
Klass .__ next__` en Python3. (Dans cet article, nous utiliserons le format Python3 ci-dessous.)
__iter__
qui retourne un itérateur est appelé un itérable.Pour les objets itérables, vous pouvez créer un itérateur, tel que ʻiter (x) . Il peut également être spécifié après in dans une instruction for, ou sur le côté droit de l'opérateur in (l'opérateur in essaiera «__contains__» s'il existe, ou «__iter__» si ce n'est pas le cas). Les exemples d'itérables incluent les objets list, tuple, dict, str et file. L'itérateur lui-même est également itérable. Alternativement, l'objet de la classe qui implémente la méthode
getitemest également itérable. Un itérateur ʻiter (x)
créé à partir d'un objet d'une classe où __iter__
n'est pas implémenté et __getitem__
est implémenté est x [0]
, x [1] chaque fois que next est appelé. Il renvoie
, ... et lève une exceptionStopIteration
lorsque ʻIndexError` est levé.
Continuez avec next (it)
, et finalement une exceptionStopIteration
sera levée lorsque l'élément suivant aura disparu.
Lors de l'implémentation de Klass .__ next__
, lancez une exceptionStopIteration
s'il n'y a plus rien à retourner.
Un "générateur" est une fonction qui renvoie un itérateur, similaire à une fonction régulière, mais avec une instruction yield. Le générateur lui-même n'est pas un itérateur. De plus, une "expression de générateur" est une expression qui renvoie un itérateur, similaire à la notation d'inclusion de liste, mais entourée de cercles au lieu de carrés.
iter(it) is it
Quand ʻitest un itérateur, ʻiter (it)
doit retourner ʻitlui-même. Autrement dit, si vous implémentez un itérateur, vous devriez dire quelque chose comme
Klass .__ iter __ (self): return self. Dans l'instruction for,
for x in it:et
for x in iter (it): sont censés être équivalents. Ce qui suit est un exemple de ce qui se passe lorsque ʻit
et ʻiter (it) `sont différents.
it et iter(it)
print(sys.version) # ==> 3.4.1 (default, May 23 2014, 17:48:28) [GCC]
# iter(it)Si le retourne
class Klass:
def __init__(self):
self.x = iter('abc')
def __iter__(self):
return self
def __next__(self):
return next(self.x)
it = Klass()
for x in it:
print(x) # ==> 'a', 'b', 'c'
# iter(it)Ne le retourne pas
class Klass2(Klass):
def __iter__(self):
return iter('XYZ')
it = Klass2()
for x in it:
print(x) # ==> 'X', 'Y', 'Z'
print(next(it)) # ==> 'a'
Quand iter est appelé avec deux arguments, il renvoie toujours un itérateur, mais le comportement est très différent. S'il y a deux arguments, le premier argument doit être un objet appelable (une fonction ou un autre objet avec les méthodes call), non itérable. L'itérateur renvoyé par this appelle un appel sans argument à chaque fois qu'il appelle next. Lève une exception StopIteration si le résultat renvoyé est égal à sentinel. Si vous l'écrivez comme un générateur avec un pseudo-code, il se comportera ainsi.
2 argument iter
def iter_(callable, sentinel):
while 1:
a = callable()
if a == sentinel:
raise StopIteration
else:
yield a
Le [Document officiel] de Python (http://docs.python.jp/3/library/functions.html#iter) déclare qu'il est utile, par exemple, lors de la lecture d'un fichier jusqu'à ce qu'une ligne vide apparaisse.
Cité du document officiel
with open('mydata.txt') as fp:
for line in iter(fp.readline, ''):
process_line(line)
Dans le générateur
v = (yield x)
Si vous écrivez comme, vous pouvez obtenir la valeur v lorsque le générateur redémarre.
Si le générateur est redémarré normalement, v sera Aucun.
Si vous appelez la méthode send au lieu de next, comme gen.send (a)
, le générateur redémarrera et v contiendra un fichier. Il retourne ensuite si la valeur est renvoyée, comme lors de l'appel suivant, et lève une exception StopIteration si rien n'est généré.
Document officiel donne un exemple de compteur avec une fonction de changement de valeur.
Cité du document officiel
def counter(maximum):
i = 0
while i < maximum:
val = (yield i)
# If value provided, change counter
if val is not None:
i = val
else:
i += 1
Au fait. Vous ne pouvez pas utiliser l'envoi soudainement et vous devez faire la suite au moins une fois avant de pouvoir utiliser l'envoi. (TypeError: impossible d'envoyer une valeur non-None à un générateur qui vient de démarrer
.) Comme vous pouvez voir d'où va la valeur envoyée, si elle n'a jamais été la suivante, la valeur va. Probablement parce qu'il n'y a pas de place.
generator.throw(type[, value[, traceback]])
Vous permet de déclencher une exception où le générateur a été interrompu.
Si le générateur renvoie une valeur, il la renvoie et lève une exception StopIteration si rien ne donne. Si l'exception levée n'est pas traitée, elle sera propagée telle quelle. (Honnêtement, je ne peux penser à aucune utilisation efficace)
Déclenche une exception GeneratorExit où le générateur a été interrompu. Si une exception GeneratorExit ou StopIteration est levée, generator.close () s'arrête là. Si une valeur est renvoyée, un RuntimeError sera déclenché. Si le générateur était initialement fermé, ne faites rien. Ecrit en pseudo code, ça ressemble à ça?
generator.Traitement proche
def generator_close(gen):
try:
gen.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
return
throw RuntimeError
Je ne vois aucune utilité pour cela non plus. Il n'y a aucune garantie qu'il sera appelé, vous ne pouvez donc pas écrire quelque chose qui est censé être appelé. Je n'ai trouvé aucun document indiquant clairement cela, mais quand je romps avec l'instruction for, il semble qu'une exception GeneratorExit soit lancée au générateur.
Une exception de sortie du générateur se produit lors de la rupture avec une instruction for
def gen():
for i in range(10):
try:
yield i
except GeneratorExit:
print("Generator closed.")
raise
for i in gen():
break # ==> "Generator closed." is printed.
Vous pouvez renvoyer expr séquentiellement en écrivant yield from expr
(expr est une expression qui renvoie un itérable) dans le générateur.
Sans considérer l'envoi, les deux codes suivants sont équivalents.
Délégation au sous-générateur
def gen1():
yield from range(10)
yield from range(20)
def gen2():
for i in range(10):
yield i
for i in range(20):
yield i
S'il y a envoi, la valeur envoyée est transmise au sous-générateur.
Les itérateurs sont souvent utilisés en Python, mais ils se sont souvent arrêtés à ce qu'ils pensaient savoir de leurs spécifications et de leurs anciennes connaissances. Par conséquent, j'ai examiné les spécifications linguistiques sur la base de la documentation officielle. Il y avait pas mal de choses que je ne savais pas, mais pour être honnête, la plupart d'entre elles ne proposent pas d'utilisations utiles. S'il y a quelque chose comme "Il y a d'autres spécifications comme celle-ci" ou "J'utilise cette fonction comme ça", veuillez l'écrire dans la section commentaires.