Interop

Mojo can import and use Python modules directly. This lets you leverage the entire Python ecosystem — NumPy, Pandas, matplotlib — while writing performance-critical code in Mojo. The boundary between Python and Mojo has a cost, and understanding that cost is essential.

Code

from std.python import Python, PythonObject

fn main() raises:
  var np = Python.import_module("numpy")
  var plt = Python.import_module("matplotlib.pyplot")

  var py_list: PythonObject = [1, 2, 3, 4, 5]
  var arr = np.array(py_list)
  print(arr)
  print(np.sum(arr))  # 15

  var x: PythonObject = [1, 2, 3]
  var y: PythonObject = [1, 4, 9]
  plt.plot(x, y)
  plt.show()

To run this, you need to add both numpy and matplotlib to your Mojo environment. Add the following content to your pixi.toml file.

[dependencies]
numpy = "*"
matplotlib = "*"

Then run pixi install

Object Bridging

Python objects in Mojo are wrapped as PythonObject. Every operation on a PythonObject goes through the Python runtime — dynamic dispatch, reference counting, GIL acquisition:

Constraint

Import NumPy, create a 1000-element array, and sum it using both np.sum() and a manual Mojo loop over the elements. Compare the approaches — the manual loop through PythonObject will be dramatically slower than np.sum().

Why It Matters

Every Python interop call crosses a boundary: Mojo → CPython C API → Python runtime → GIL → dynamic dispatch. For bulk operations, use Python libraries (they're already optimized in C). For tight loops, stay in pure Mojo. Never cross the boundary inside a hot loop.