Implement Go channels in Python
Go language is well known for its native support and handy methods of communicating and synchronizing built into the language.
Python is a multi-paradigm language on its own and also shines when writing concurrent code with asyncio. In this article we will implement Go channels in Python to provide as similar an experience to writing concurrent code in Python as you would using Go.
Requirements
- Supports arrow notation for sending and receiving over the channel
- Supports
rangeandclose - Supports buffering
Go approach
Let’s now look at the classical producer-consumer example which is often used to showcase synchronization using exit/done channel.
1 | package main |
Let’s write some Python
Due to language limitations, some syntax adaptations are necessary, but we can get remarkably close to Go’s elegant channel interface. First, we need to implement a Channel class that wraps Python’s asyncio.Queue and a Value class to hold received data.
1 | import asyncio |
Now let’s create the Value class that will allow us to receive data using the arrow syntax:
1 | from typing import Any |
In order to be able to receive values from a channel using value << channel syntax, we need to implement the __lshift__ method on the Value class.
1 | async def __lshift__(self, channel: Channel): |
Now let’s check both sending and receiving data to a channel:
1 | ch = Channel() |
When running this code as-is you will get the error below:
1 | RuntimeWarning: coroutine 'Channel.__lshift__' was never awaited |
That’s the limitation we’ve talked about, as we have to explicitly run the coroutine using await, so exact syntax is not possible to achieve. To make everything work, update the lines as shown here:
1 | await (ch << 23) |
Finally, the output looks as expected, so we can move forward and implement more complex logic.
1 | v holds '23' value |
To support range and close operations on channels, we need a few helper functions:
1 | def Close(channel: Channel): |
Put everything together
Now we can recreate the producer-consumer example from Go in Python:
1 | async def main(): |
As you can see, the code is almost identical and visually undoubtedly similar to the Go variant (wrt syntax differences). There is also a minor change to the logging configuration (to make output look similar) and adding of the go alias for create_task function to schedule an execution of a coroutine.
1 | import asyncio |
Here’s the output produced when running the program:

The buffered channel with size 2 allows the producer to send values ahead of consumption, which is why you see “Sent 0” and “Sent 1” appearing before the consumer starts receiving. This demonstrates that our Python implementation successfully mimics Go’s channel behavior including buffering and synchronization.
