Deep Engineering #21: Mojo–Python Interop in Late 2025 with Ivo Balbaert
Free-threaded CPython meets Mojo 0.25.6—practical patterns for calling across the Python↔Mojo boundary.
Join Snyk on October 22, 2025 at DevSecCon25 - Securing the Shift to AI Native
Join Snyk October 22, 2025 for this one-day event to hear from leading AI and security experts from Qodo, Ragie.ai, Casco, Arcade.dev, and more!
The agenda includes inspiring Mainstage keynotes, a hands-on AI Demos track on building secure AI, Snyk’s very FIRST AI Developer Challenge and more!
✍️From the editor’s desk,
Welcome to the twenty-first issue of Deep Engineering.
Python 3.14 shipped earlier this week and it makes free-threaded CPython officially supported, keeps the JIT experimental, and retires PGP signatures in favor of Sigstore for release verification—material shifts for anyone embedding Python or managing supply-chain policy.
Mojo’s latest stable release is 0.25.6.0, published on September 22, 2025 (via PyPI). This drop also coincides with Modular’s 25.6 cycle, which highlights that the PyPI package now bundles the compiler, LSP server, and debugger—making pip install mojo a complete setup for most dev environments.
But what is the state of Python–Mojo interoperability today? This issue answers that question in today’s feature and goes further with
’s latest article within our Expert Insight section—a practical walkthrough of Mojo↔Python interop: comparing Python and Mojo, showing how Mojo calls Python viaPython.import_module/PythonObject, using local and ecosystem libraries from Mojo, compiling/running Mojo code, and previewing how Python can call Mojo.Balbaert is a lecturer in web programming and databases at CVO Antwerpen and a long-time Packt author of introductions to new languages—including Dart, Julia, Rust, and Red.
Let’s get started!
Mojo and Python: State of Interoperability in Late 2025 with Ivo Balbaert
From day one, Mojo has embraced Python’s syntax and ecosystem. Mojo can directly import and call Python modules via its built-in python module, letting developers reuse the Python library stack. Example: Python.import_module(”compute”) → compute.mul(42, 7); or var np = Python.import_module(”numpy”) → np.arange(7). Calls return PythonObject, a handle to Python data.
PythonObject is the core bridge. Mojo executes Python through CPython, and any Python value seen by Mojo is a PythonObject (numbers, strings, lists, NumPy arrays, etc.). Mojo provides conveniences for constructing and handling these—e.g., var py_list: PythonObject = [42, “cat”, 3.14159] evaluates to a Python list. Certain Mojo values convert to PythonObject automatically.
This lets Mojo use Python types and libraries where Mojo is not yet feature-complete. For example, Python’s arbitrary-precision int avoids overflow where Mojo’s fixed-size integers would. The same applies to advanced Python APIs—from dataframes to ML toolkits—usable today without waiting for Mojo-native equivalents.
Mojo’s integration invokes the Python interpreter at compile time to resolve imports and execute needed Python code, wiring in the hooks for runtime interop. Any Mojo function that calls Python must be marked raises, since Python operations can throw; you structure code accordingly (error handling, raises) and accept that interop isn’t “free.”
Using Python from Mojo requires a Python runtime environment. Mojo executables do not bundle Python or its libraries; the target must provide them. In practice, teams add Python to the project (e.g., with Pixi) and install required packages (NumPy, Pandas, etc.). Missing modules cause import-time failures. Interop works best in controlled environments (containers or Pixi-managed) where dependencies are guaranteed—for example, calling matplotlib from Mojo is powerful, but it still relies on Python’s plotting stack being present.
Two-Way Bridge (Preview): Python Calling Mojo
Up to now, Mojo’s focus has been on calling into Python. What about Python code calling Mojo? This is important for using Mojo as a performance accelerator inside existing Python applications. The good news is that in 2025 this reverse interoperability has begun to emerge. In fact, by mid-2025 the Mojo team introduced preview support for Python-to-Mojo calls, allowing developers to import Mojo functions into a Python program. The Mojo v0.25.5 release notes highlight that “the Python–Mojo bindings are available as a preview release,” enabling one to call Mojo functions from a normal Python codebase. The typical use case is to speed up hotspots in Python: write those parts in Mojo, then call them from Python without needing to switch the entire project to Mojo. Chris Lattner (Mojo’s co-founder) has noted that many people like this approach because it lets them make “slow Python code go faster” without writing C++ or dealing with a foreign API – Mojo feels like Python, so the integration is relatively seamless.
How does Python call Mojo in practice? The current approach (still in beta) is to compile Mojo code as a Python module. Mojo provides a PythonModuleBuilder API to expose Mojo functions to Python. Developers annotate a special initializer (conventionally PyInit_modulename) and export functions they want accessible in Python. Once compiled, Python can import this Mojo module (using a provided max.mojo.importer mechanism) and call Mojo functions as if they were regular Python functions.
The Mojo team has been smoothing this process: for example, as of August 2025, Mojo functions can accept Python keyword arguments directly, so Python callers can use foo(x=3) syntax without extra wrapper code. They also added conveniences like allowing Mojo methods to use a raw pointer to self instead of a PythonObject, reducing boilerplate when binding class methods.
In September 2025, Modular even made Mojo installable via pip in a Python environment, “enhancing interoperability with the Python ecosystem” and making it easier to drop Mojo code into Python projects. All these developments signal that Mojo is evolving toward a two-way street with Python. While Mojo→Python calls are stable and heavily used, Python→Mojo calls are in preview with some manual steps – but they reflect a clear direction in Mojo’s roadmap. Ultimately, the goal is that a Python developer can use Mojo modules as if they were any other C extension or library, but with far less friction since Mojo shares Python’s DNA.
Why Interop Matters for Mojo’s Adoption
Interop is the practical bridge between a new language and production reality; for Mojo, it’s the difference between theoretical promise and immediate usefulness.
Leverage the ecosystem: Python’s libraries and tooling are decades ahead. Mojo can’t replace them overnight, so calling into Python lets teams use proven modules (from NumPy to Pandas to TF) while Mojo-native equivalents mature.
Enable incremental migration: Port only hot paths to Mojo for speed, keep everything else in Python, and stitch at clear boundaries. This mix-and-match path delivers gains now without a risky rewrite-and-replace program.
Match the audience: Most early Mojo users are Python developers. Familiar syntax and direct use of Python modules reduce cognitive load and adoption friction; Mojo complements existing workflows rather than displacing them.
Hedge against youth: Where Mojo lacks features or libraries today, Python fills the gaps. Teams can ship sooner—Mojo for performance, Python for breadth—without waiting for Mojo to rebuild the world.
Mojo is indeed an accelerant layered onto Python.
Friction Points and Limitations
Despite its promise, Mojo–Python interoperability comes with significant friction points that engineering teams should understand:
Language feature gaps: Mojo is still missing many dynamic features that Python developers take for granted. For example, user-defined classes and inheritance are not yet supported in Mojo, and top-level code must be wrapped in functions. Python’s dynamic typing and reflection capabilities have only limited analogs in Mojo at this stage. The Mojo roadmap explicitly defers “core dynamic features” like classes and untyped Python-style code to a later phase. Similarly, Mojo’s standard library is incomplete (by design in Phase 1), with “many missing APIs (e.g. potentially file system APIs).” Interoperability gives access to Python replacements, but a Mojo project can become dependent on Python for routine tasks. Mojo isn’t a full Python drop-in, and developers will hit “Not implemented yet” walls.
Performance overhead and design constraints: Calling Python from Mojo carries the baggage of the Python interpreter. Each cross-language call can be expensive; Mojo loses its speed advantage when executing Python code (GIL and interpreter overhead). Frequent round-trips can bottleneck performance. The team encourages using
PythonObjectonly when necessary and otherwise writing pure Mojo, noting thatPythonObjects are heap-allocated and garbage-collected. Avoid Python calls inside tight loops or performance-critical paths. Compile-time invocation of Python can slow builds and couples the compiler to Python. If Mojo calls Python from multiple threads, the GIL will serialize those calls.Deployment and distribution complexity: A Mojo program that uses Python is not self-contained. You need to ensure the target environment has the exact Python version and packages your Mojo code expects; mismatches will fail at import. This often means shipping a Python runtime or containerizing the environment. Tools like Pixi help during development, but production still means two dependency graphs (Mojo and Python).
pip install mojostreamlines setup, but production packaging needs planning.Debugging and tooling limitations: Debugging a mixed Mojo/Python project is challenging. Mojo has an LLDB-based debugger and LSP; Python has its own. There is no integrated solution to step through both together. Interface bugs (e.g., unexpected Python objects causing Mojo type errors) can be cumbersome to trace. The roadmap lists “Debugger/LSP support for mixed Python/Mojo applications” as an open task. Today, teams often debug each side separately, and testing across the seam can be awkward.
Ecosystem maturity and “beta” concerns: Mojo is evolving rapidly, and breaking changes can happen. Interop surfaces may change as designs are refined. Messaging has shifted from “full Python superset now” toward performance-focused coexistence. Outside interop, general-purpose conveniences (I/O, networking) are still nascent, so some projects involve other languages or FFI.
Missing features, boundary costs, operational overhead, and evolving tooling don’t negate Mojo’s potential; rather, they define where interop helps and where architectural care is needed.
Recent Developments (Jul–Oct 2025) and Roadmap Signals
The past few months (Q3 2025) have seen significant improvements in the Mojo ecosystem that strengthen Python interoperability or clarify Mojo’s direction:
Mojo available via pip: In late September 2025, Modular released Mojo v0.25.6 which for the first time allowed installing Mojo through Python’s package manager (pip). The team noted this enhances Mojo’s integration with the Python ecosystem, “making it easier to extend your Python code with Mojo”. In practical terms, a Python developer can now do
pip install mojoand get the Mojo compiler and libraries in a virtual environment. This lowers setup friction for trying Mojo alongside Python; you no longer have to use Pixi if you prefer not to.Preview of Python calling Mojo (bindings): Python→Mojo bindings were introduced as a preview in summer 2025. The v25.5 (August 2025) changelog framed this as a way to “speed up hot spots/slow Python code by rewriting certain portions… in Mojo”. Supporting features landed alongside: accepting keyword arguments in Mojo functions when called from Python; easier method binding. For example, a Mojo function can have
fn do_something(x: Int, kwargs: OwnedKwargsDict[PythonObject])and Python can calldo_something(5, y=10)withkwargscapturingy. APythonModuleBuilderAPI and related utilities also appeared. These preview features point to a concrete path toward fully supported two-way interop.Community engagement and content: The community has published more guidance on Mojo–Python interop. The open-source py2mojo tool appeared for early Python→Mojo translation, and Modular has stated plans for an official translator. Public discussions, including Chris Lattner’s July 2025 Reddit comments, acknowledged that “Python superset” was oversold and refocused expectations on performance and gradual evolution. Hearing from maintainers directly has helped teams set realistic production roadmaps.
Roadmap clarity: Modular’s roadmap (updated in this period) reinforces the trajectory: Phase 1 prioritizes core language features and Python interop basics; “Debugger/LSP support for mixed Python/Mojo applications” is listed as a to-do. It also states that full Python library parity and dynamic language features are not Phase 1 goals; later phases (e.g., Phase 3) target classes, inheritance, and more dynamic behavior. The message: interop will keep improving through 2025 while the focus remains performance, safety, and foundations.
Mojo’s stated goal is to become a superset of Python; it isn’t there yet, but the last quarter’s releases and previews show steady progress toward deeper, smoother interoperability.
💡Key Takeaways
Mojo leverages Python’s ecosystem: Mojo can import Python modules and work with Python objects, exposing libraries like NumPy and Matplotlib from Mojo code. This bridge offsets Mojo’s young standard library.
Interoperability enables gradual adoption: In practice, teams move compute-heavy paths to Mojo while keeping surrounding code in Python, reusing existing libraries and minimizing disruption.
Limits and costs remain: Mojo is not yet a full Python substitute; gaps (e.g., classes, some I/O, dynamic features) often route work back to Python. Cross-language calls add interpreter/GIL overhead and require a Python runtime. Mixed-language debugging and tooling are still maturing.
Recent releases reduce friction:
pip install mojosimplifies setup, and preview Python→Mojo bindings support Pythonic call patterns. Public roadmap updates indicate continued investment in interop and staged delivery of dynamic features.Current operating picture: In 2025, production usage commonly combines Python’s breadth with Mojo’s targeted performance, with architectural care around boundaries, dependencies, and tooling.
🧠Expert Insight
Ivo Balbaert’s new article walks through a Python–Mojo comparison, the interop mechanics, Mojo→Python advantages, practical use of Python functions and PythonObject, working with local modules and ecosystem libraries, and even links to a preview of calling Mojo from Python.
Building with Mojo (Part 3): Python and Mojo
This article is Part 3 of our ongoing series on the Mojo programming language. Part 1 introduced Mojo’s origins, design goals, and its promise to unify Pythonic ergonomics with systems-level performance.
In Case you missed it:
🛠️Tool of the Week
maturin — the production-grade way to ship Rust in Python.
It’s the build backend behind pydantic-core, and it keeps shipping fast (new releases in the past week). If you’re chasing Python performance without leaving the ecosystem, this is the tool.
📎Tech Briefs
Python 3.14 is GA: Ships official free-threaded builds, an experimental JIT (off by default), template strings (PEP 750), deferred annotations (PEP 649), and more; the docs and release page are live.
PyCrucible aims for “one-click” Python app redistribution: A new packaging tool intended to create redistributable builds with minimal setup, complementing PyApp/uv in the “ship Python apps” space.
Polars 1.34.0 released: A substantial update with query-engine speedups, deterministic Python package variant import order, unsigned 128-bit integer support, and numerous streaming/Parquet fixes.
Microsoft previews open-source Agent Framework: A .NET/Python SDK and runtime for building graph-orchestrated multi-agent apps, with MCP/A2A support and Azure AI Foundry integration.
AI infra roundup: Amazon Bedrock’s AgentCore MCP server and more: SD Times’ weekly digest highlights an MCP-compatible agent server for Bedrock plus other platform updates, signaling rapid standardization around tool/server protocols for agents.
That’s all for today. Thank you for reading this issue of Deep Engineering. We’re just getting started, and your feedback will help shape what comes next. Do take a moment to fill out this short survey we run monthly—as a thank-you, we’ll add one Packt credit to your account, redeemable for any book of your choice.
We’ll be back next week with more expert-led content.
Stay awesome,
Divya Anne Selvaraj
Editor-in-Chief, Deep Engineering
If your company is interested in reaching an audience of developers, software engineers, and tech decision makers, you may want to advertise with us.







