DEV Community

Super Kai (Kazuya Ito)
Super Kai (Kazuya Ito)

Posted on • Edited on

Iterator in Python (3)

Buy Me a Coffee

*Memo:

A generator:

  • is the function with one or more yield statements:
    • A yield statement is a yield or yield from.
  • 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 a yield or yield 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:
Enter fullscreen mode Exit fullscreen mode
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:
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode
def func():
    yield from 0
    yield from 1
    yield from 2

gen = func()

print(next(gen))
# TypeError: 'int' object is not iterable
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

<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: 
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode
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:
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode
def func():
    for x in [0, 1, 2]:
        yield from x

gen = func()

print(next(gen))
# TypeError: 'int' object is not iterable
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode

<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:
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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'
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

<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()
Enter fullscreen mode Exit fullscreen mode

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)