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:

*Memo:

  • next() starts or resumes a generator, executes a yield statement to return a value and pauses the generator at a yield statement or raises StopIteration if a generator is terminated:
  • A generator is always paused at a yield statement.
  • __next__() also does the same things.

<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 generator comprehension can create a generator's iterator as shown below:

<1D iterator>:

v = (x**2 for x in [0, 1, 2, 3, 4, 5, 6, 7]) for x in v: print(x) # 0 # 1 # 4 # 9 # 16 # 25 # 36 # 49 
Enter fullscreen mode Exit fullscreen mode

<2D iterator>:

sample = [[0, 1, 2, 3], [4, 5, 6, 7]] v = ((y**2 for y in x) for x in sample) for x in v: for y in x: print(y) # 0 # 1 # 4 # 9 # 16 # 25 # 36 # 49 
Enter fullscreen mode Exit fullscreen mode

<3D iterator>:

sample = [[[0, 1], [2, 3]], [[4, 5], [6, 7]]] v = (((z**2 for z in y) for y in x) for x in sample) for x in v: for y in x: for z in y: print(z) # 0 # 1 # 4 # 9 # 16 # 25 # 36 # 49 
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

throw() can raise an exception at the point where the generator is paused as shown below:

*Memo:

  • The 1st argument is value(Required-Type:BaseException):
    • Don't use value=.

<yield without a try statement>:

def func(): yield 0 yield 1 yield 2 yield 3 gen = func() print(next(gen)) # 0 print(next(gen)) # 1 print(gen.throw(Exception)) # Exception: 
Enter fullscreen mode Exit fullscreen mode

<yield from without a try statement>:

def func(): yield from [0, 1, 2, 3] gen = func() print(next(gen)) # 0 print(next(gen)) # 1 print(gen.throw(Exception)) # Exception: 
Enter fullscreen mode Exit fullscreen mode

<yield with a try statement>:

def func(): yield 0 try: yield 1 except Exception: pass yield 2 yield 3 gen = func() print(next(gen)) # 0 print(next(gen)) # 1 print(gen.throw(Exception)) # 2 print(next(gen)) # 3 
Enter fullscreen mode Exit fullscreen mode

<yield from with a try statement>:

def func(): try: yield from [0, 1] except Exception: pass yield from [2, 3] gen = func() print(next(gen)) # 0 print(next(gen)) # 1 print(gen.throw(Exception)) # 2 print(next(gen)) # 3 
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.

<yield without a finally clause>:

def func(): yield 0 yield 1 yield 2 yield 3 gen = func() print(next(gen)) # 0 print(next(gen)) # 1  gen.close() print(next(gen)) # StopIteration: 
Enter fullscreen mode Exit fullscreen mode

<yield with a finally clause>:

def func(): yield 0 yield 1 yield 2 yield 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

<yield from without a finally clause>:

def func(): 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

<yield from with a finally clause>:

def func(): 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

gi_yieldfrom can return an iterator if the generator is resumed at yield from otherwise it returns None as shown below:

def func(): yield from [0, 1] yield 2 yield from [3, 4] yield 5 gen = func() while True: try: print(gen.gi_yieldfrom, next(gen.gi_yieldfrom)) except: print(gen.gi_yieldfrom, next(gen)) # None 0 # <list_iterator object at 0x000001C66EBFE410> 1 # <list_iterator object at 0x000001C66EBFE410> 2 # None 3 # <list_iterator object at 0x000001C66EBFE410> 4 # <list_iterator object at 0x000001C66EBFE410> 5 
Enter fullscreen mode Exit fullscreen mode

gi_running can check if the generator is currently running as shown below:

<yield>:

def func(): print(gen.gi_running, "func") # True func  yield 0 print(gen.gi_running, "func") # True func  yield 1 print(gen.gi_running, "func") # True func  yield 2 print(gen.gi_running, "func") # True func  gen = func() print(gen.gi_running) # False print(next(gen)) # 0 print(gen.gi_running) # False print(next(gen)) # 1 print(gen.gi_running) # False print(next(gen)) # 2 print(gen.gi_running) # False print(next(gen)) # StopIteration: 
Enter fullscreen mode Exit fullscreen mode

<yield from>:

def func(): print(gen.gi_running, "func") # True func  yield from [0, 1] print(gen.gi_running, "func") # True func  yield from [2, 3] print(gen.gi_running, "func") # True func  yield from [4, 5] print(gen.gi_running, "func") # True func  gen = func() print(gen.gi_running) # False print(next(gen)) # 0 print(gen.gi_running) # False print(next(gen)) # 1 print(gen.gi_running) # False print(next(gen)) # 2 print(gen.gi_running) # False print(next(gen)) # 3 print(gen.gi_running) # False print(next(gen)) # 4 print(gen.gi_running) # False print(next(gen)) # 5 print(gen.gi_running) # False print(next(gen)) # StopIteration: 
Enter fullscreen mode Exit fullscreen mode

gi_suspended can check if the generator is currently suspended(paused) as shown below:

<yield>:

def func(): print(gen.gi_suspended, "func") # False func  yield 0 print(gen.gi_suspended, "func") # False func  yield 1 print(gen.gi_suspended, "func") # False func  yield 2 print(gen.gi_suspended, "func") # False func  gen = func() print(gen.gi_suspended) # False print(next(gen)) # 0 print(gen.gi_suspended) # True print(next(gen)) # 1 print(gen.gi_suspended) # True print(next(gen)) # 2 print(gen.gi_suspended) # True print(next(gen)) # StopIteration: 
Enter fullscreen mode Exit fullscreen mode

<yield from>:

def func(): print(gen.gi_suspended, "func") # False func  yield from [0, 1] print(gen.gi_suspended, "func") # False func  yield from [2, 3] print(gen.gi_suspended, "func") # False func  yield from [4, 5] print(gen.gi_suspended, "func") # False func  gen = func() print(gen.gi_suspended) # False print(next(gen)) # 0 print(gen.gi_suspended) # True print(next(gen)) # 1 print(gen.gi_suspended) # True print(next(gen)) # 2 print(gen.gi_suspended) # True print(next(gen)) # 3 print(gen.gi_suspended) # True print(next(gen)) # 4 print(gen.gi_suspended) # True print(next(gen)) # 5 print(gen.gi_suspended) # True 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:

  • send() starts or resumes the generator, sends a value into the variable assigned a yield statement only when resuming the generator, executes a yield statement to return a value and pauses the generator or raises StopIteration if the generator is terminated:
    • The 1st argument is value(Required-Type:Any):
      • It must be None when starting the generator.
      • Don't use value=.
  • The variable assigned a yield statement has None by default.
  • yield from only with a generator(yield from generator) works with send() properly.

<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 print(next(gen)) # None func  # 1 print(next(gen)) # None func  # 2 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 yield statement cannot be assigned to a for statement as shown below:

def func(): for v in yield [0, 1, 2]: # for v in yield from [0, 1, 2]:  print(v) # SyntaxError: invalid syntax 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)