PyZeROS: a Python-first alternative to `rclpy` over Zenoh

Hi everyone,

I am releasing PyZeROS, an experimental alternative to rclpy for communicating with ROS from Python. This is not a Python wheel packaging rclpy: I bit the bullet and wrote a client from scratch in pure Python.

You can install PyZeROS like a standard python package:

pip install pyzeros

It does not require a ROS installation, colcon workspace, message compilation, or a ROS executor. It communicates with ROS 2 through Zenoh and interoperates with standard ROS 2 nodes using rmw_zenoh_cpp.

The main features are:

  • Interoperability with Jazzy and Lyrical
  • Designed for asyncio and asynchronous Python
  • Topics, services, and QoS
  • Custom ROS messages defined directly with Python classes
  • Installation through pip with minimal dependencies

A subscriber looks like this:

import asyncio

import asyncio_for_robotics as afor
import pyzeros
from ros2_pyterfaces.cyclone.all_msgs import String


async def main():
    sub = pyzeros.Sub(String, "chatter")
    async for msg in sub.listen_reliable():
        print(msg.data)


with pyzeros.auto_context(node="listener", namespace="/demo"):
    asyncio.run(main())

Why another ROS 2 client?

You’ll find that Rust has many independent ROS 2 client implementations, all with interesting designs and trade-offs. In Python, however, we have only rclpy, and RoboStack+Pixi as (fantastic) alternative installation method.

I made PyZeROS as a Python-native option built around standard Python tooling and asyncio. It’s not a repackaging of rclpy or rcl and is widely different from it. The goal is to communicate with a ROS network from python, not to integrate with the whole ROS ecosystem.

Main differences are:

  • PyZeROS installs through pip, so it should work easily with standard isolated-environment tooling like venv, uv, pipx, uvx, pixi.
  • It is primarily coded in python so no additional colcon build to compile a message types. And Python developers can dive into the source code.
  • It uses standard Python tools and small dependencies. It should run mostly anywhere.
  • PyZeROS deliberately does not aim to support every ROS feature. The Python ecosystem is prioritized: argparse for configuration, subprocess for launching processes, and importlib.resources for shared package data.
  • asyncio is the primary executor.

Why asyncio?

Robots are asynchronous systems, so they need an execution model. Python already has one: asyncio, so I use it.

Using callbacks directly is possible, but it can quickly lead to shared-state issues, locks, and complicated lifecycle management. After using asyncio in robot applications for several years, I find async/await much easier to reason about, and the python community has many tools for it. In my benchmarks, PyZeROS is also significantly faster than rclpy’s callback-and-executor model, so there does not appear to be a large performance hit from asyncio.

Custom messages

This is essential to ROS, and making them easy to define was especially important to me. In PyZeROS, you can define them directly as Python dataclasses and interoperate with standard ROS 2 messages:

from dataclasses import dataclass, field

import pyzeros
from ros2_pyterfaces.cyclone import all_msgs, idl


@dataclass
class Sphere(
    idl.IdlStruct,
    typename="tutorial_interfaces/msg/Sphere",
):
    center: all_msgs.Point = field(default_factory=all_msgs.Point)
    radius: idl.types.float64 = 0.0


pub = pyzeros.Pub(Sphere, "sphere")
pub.publish(Sphere(radius=42.0))

There is no .msg file, CMake configuration, or colcon build required on the PyZeROS side. For interoperability, the type name, field names, and field types must match the message definition used by the other ROS 2 nodes.

Performance

I also measured PyZeROS against rclpy for round-trip latency:

  • PyZeROS: ~13 µs
  • rclpy with SingleThreadedExecutor: ~70 µs

This is about 5.5× faster in this microbenchmark.

The benchmark sends sensor_msgs/msg/JointState messages continuously inside one node using two publisher/subscriber pairs. Keeping everything local minimizes transport latency, so the benchmark primarily measures executor and message serialization/deserialization overhead.

The benchmark code and complete results are available here: GitHub - 2lian/afor_benchmarks · GitHub

I also tested PyZeROS in a more realistic stress test with 100 nodes publishing mostly JointState messages at around 10 Hz. This uses my own ROS nodes for controlling a robot swarm that I have been working on for several years. In that application, the PyZeROS version used roughly one-quarter of the CPU used by the rclpy version.

Related libraries I created for PyZeROS

ros2-pyterfaces is how I define messages in Python. It provides XCDR1 serialization and ROS RIHS01 type hashes. It can serialize using cyclone_idl (tweaked by me) or cydr (created by me), with cydr being faster than rclpy to ser/de messages. It is a standalone low-level library, so it can also be used independently to send ROS messages over DDS or another RMW:

asyncio-for-robotics (afor) is the asynchronous model that I’ve been using on my robot software for a while now. It already supports ROS (rclpy) and other systems, and it now supports Lyrical and its new AsyncNode:

Feedback, testing, issues, and contributions are very welcome. I am sure there are still some rough edges, but I cannot wait indefinitely for perfection before releasing it. Reaching this point took a long time. ROS is a large ecosystem, and I am already very happy to have topics and services working

3 Likes

Have you looked at GitHub - JafarAbdi/zrm: Zenoh ROS-like Middleware · GitHub ?

1 Like

I didn’t know about that one. It does not interop with ROS, no? So it cannot send messages to/from ROS nodes. Especially as its codec is not XCDR1. PyZeROS on the other hand can talk to any ROS node.

Actually the closest I know of (and tried to use to create pyzeros) is hiroz (previously ros-z) by the Zenoh team. It’s basically a ros client for Rust, only working with Zenoh RMW, with Python binding. My issue with it was message definition that still had to be compiled in Rust, then the bindings re-generated. Overall their goal is Rust, and Python is an afterthought of hiroz, creating a lot of friction in Python so I went away from it.

1 Like

Just a note on the performance section. We recently had a contributor add AsyncIO support to the rclpy executors, which also improves performance quite a bit. It would be interesting to see the benchmark between that new executor and the one you have created to see if there are any other quick obvious wins.

1 Like

Yep I am planning to add the new AsyncioNode to my benchmarks (I added it to afor last week).

I tried it when it was at the PR stage (before lyrical release) early this year, but it was … ahem fairly terrible and buggy. I didn’t have any spare time to contribute, so I ignored it. Now is a totally different story, you guys did a fantastic job with the AsyncioNode since then, and I now have time to dive into it.

1 Like

Got some early benchmarks on a strong desktop PC!

Empty String message:

  • pyzeros: 12us
  • AsyncNode: 10us

12 joints JointStates messages:

  • pyzeros: 16us
  • AsyncNode: 22us

Standard deviation is 4us for all

So basically, AsyncNode is slightly lighter, I guess because it’s just a callback, whereas pyzeros has a queue and backpressure system that adds a few more us. And maybe the zenoh-python bindings are a few us slower than the C RMW. Also AsyncNode might be better at handling empty messages, delegating to C, instead of pyzeros that has to create empty python objects and allocate memory.

On non-empty messages, my XCDR1 codec cydr – that is very optimised for Python – makes pyzeros take the lead. But I doubt 8us come from the codec alone, maybe I also do less memory operations than AsyncNode.

PS: the new AsyncNode is insanely fast, just like what I found by using asyncio on my own. Good job to the ROS team, 6x improvement, that’s fantastic.

1 Like

Props to @nadavelkabets who did a bulk of the implementation here and will be presenting at ROScon.

If this is the kind of thing that you are interested in, would love to have you at the client library working group meetings, or in the chat!

2 Likes