Mojo Python Interop

Mojo runs Python code directly and imports Python libraries without any conversion layer. Your existing Python ecosystem — NumPy, Pandas, Matplotlib, PyTorch, and thousands more packages — works inside Mojo files today. You write performance-critical parts in Mojo and call Python for everything else.

The Bridge Analogy

  Python world                       Mojo world
  ──────────────────                 ──────────────────────────
  NumPy, Pandas, Torch               Fast compiled Mojo code
  Rich ecosystem                     SIMD, parallelism, ownership
  Slow execution                     Fast execution

  Python ←──── bridge ────→ Mojo

  You walk across the bridge freely in both directions.
  The bridge costs a small overhead per crossing,
  so keep hot loops on the Mojo side.

Importing Python Modules

from python import Python

fn main() raises:
    # Import numpy exactly like you would in a Python script
    var np = Python.import_module("numpy")

    # Create a NumPy array
    var arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
    print(arr)            # [1. 2. 3. 4. 5.]

    # Call NumPy functions
    print(np.sum(arr))    # 15.0
    print(np.mean(arr))   # 3.0
    print(np.std(arr))    # 1.4142...

Using Python Objects

Python objects returned from Python calls are typed as PythonObject in Mojo. You interact with them using the same attribute and method access syntax as Python.

from python import Python

fn main() raises:
    var py = Python.import_module("builtins")

    # Use Python's built-in list
    var py_list = py.list([10, 20, 30, 40])
    py_list.append(50)
    print(py_list)          # [10, 20, 30, 40, 50]
    print(py_list[0])       # 10

    # Use Python's dict
    var py_dict = py.dict()
    py_dict["name"] = "Mojo"
    py_dict["speed"] = "fast"
    print(py_dict)           # {'name': 'Mojo', 'speed': 'fast'}

Calling Python Functions

from python import Python

fn main() raises:
    var math = Python.import_module("math")

    # Call Python math functions
    print(math.sqrt(16.0))     # 4.0
    print(math.pi)             # 3.141592653589793
    print(math.factorial(10))  # 3628800

    var os = Python.import_module("os")
    var cwd = os.getcwd()
    print("Working directory:", cwd)

Plotting with Matplotlib

from python import Python

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

    var x = np.linspace(0, 2 * np.pi, 100)
    var y = np.sin(x)

    plt.plot(x, y)
    plt.title("Sine Wave")
    plt.xlabel("x")
    plt.ylabel("sin(x)")
    plt.savefig("sine_wave.png")   # saves a PNG file
    print("Plot saved")

Converting Between Mojo and Python Types

from python import Python, PythonObject

fn mojo_to_python() raises:
    var py = Python.import_module("builtins")

    # Mojo Int → Python int (automatic)
    var mojo_int: Int = 42
    var py_int = PythonObject(mojo_int)
    print(py_int + 1)   # 43 (Python does the math)

    # Mojo String → Python str
    var mojo_str: String = "Hello"
    var py_str = PythonObject(mojo_str)
    print(py_str.upper())   # HELLO (Python str method)
Type conversion table:
  Mojo Type   │  Python Type
  ────────────┼─────────────
  Int         │  int
  Float64     │  float
  String      │  str
  Bool        │  bool
  PythonObject│  any Python object

Running Python Evaluation Inline

from python import Python

fn main() raises:
    # Run arbitrary Python expressions
    Python.evaluate("print('Python running inside Mojo!')")

    # Evaluate an expression and get the result back
    var result = Python.evaluate("sum(range(1, 11))")
    print(result)   # 55

Performance Pattern: Python Data, Mojo Compute

The most common pattern uses Python to load and preprocess data (where Python's ecosystem excels) and Mojo to compute on it (where Mojo's speed excels).

from python import Python

fn fast_scale(data: List[Float64], factor: Float64) -> List[Float64]:
    var result = List[Float64]()
    for i in range(len(data)):
        result.append(data[i] * factor)
    return result

fn main() raises:
    # Use pandas to load data (Python does this well)
    var pd = Python.import_module("pandas")
    var df = pd.read_csv("data.csv")
    var column = df["value"].tolist()

    # Convert Python list to Mojo list
    var mojo_data = List[Float64]()
    for i in range(len(column)):
        mojo_data.append(Float64(column[i]))

    # Use Mojo to compute fast
    var scaled = fast_scale(mojo_data, 1.5)
    print(len(scaled), "elements scaled")

Limitations of Python Interop

Crossing the Mojo/Python boundary:
  ✓ Works correctly for all Python objects
  ✗ Has overhead (GIL, Python object boxing)
  ✗ Python GIL prevents true parallelism in Python code
  ✗ Python objects live on the heap, not Mojo's stack

Best practices:
  ✓ Cross the boundary as few times as possible
  ✓ Move large computation loops to Mojo
  ✓ Use Python for I/O, plotting, and library access
  ✓ Pass large arrays as NumPy arrays for efficient buffer sharing

Key Takeaways

Mojo imports Python modules directly using Python.import_module(). All Python libraries — NumPy, Pandas, PyTorch, Matplotlib — work inside Mojo files without modification. Python objects are typed as PythonObject in Mojo and support the same attribute and method access syntax as Python. Convert Mojo values to PythonObject explicitly when needed. The golden pattern is: use Python for its rich ecosystem (I/O, visualization, ML libraries) and Mojo for the performance-critical numerical computation.

Leave a Comment

Your email address will not be published. Required fields are marked *