An Introduction to Python Generators and Coroutines

Before we get into the topic, let's get some definitions right first:

  1. Iterator - this is an object that can be iterated upon. An iterator object returns data, one element at a time. Some of python’s containers that are iterable include lists, tuples, strings. These have the __iter__ method defined, which when called returns an iterator object.
  2. Generator - a simple way of creating iterators defined above.
  3. Coroutine - same concept as generators but we’ll see how they differ later in the article.

To better illustrate, consider a list:[pastacode lang="python" manual="%20my_list%20%3D%20%5B1%2C%202%2C%203%2C%204%2C%205%5D%0A%20%20iter_obj%20%3D%20iter(my_list)%20%23%20calls%20__iter__%20%2C%20and%20an%20iterable%20object%20is%20returned%0A%20%20next(iter_obj)%20%23%20prints%201%0A%20%20next(iter_obj)%20%23%20prints%202%0A" message="" highlight="" provider="manual"/]And so on, till we have gotten all items from the list after which the StopIteration exception is raised. By the way, we can also call iter_obj.__next__()In order to build your own iterators, you have to implement a class with the __iter__ and __next__ methods. At times this can be very cumbersome, and that is where generators come in. Generators allow us to easily create iterators. All implementations mentioned above are not required as they are automatically handled. It’s fairly simple to create a generator in python: as easy as defining a normal function with the yield statement rather than the return statement. If you’re wondering what’s the difference between these two terms; return terminates a function while yield pauses the function while saving its state and will continue from where it left off on successive calls.As an example, we can define a simple function that is a generator for Fibonacci numbers. (Some people might argue that the sequence starts from 0 but let’s focus on the python here :[pastacode lang="python" manual="%20def%20fibonacci(limit)%3A%0A%20%20%20n2%20%3D%201%0A%20%20%20if%20limit%20%3E%3D%201%3A%0A%20%20%20%20%20%20%20yield%20n2%0A%0A%20%20%20n1%20%3D%200%0A%0A%20%20%20for%20_%20in%20range(1%2C%20limit)%3A%0A%20%20%20%20%20%20%20n%20%3D%20n1%20%2B%20n2%0A%20%20%20%20%20%20%20yield%20n%0A%20%20%20%20%20%20%20n1%2C%20n2%20%3D%20n2%2C%20n%0A%0ATo%20get%20the%20data%2C%20we%20can%20do%3A%0A%0AFib%20%3D%20fibonacci(5)%20%23%20we%20limit%20the%20number%20of%20elements%20we%20get%20to%205%0Anext(fib)%20%23%20prints%201%0Anext(fib)%20%23%20prints%201%0Anext(fib)%20%23%20prints%202%0Anext(fib)%20%23%20prints%203%0Anext(fib)%20%23%20prints%205%0Anext(fib)%20%23%20StopIteration%20exception%20is%20raised%0A" message="" highlight="" provider="manual"/]Simple, isn’t it? On to coroutines now. Please note that in this article I talk about simple coroutines rather than native coroutines.We’ve seen that generators allow us to pull data and pause execution from a function context. Coroutines allow us to push data. In this case, the yield statement basically means “Wait until you get some input data”. We also use the yield statement a bit different as we shall see in a bit. We’ll go through a very simple example which provides a good basis: Imagine we have a list of names and want to find out if some names we have in mind (we don’t know how many since they’ll be input at runtime) are in this list.[pastacode lang="python" manual="def%20check_name_exists()%3A%0A%20%20%20names%20%3D%20%5B%22Dennis%22%2C%20%22Nick%22%2C%20%22Fury%22%2C%20%22Tony%22%2C%20%22Stark%22%5D%0A%20%20%20print(%22Ready%20to%20check%20for%20names.%22)%0A%20%20%20while%20True%3A%0A%20%20%20%20%20%20%20name%20%3D%20yield%0A%20%20%20%20%20%20%20if%20name%20in%20names%3A%0A%20%20%20%20%20%20%20%20%20%20%20print(%22Found%22)%0A%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20print(%22Not%20found%22)%0A" message="" highlight="" provider="manual"/]We can now do:[pastacode lang="python" manual="coro%20%3D%20check_name_exists()%20%0A" message="" highlight="" provider="manual"/]However, a coroutine can’t start receiving data right away and we’ll get an error when we try to do so. We first need to prime it. This can be done easily by doing:[pastacode lang="python" manual="%20coro.send(None)%20%23%20prints%20out%20Ready%20to%20check%20for%20names.%0A" message="" highlight="" provider="manual"/]Now, we can start sending some data to our coroutine, and this is done by making use of the send method.[pastacode lang="python" manual="coro.send(%22Dennis%22)%20%23%20prints%20out%20Found%0Acoro.send(%22Captain%22)%20%23%20prints%20out%20Not%20found%0A" message="" highlight="" provider="manual"/]An important thing to note is if our generator or coroutine does not have a breakpoint, we can manually stop it by doing:[pastacode lang="python" manual="coro.close()%0A" message="" highlight="" provider="manual"/]That’s it for now, I hope this was helpful in beginning to understand generators and coroutines. You can read more from these resources:

READ: How to build your own version of React from scratch (Part 1)

Related posts

The latest articles from Andela.

Visit our blog

Top takeaways from Gartner IT Symposium

As the symposium concluded, it became evident that the journey into the AI-driven future is both challenging and exhilarating for IT leaders.

Android ML face detection with Camera X

In this Writer's Room tutorial, Andela Community member Stephen Henry explains how to integrate ML face detection into an Android app using CameraX.

Advance your career in: Application Engineering

Serving as a bridge between the engineering and business sides of an organization, application engineers are highly sought after - and by upskilling in this field, you can set yourself up for an incredibly impactful and lucrative career.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.