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.
