Generators and Coroutines in Python

Introduction

Python has generators. They may be used for a practice of coroutines. In this post, we will learn about generators and Coroutines in Python. Before understanding the generators and coroutines we will have to be acquainted with the concept of an Iterator.

What is Iterator?

Iterator is an object on behalf of a stream of data. This is valuable as it allows any custom object to be iterated over using the standard Python for –in syntax. This is eventually how the internal list and dictionary types work. Also, how they permit for-in to iterate over them.

An iterator is very memory well-organized. It means there is only ever one element being picked up at once. An iterator object delivers an infinite order of elements. We’ll never find our program shattering its memory allocation.

Description

Generators

Generators make available a suitable method to implement the iterator protocol. When a container object’s __iter__() method is applied as a generator, it would automatically return an iterator object.

  • Generators provides nice syntax sugar around making a simple Iterator.
  • They help to decrease the boilerplate code needed to make something iterable.
  • A Generator may support to reduce the code boilerplate related with a ‘class-based’ iterator.
  • Because they’re designed to grip the state management logic.
  • We would then have to write ourselves.

Application

  • A Generator is a function that returns a generator iterator.
  • Therefore, it perform alike to how __iter__ works.
  • Keep in mind that it returns an iterator.
  • Actually a Generator is a subclass of an Iterator.
  • The generator function itself should use a yield statement.
  • That is to return control back to the caller of the generator function.
  • The caller may then progress the generator iterator by using either the for-in statement or next function.
  • That again highlights how generators are really a subclass of an Iterator.
  • If a generator yields, it in fact breaks the function at that point in time and returns a value.
  • Calling next would move the function forward.
  • It will there moreover complete the generator function or stop at the next yield declaration within the generator function.

Example;

A version of the built-in range, with 2 or 3 arguments (and positive steps) can be implemented as:

37   def myrange(start, stop, step=1):
38 """enumerates the values from start in steps of size step that are
39 less than stop.
40 """
41 assert step>0, "only positive steps implemented in myrange"
42 i = start
43 while i<stop:
44 yield i
45 i += step
46
47 print("myrange(2,30,3):",list(myrange(2,30,3)))
  • Note that the built-in range is unconventional in how it grips a single argument.
  • Because the single argument acts as the second argument of the function.
  • The built-in range also permits for indexing.
  • For example range(2, 30, 3)[2] returns 8), which the above implementation does not.
  • However myrange also works for floats that the built-in range does not.

Coroutines

  • Coroutines are computer program components that simplify subroutines for non-preemptive multitasking.
  • Those multitasking organizes by letting execution to be suspended and resumed.
  • As coroutines may pause and resume execution context.
  • They’re well right to conconcurrent processing.
  • They allow the program to determine when to context switch from one point of the code to another.
  • This is why coroutines are normally used when dealing with concepts for example an event loop.

Application

  • Generators usage the yield keyword to return a value at some point in time inside a function.
  • Then with coroutines the yield directive can also be used on the right-hand side of an = operator to indicate it will accept a value at that point in time.

Example

  • Following is an example of a coroutine.
  • Always remember that coroutine is still a generator.
  • Therefore, we’ll realize our example uses features that are related to generators (such as yield and the next() function):
def foo():
    """
    notice we use yield in both the
    traditional generator sense and
    also in the coroutine sense.
    """
    msg = yield  # coroutine feature
    yield msg    # generator feature
coro = foo()
# because a coroutine is a generator
# we need to advance the returned generator
# to the first yield within the generator function
next(coro)
# the .send() syntax is specific to a coroutine
# this sends "bar" to the first yield
# so the msg variable will be assigned that value
result = coro.send("bar")
# because our coroutine also yields the msg variable
# it means we can print that value
print(result)  # bar
  • Note that coro is an identifier commonly used to refer to a coroutine.
  • Following is an example of a coroutine using yield to return a value to the caller.
  • That is previous to the value received through a caller using the .send() method:
def foo():
    msg = yield "beep"
    yield msg
coro = foo()
print(next(coro))  # beep
result = coro.send("bar")
print(result)  # bar
  • We can understand in the above example;
  • That when we moved the generator coroutine to the first yield statement (using next(coro))
  • That the value beep was returned for us to print.

1 thought on “Generators and Coroutines in Python”

Leave a Comment