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

Customer-obsessed? 4 Steps to improve your culture

If you get your team's culture right, you can create processes that will allow you to operationalize useful new technologies. Check out our 4 steps to transform your company culture.

How to Build a RAG-Powered LLM Chat App with ChromaDB and Python

Harness the power of retrieval augmented generation (RAG) and large language models (LLMs) to create a generative AI app. Andela community member Oladimeji Sowole explains how.

Navigating the future of work with generative AI and stellar UX design

In this Writer's Room blog, Carlos Tay discusses why ethical AI and user-centric design are essential in shaping a future where technology amplifies human potential.

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.