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
JazzyandLyrical - Designed for
asyncioand asynchronous Python - Topics, services, and QoS
- Custom ROS messages defined directly with Python classes
- Installation through
pipwith 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 likevenv,uv,pipx,uvx,pixi. - It is primarily coded in python so no additional
colcon buildto 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:
argparsefor configuration,subprocessfor launching processes, andimportlib.resourcesfor shared package data. asynciois 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
rclpywithSingleThreadedExecutor: ~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