*Memo:
- My post explains an iterator (1).
- My post explains an iterator (2) and the iterator with copy and sorted().
- My post explains a class-based iterator with __iter__() and/or __next__().
- My post explains the shallow and deep copy of an iterator.
A generator:
- is the function with one or more
yield
statements:- A
yield
statement is ayield
oryield from
.
- A
- can return an iterator.
- 's iterator can be created by a generator comprehension:
- A generator comprehension is an expression.
- terminates if there is no element to return, if close() is called or of course if error occurs.
- 's iterator cannot be copied.
A generator can be created with a function and one or more yield
statements and read with next()
as shown below:
*Memo:
-
__next__()
can also be used to read a generator. - A
yield
statements is ayield
oryield from
. - A
yield
can return any type of element. - A
yield from
can only return an iterable.
<yield>:
def func():
yield 0
yield 1
yield 2
print(func) # <function func at 0x000001FCD2FF93A0>
print(type(func)) # <class 'function'>
gen = func()
print(gen) # <generator object func at 0x000001FCD3015220>
print(type(gen)) # <class 'generator'>
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # StopIteration:
def func():
yield [0, 1, 2]
yield [3, 4, 5]
gen = func()
print(next(gen)) # [0, 1, 2]
print(next(gen)) # [3, 4, 5]
print(next(gen)) # StopIteration:
<yield from>:
def func():
yield from [0, 1, 2]
yield from [3, 4, 5]
print(func) # <function func at 0x000001FCD640B1A0>
print(type(func)) # <class 'function'>
gen = func()
print(gen) # <generator object func at 0x000001FCD661DD80>
print(type(gen)) # <class 'generator'>
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen)) # 4
print(next(gen)) # 5
print(next(gen)) # StopIteration:
def func():
yield from 0
yield from 1
yield from 2
gen = func()
print(next(gen))
# TypeError: 'int' object is not iterable
<yield & yield from>:
def func():
yield [0, 1, 2]
yield from [3, 4, 5]
gen = func()
print(next(gen)) # [0, 1, 2]
print(next(gen)) # 3
print(next(gen)) # 4
print(next(gen)) # 5
print(next(gen)) # StopIteration:
This is how a generator works as shown below:
<yield>:
def func():
print("func() starts.")
yield "func() pauses."
print("func() resumes.")
yield "func() pauses again."
print("func() resumes again.")
yield "func() terminates."
gen = func() # `func()` doesn't start yet.
print(next(gen))
# func() starts.
# func() pauses.
print(next(gen))
# func() resumes.
# func() pauses again.
print(next(gen))
# func() resumes again.
# func() terminates.
print(next(gen)) # StopIteration:
<yield from>:
def func():
print("func() starts.")
yield from ["func() pauses.",
"func() resumes and pauses."]
print("func() resumes.")
yield from ["func() pauses.",
"func() resumes and terminates."]
gen = func() # `func()` doesn't start yet.
print(next(gen))
# func() starts.
# func() pauses.
print(next(gen))
# func() resumes and pauses.
print(next(gen))
# func() resumes.
# func() pauses.
print(next(gen))
# func() resumes and terminates.
print(next(gen)) # StopIteration:
This is the generator with a for
statement as shown below:
<yield>:
def func():
for x in [0, 1, 2]:
yield x
gen = func()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # StopIteration:
def func():
for x in [[0, 1, 2], [3, 4, 5]]:
yield x
gen = func()
print(next(gen)) # [0, 1, 2]
print(next(gen)) # [3, 4, 5]
print(next(gen)) # StopIteration:
<yield from>:
def func():
for x in [[0, 1, 2], [3, 4, 5]]:
yield from x
gen = func()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen)) # 4
print(next(gen)) # 5
print(next(gen)) # StopIteration:
def func():
for x in [0, 1, 2]:
yield from x
gen = func()
print(next(gen))
# TypeError: 'int' object is not iterable
<yield & yield from>:
def func():
for x in [[0, 1, 2], [3, 4, 5]]:
yield x
yield from x
gen = func()
print(next(gen)) # [0, 1, 2]
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # [3, 4, 5]
print(next(gen)) # 3
print(next(gen)) # 4
print(next(gen)) # 5
print(next(gen)) # StopIteration:
A yield
statement can be assigned to a variable to be used with or without send() as shown below:
*Memo:
- The variable assigned a yield statement has None
by default.
<yield without send()>:
def func():
v1 = yield 0
print(v1, 'func')
v2 = yield 1
print(v2, 'func')
v3 = yield 2
print(v3, 'func')
gen = func()
print(next(gen)) # 0
# None func
print(next(gen)) # 1
# None func
print(next(gen)) # 2
# None func
print(next(gen)) # StopIteration:
<yield with send()>:
def func():
v1 = yield 0
print(v1, 'func')
v2 = yield 1
print(v2, 'func')
v3 = yield 2
print(v3, 'func')
gen = func()
print(gen.send(None)) # 0
print(gen.send('A')) # A func
# 1
print(gen.send('B')) # B func
# 2
print(gen.send('C')) # C func
print(gen.send('D')) # StopIteration:
def func():
v1 = yield 0
print(v1, 'func')
v2 = yield 1
print(v2, 'func')
v3 = yield 2
print(v3, 'func')
gen = func()
print(gen.send('A'))
# TypeError: can't send non-None value to a just-started generator
<yield from without send()>:
def func():
v1 = yield from [0, 1]
print(v1, 'func')
v2 = yield from [2, 3]
print(v2, 'func')
v3 = yield from [4, 5]
print(v3, 'func')
gen = func()
print(next(gen)) # 0
print(next(gen)) # 1
# None func
print(next(gen)) # 2
print(next(gen)) # 3
# None func
print(next(gen)) # 4
print(next(gen)) # 5
# None func
print(next(gen)) # StopIteration:
<yield from with send()>:
def sub_func():
v1 = yield 0
print(v1, 'sub_func')
v2 = yield 1
print(v2, 'sub_func')
v3 = yield 2
print(v3, 'sub_func')
def func():
v = yield from sub_func()
print(v, 'func')
gen = func()
print(gen.send(None)) # 0
print(gen.send('A')) # A sub_func
# 1
print(gen.send('B')) # B sub_func
# 2
print(gen.send('C')) # C sub_func
# None func
# StopIteration:
def func():
v1 = yield from [0, 1]
print(v1, 'func')
v2 = yield from [2, 3]
print(v2, 'func')
v3 = yield from [4, 5]
print(v3, 'func')
gen = func()
print(gen.send('A'))
# TypeError: can't send non-None value to a just-started generator
def func():
v1 = yield from [0, 1]
print(v1, 'func')
v2 = yield from [2, 3]
print(v2, 'func')
v3 = yield from [4, 5]
print(v3, 'func')
gen = func()
print(gen.send(None)) # 0
print(gen.send('A'))
# AttributeError: 'list_iterator' object has no attribute 'send'
A generator comprehension can create a generator's iterator as shown below:
gen = (x.upper() for x in ['a', 'b', 'c'])
print(gen) # <generator object <genexpr> at 0x00000168B98E9080>
print(type(gen)) # <class 'generator'>
print(next(gen)) # A
print(next(gen)) # B
print(next(gen)) # C
print(next(gen)) # StopIteration:
close() can terminate a generator as shown below:
*Memo:
- It has no arguments.
- It should be used in a
finally
clause to terminate a generator for if error occurs.
<close() without a finally clause>:
def func():
yield 0
yield 1
yield 2
yield 3
# yield from [0, 1, 2, 3]
gen = func()
print(next(gen)) # 0
print(next(gen)) # 1
gen.close()
print(next(gen)) # StopIteration:
<close() with a finally clause>:
def func():
yield 0
yield 1
yield 2
yield 3
# yield from [0, 1, 2, 3]
gen = func()
try:
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
finally:
gen.close()
A generator's iterator cannot be copied as shown below:
import copy
def func():
yield 0
yield 1
yield 2
# yield from [0, 1, 2]
v1 = func()
v2 = copy.copy(v1)
v2 = copy.deepcopy(v1)
# TypeError: cannot pickle 'generator' object
Top comments (0)