HORUS/blog

Sep 5, 2026 · python · robotics · real-time · software-architecture

Why Python Is Fine for Robotics Until It Suddenly Isn't

Python is fine until one loop has a deadline it keeps missing. Move that loop to C++ or Rust, keep the rest in Python, and skip the whole rewrite.

Python is fine for robotics until one loop has a deadline it keeps missing, and then that loop alone moves to C++ or Rust. The reason is interruption rather than arithmetic: an interpreter pauses to tidy memory, and anything sharing the program — a camera, a log, a model — can make the timed part wait. The verdict never flips if nothing on your robot fails visibly for being late, and the split it implies is one both ROS 2 and HORUS are built to support. The rest of this post is for a Python-first builder, often arriving from machine learning, who wants to know which part to move and when.

The notebook version worked. You loaded the model, ran it on a frame, got a sensible answer, wrote a loop that turns the answer into a command and sends it to the arm. On the desk it was convincing enough that you told someone about it.

Now it is on the robot and something is off in a way you cannot name. The arm mostly tracks and then twitches once, on a path it completed cleanly a minute earlier. The robot seems to be reacting to where things were rather than where they are, but only sometimes. You add a print statement to find out when it happens and the behaviour changes, which is its own bad news. Nothing throws. Nothing is logged. The processor is not pinned, memory is fine, and the machine looks healthy by every reading you can take. Somebody on a forum says Python is too slow and you should write it in C++, which sounds both plausible and like months you do not have. You cannot tell whether you have hit a real limit of the language or written the program in a shape that would misbehave in any language.

Should you build your robot in Python?

Yes, and you should expect to move a small part of it out later. Python is the right default for robotics work because almost everything on a robot is judged by whether it eventually produces the correct answer, not by whether it produced it on schedule: deciding where to go, running a model at whatever pace the camera manages, talking to an operator, saving data, calibrating, replaying logs. All of that rewards being able to change your mind quickly, and Python is unmatched at that. The exception is narrow and specific — one loop that reads a sensor and commands an actuator on a fixed rhythm, where arriving late once is a visible fault rather than a slightly worse average. On most robots that loop is a few dozen lines. The correct plan, then, is not a choice between languages. It is Python everywhere, written so that the one part with a deadline can be lifted out cleanly when the day comes, and left where it is when that day never arrives.

What kind of problem does "Python is too slow" actually describe?

It usually describes a program that waits, not a program that computes slowly. Those are different faults with different fixes and they get confused constantly. A slow program takes a long time to produce an answer, and you fix it by doing less work or doing that work in a compiled language. A waiting program produces answers quickly on average and occasionally not at all on time, because something inside it stopped to do something else: fetch a frame, write a file, tidy memory, wait its turn behind another task. Robots are unusually sensitive to the second fault and unusually tolerant of the first. A machine acting on a description of the past can be tuned around a delay it can count on; it cannot be tuned around a delay that is normally small and occasionally not. That is why builders talk about deadlines rather than speed. The arm stops before it hits the table only if the check that says stop happens on schedule every single time, including the moment a log is being flushed and a model is loading weights.

What are your actual options once Python stops being enough?

There are eight options and only one of them is a rewrite. You can leave the whole robot in one Python program and accept its worst moments, which is right for a great many machines. You can push one slow function into a compiled extension. You can split the robot into a Python process that decides and a compiled process that keeps time — which is what ROS 2 supports through its Python and C++ client libraries with a very large ecosystem attached, and what HORUS exists for on a single machine, being an open-source real-time robotics middleware for Rust, Python and C++ in which all three share the same shared-memory ring buffers, so messages between processes on one machine are not serialised. You can move the loop onto a microcontroller. You can move the model off the robot entirely. You can rewrite everything in a compiled language. Or you can stay inside the SDK your arm arrived with. Read the table as a description of situations rather than a ranking.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
One Python program for the whole robotFirst robots, bench experiments, research scriptsPython and your sensor's libraryNothing on the machine is judged by its worst momentA timed loop shares the program with a camera or a model
Python with one function in a compiled extensionTeams with a single expensive calculationBuilding native modules and packaging themOne function is genuinely the whole problemThe fault is timing rather than a slow function
Model in Python, control in a compiled process, on ROS 2Builders who need borrowed drivers, mapping or planningLinux, ROS 2 tooling, launch files, message typesThe ecosystem is the reason you are there at allOne machine, one task, nobody to maintain a stack
HORUS between a Python process and a compiled loopSmall teams on one machine mixing languagesRust, Python or C++, plus your own control codeCamera-sized data crosses between processes constantlyYou want drivers, mapping and planning handed to you
Timed loop on a microcontroller, Python aboveAnyone with a motor that must never be commanded lateEmbedded C or an embedded Rust toolchainThe rhythm matters more than the thinking above itThe loop itself needs a map, a model or a camera
Model off-board, thin client on the robotTeams whose model will not fit the onboard computerNetworking and what to do when the link stallsThe robot can wait politely for an answerThe robot must keep working with no network
Whole robot in C++ or RustTeams shipping a product on settled hardwareA compiled language and its build toolingThe behaviour must be the same on the worst dayYou are still discovering what the robot should do
Vendor SDK driven from PythonOwners of a complete arm or mobile baseThe vendor's API and its supported languageThe machine should do its documented jobYou must mix in hardware the vendor never planned for

What is the exact moment Python stops being fine?

The moment is when a second thing enters the program that the timed part cannot control. Up to that point you have a script that reads, computes and writes, and Python does that on time far more consistently than its reputation suggests. Then you add the camera, and now the loop shares a program with something that arrives on its own schedule and takes real work to handle. Or you add the model, which occupies the interpreter for a stretch each time it runs. Or you add logging to disk, which pauses whenever the operating system decides to actually write. Any one of those turns a predictable loop into an unpredictable one, and it happens on a single afternoon rather than gradually. There is a second, quieter moment: when the data being passed around gets large. A few numbers crossing between two parts of a robot is nothing. A camera frame crossing four times, copied and rebuilt each time, becomes most of what the machine is doing — which is the subject of what zero-copy messaging is and why roboticists care.

What should you do if you came to robotics from machine learning?

Keep Python and change where the boundary sits, because the habits that serve you in training are exactly the ones that hurt on a robot. In training, getting through the data is everything and a pause costs you nothing but patience; batching, prefetching and letting the pipeline buffer are all virtues. On a robot each of those becomes a way of acting on stale information, because a buffered frame is a frame describing a world that has already changed. The first thing to do is stop queueing sensor data: for control, the newest frame is the only frame that matters and the backlog should be dropped rather than worked through. The second is to accept that inference and control belong in different programs, since a model that occupies the interpreter for a stretch will hold up anything sharing that interpreter. The third is to notice how much of your time goes into moving arrays between processes, because that is the cost that grows as the robot gets more sensors, and it is invisible in a notebook where everything shares one address space.

What does the computer on your robot change about the answer?

A small onboard computer moves the tipping point much earlier, so Python stops being fine sooner than it would on a workstation. A single-board computer is a real machine running a real operating system, which means your program is one of several things asking for attention and the operating system is entitled to look elsewhere at an inconvenient moment. It also has less headroom to absorb a mistake: on a desktop, a wasteful copy of every frame disappears into the spare capacity, and on a small board it is the difference between smooth and stuttering. Start-up cost matters too, since a robot that takes a long time to become responsive is one you will test less often. The practical pattern on small hardware is layered: a motor controller or microcontroller holds the fastest rhythm, one compiled process handles anything that must not be late, and Python sits above deciding things at human pace. Each layer talks to the one below in messages rather than function calls, so a slow layer cannot drag a fast one along with it.

What if you have a demo date to hit?

Write it in Python, all of it, and buy any rhythm you cannot afford to build. A deadline is a budget on how many times you can change your mind, and Python maximises that number more than any other decision you will make. The failure mode that ruins demo dates is not a slow language; it is a half-finished rewrite that arrives on the day working in neither language. So if the machine genuinely needs a rhythm Python cannot hold — a balancing robot, a fast gripper, a motor that must not be commanded late — purchase that rhythm rather than writing it: a motor controller with the loop already inside, a microcontroller running one fixed behaviour, or a small compiled program you keep deliberately boring. Everything else stays in Python where you can edit it between attempts. It is also worth being honest about what the demo needs to prove, since a great many impressive demonstrations are not autonomous at all, as teleoperation versus autonomy in robot demos sets out.

What if Python is the only language anyone on the team knows?

Then stay in Python and spend your discipline on shape instead of syntax. A team fluent in one language will get further in a month than the same team half-fluent in two, and the things that sink early robots — reversed signs, wrong units, a sensor mounted backwards, a gripper that reports closed while holding nothing — are found by trying, not by compiling. What you owe your future self is structure: keep the code that touches hardware separate from the code that decides, put control maths in plain functions that take numbers and return numbers, and keep anything that waits on disk, network or camera out of the part that must keep time. Do that and the day a compiled loop becomes necessary you are moving a small file, not untangling a robot. There is also a legitimate route that avoids compiled languages entirely on the main computer: leave the timed loop on hardware that already runs compiled code, meaning the motor controller or a microcontroller. That is the standard architecture rather than a compromise, and it lets a Python-only team ship a machine that holds its rhythm.

What does the failure actually look like on the robot?

It looks like a machine that is fine until it briefly, unpredictably is not, with nothing anywhere to explain it. The arm tracks smoothly and twitches once. The balancing robot holds, then leans, then over-corrects and settles. The wheels hum at a steady pitch and the pitch wavers whenever the vision process wakes up. The robot avoids an obstacle that is no longer there and clips one that is. Nothing crashes, nothing is logged, and every tool you would reach for reports a healthy system, because from the software's point of view nothing went wrong — every instruction ran correctly, just later than the physical world required. Two tells are worth memorising. The fault moves when you add logging, because logging changes the timing you were trying to observe. And it gets worse when you add features that have nothing to do with motion, such as a web interface or a recorder, which is the clearest possible sign that the problem is the shape of the program rather than the speed of the arithmetic.

What do you give up by moving part of the robot out of Python?

You give up the speed of changing your mind, which is what got you this far. A Python loop can be edited and rerun in seconds; a compiled one needs a build, and on a small board that build is long enough to break your concentration. You give up having a single place to look: two programs start separately, can disagree about what is running, and a bug can live in the seam between them rather than inside either. Debugging gets harder because the interesting moment is now spread across two processes. Deployment gets harder because you are shipping a binary that must match the machine it runs on. And you take on a boundary question that a single program never had to answer out loud — what should the loop do when the decision above it is late, or missing, or describing a world that has moved on. Those costs are real, and they are worth paying only for the parts genuinely judged by their worst moment, which is why keeping that part small is the whole art.

When is ROS 2 the better choice?

ROS 2 is the better choice whenever the code you would rather not write already exists as a ROS 2 package. A robot that must build a map of a building and drive to a goal inside it is a ROS 2 project, because mapping and navigation represent an amount of accumulated work you will not reproduce on the side. The same goes for arm motion planning around obstacles. If your lidar, depth camera or arm ships with a ROS 2 driver and nothing else, that settles it too, since porting a driver is a poor trade for nearly everyone. ROS 2 also wins when the system spans more than one computer, when new people must be able to read the robot on their first day, and when a lab already shares the tooling and the vocabulary. In all of those cases HORUS is not the answer and choosing it means rebuilding plumbing you could have inherited. Note that the language question is separate: ROS 2 has an official Python library, and a ROS 2 robot with a late loop has precisely the same problem for precisely the same reasons.

Is the global interpreter lock the reason your robot stutters?

No, and here is why: the lock is one mechanism among several, and the shape of your program is what actually decides. The interpreter lock stops two Python threads running Python code at the same moment, which certainly bites when a vision thread and a control thread live in one process. But a robot with a single-threaded control script can stutter just as badly because the interpreter paused to tidy memory, or because the loop stopped to write a log line, or because the operating system gave the processor to something else. Removing the lock would not fix any of those. More usefully, the same fault appears in compiled languages: put a disk write inside a timed loop in C++ and it will hesitate too, just with a wider margin before you notice. The test is cheap. Strip the loop down to reading a sensor and writing a command, with nothing else in the program, and see whether the stutter survives. If it disappears, you have a structure problem that a rewrite would have hidden rather than solved.

Will rewriting the robot in Rust fix it?

Partly, but not the way you think. Rewriting the one timed loop in Rust or C++ does help, genuinely, because it removes interpreter pauses and lets that loop keep running while everything else on the machine carries on. Rewriting the whole robot buys you a much longer project and a stutter of a similar shape, because the parts you translated — the mission logic, the operator interface, the logger, the calibration scripts — were never the problem, and the loop is still sharing a machine with all of them. Big rewrites also begin at the worst moment: after the prototype works and before anyone knows which behaviours are final, so you translate code about to be deleted. The version that pays is small and surgical: take the loop out, keep it to reading a sensor, computing a command and writing an output, leave everything else alone. If you are weighing the language on its own merits rather than as a cure, whether to write robot software in Rust is the more useful question.

How do you tell which problem you actually have?

Ask whether the robot is late or wrong, because those two words lead to completely different repairs. If the answer it produces is correct but arrives after the moment it was needed, you have a timing problem, and the fix is structural: separate what waits from what keeps time, and move the timed part into its own program. If the answer is simply slow to compute — a model that takes a while, a search that takes a while — you have a work problem, and the fix is doing less work or doing it elsewhere. A second question separates the remaining cases: does the trouble get worse when you add something unrelated to motion? If yes, the loop is sharing with a neighbour it cannot control. A third question is about volume: are you moving camera-sized things between processes constantly? If so, the copying is the job now, and no language choice repairs that. The broader framing of one language versus several is covered in should you use one language or several in a robot.

Decide by symptom rather than by preference:

When two options stay close, weigh them on the five axes of the HORUS Fit Framework — ecosystem size, setup effort, team size fit, deployment target, and licence — and take the one that loses on the fewest. No scores and no numbers: five honest questions about your situation rather than about the software. If your robot keeps landing on one machine with Python above and something compiled below, star HORUS on GitHub so it is in your list when you start building.

Found this useful? Share it:Discuss on HNShare on X