__iter__ and __next__ methods and which raise the StopIteration exception automatically behind the scenes. yield statement (or the equivalent(?) yield expression) is used. def gen():
yield 42
yield 99
yield -1
g = gen()
print(g.__class__) # <class 'generator'>
g.__next__() # 42
g.__next__() # 99
g.__next__() # -1
g.__next__() # -> StopIteration is raised
a = [1,2,3,4,5] g = (b+10 for b in a) g.__class__ # -> <class 'generator'>
send() takes exactly one argument. .send(None) is equivalent to calling next(). send(), like next(), returns the next yielded value or raises StopIteration. def gen():
val_to_user = 42
while( val_from_user := (yield val_to_user)) != None:
val_to_user = val_from_user + 3
g = gen()
g.send(None) # 42
g.send(17) # 20
g.send(3) # 6
g.send(None) # Raises StopIteration
for and optional if clause: >>> gen_expr = ( math.sqrt(i) for i in [3,-2,5,6] if i >= 0 ) >>> type(gen_expr) <class 'generator'>
yield or yield from in a function's body determines if the function is a generator («coroutine»), refactoring (i. e. removing/adding yield or yield from to/from a function) can lead to unobvious errors.