Python Interop Gotchas in Mojo

Python Interop Gotchas in
Mojo

Common pitfalls when using Python modules and objects in Mojo. Tested
on Mojo version 0.26.1, Ubuntu, Python 3.12.

Each entry includes the problem, the error message you will see, and
the correct solution with a working code example.


1. Passing tuples to Python
functions

Problem: Mojo tuples cannot be passed directly to
Python functions as arguments.

Error:

invalid call to '__call__': value passed to 'kwargs' cannot be converted
from 'Tuple[String, String]' to 'PythonObject'

Wrong:

response = requests.get(url, auth=(username, password))

Correct:

# Build the tuple on the Python side using Python.evaluate()
var auth = Python.evaluate("lambda u, p: (u, p)")(username, password)
response = requests.get(url, auth=auth)

The same pattern applies to any Python function that expects a tuple
argument, for example timeouts:

# (connect_timeout, read_timeout) as a Python tuple
var timeout = Python.evaluate("lambda c, r: (c, r)")(3, 10)
response = requests.get(url, timeout=timeout)

2. Catching
exceptions with a named variable

Problem: Mojo does not yet support
except Exception as e syntax. You cannot inspect the
exception type or message at runtime.

Error:

invalid syntax

Wrong:

except Exception as e:
    print(String(e))

Correct:

except:
    print("An error occurred.")

If distinguishing between error types is important, consider wrapping
the call in a Python helper function and returning an error code or
message string instead.


3. Using bytes() built-in

Problem: bytes() is not available as a
built-in in Mojo.

Error:

use of unknown declaration 'bytes'

Wrong:

f.write(bytes(chunk))

Correct:

# Import Python's builtins module and use bytes() from there
builtins = Python.import_module("builtins")
f.write(builtins.bytes(chunk))

Or simply pass the Python object directly if it is already bytes:

f.write(chunk)   # chunk from iter_content() is already a Python bytes object

4. Writing binary files

Problem: Mojo’s built-in open() does
not support binary write mode ("wb").

Error:

invalid call to 'open': argument #2 cannot be converted from 'StringLiteral' to ...

Wrong:

var f = open(save_path, "wb")
f.write(chunk)

Correct:

# Use Python's built-in open() for binary file operations
builtins = Python.import_module("builtins")
var f    = builtins.open(save_path, "wb")
f.write(chunk)
f.close()

5. Python’s None value

Problem: None is not defined in Mojo.
Python.None (as an attribute) does not exist. Python’s
None must be accessed via Python.none() (a
method call).

Error:

use of unknown declaration 'None'           # for bare None
Python has no attribute 'None'              # for Python.None

Wrong:

result = some_dict.get("key", None)
if result != Python.None:
    ...

Correct:

result = some_dict.get("key", Python.none())
if result != Python.none():
    ...

6. Using Python.dict() vs Mojo
Dict

Problem: Mojo’s Dict and Python’s
dict are different types and are not directly
interchangeable. Python functions expect PythonObject
(Python dict), not Mojo’s Dict[K, V].

Wrong:

from collections import Dict
var params = Dict[String, String]()
params["key"] = "value"
requests.get(url, params=params)   # params is a Mojo Dict, not a PythonObject

Correct:

# Use Python.dict() to create a Python-native dict
params = Python.dict()
params["key"] = "value"
requests.get(url, params=params)

When you need to pass computed data from a Mojo Dict to
a Python function, convert it first:

py_dict = Python.dict()
for item in mojo_dict.items():
    py_dict[item.key] = item.value

7. Calling Python
lambdas with Python.evaluate()

Problem: Some Python constructs (lambdas,
comprehensions, multi-line expressions) cannot be expressed inline in
Mojo. Python.evaluate() lets you create a Python callable
and call it from Mojo.

Pattern:

# Create a Python lambda and call it immediately with Mojo arguments
var result = Python.evaluate("lambda a, b: (a, b)")(arg1, arg2)

# Or store the callable for repeated use
var get_score = Python.evaluate("lambda scores: lambda k: scores[k]")
summary = nlargest(n, keys, key=get_score(scores))

This is particularly useful for: – Building tuples (see #1) – Passing
key functions to Python’s sorted(), max(),
nlargest() etc. – Any Python expression that has no direct
Mojo equivalent



8. Converting
PythonObject to numeric Mojo types

Problem: Int(py_obj) and
Float64(py_obj) do not work directly with PythonObject.

Error:

no matching function in initialization

Wrong:

var x: Int     = Int(py_obj)
var y: Float64 = Float64(py_obj)

Correct:

# Convert via String as an intermediate step
var x: Int     = Int(String(py_obj))
var y: Float64 = Float64(String(py_obj))

9. Comparing PythonObject to
None

Problem: Bool(obj == Python.none()) is
unreliable — it returns True even when the object is not
None.

Wrong:

if Bool(obj == Python.none()):
    print("is none")

Correct:

# Check the Python type name instead
var builtins: PythonObject = Python.import_module("builtins")
var type_name = String(builtins.type(obj).__name__)
if type_name == "NoneType":
    print("is none")

10.
Iterating over a Mojo List and passing elements to Python

Problem: for item in list: yields a
Reference in Mojo, not the value itself. Using
item[] to dereference does not work for String
— it is interpreted as __getitem__ (index access)
instead.

Error:

no matching method in call to '__getitem__'

Wrong:

for key in keys:
    data.get(key[], Python.none())   # key[] fails for String

Correct:

# Use index-based iteration to get a clean String value
for i in range(len(keys)):
    var k: String = keys[i]
    data.get(k, Python.none())

11. Initializing List[T] with
values

Problem: List[T] constructor does not
accept initial values as positional arguments.

Error:

no matching function in initialization

Wrong:

var keys = List[String]("name", "version", "missing_key")

Correct:

var keys = List[String]()
keys.append("name")
keys.append("version")
keys.append("missing_key")

12.
String.format() alignment specifiers are not supported

Problem: Mojo’s String.format() does
not support alignment specifiers like {:<16} or
{:>10}.

Error:

constraint failed: Index :<16 not in kwargs

Wrong:

print("{:<16} {:>10}".format("Product", "Total"))

Correct:

# Print columns side-by-side with plain print()
print("Product          Total")

# Or use Python's built-in format() via builtins
builtins = Python.import_module("builtins")
print(builtins.format("Product", "<16"), builtins.format("Total", ">10"))

13.
Int(String(…)) does not accept decimal number strings

Problem: Libraries like Pandas may return integer
values as decimal strings (e.g. "5.0").
Int(String(...)) cannot parse these — it expects a plain
integer string.

Error:

String is not convertible to integer with base 10: '5.0'

Wrong:

var count: Int = Int(String(py_obj))   # fails if py_obj is "5.0"

Correct:

# Convert to Float64 first, then to Int
var count: Int = Int(Float64(String(py_obj)))

14.
NumPy multi-dimensional slicing is not supported in Mojo

Problem: NumPy’s multi-dimensional slice syntax
(arr[:, :, 0], arr[0:10, 0:10, 2]) causes a
__getitem__ error in Mojo. Mojo cannot pass Python slice
objects with multiple dimensions directly.

Error:

no matching method in call to '__getitem__'

Wrong:

var channel = array[:, :, 0]          # fails
array[20:120, 20:120, 0] = 220        # fails

Correct:

# Move slice operations into a Python helper module (helpers.py)
# and call them via Python.import_module()

# In helpers.py:
# def get_channel(arr, ch):
#     return arr[:, :, ch].astype(np.float32)

var helpers: PythonObject = Python.import_module("helpers")
var channel = helpers.get_channel(array, 0)

This pattern applies to any NumPy operation that requires
multi-dimensional indexing, boolean masking, or fancy indexing.


15.
Python.evaluate() does not accept multi-line strings

Problem: Python.evaluate() only accepts
single-line Python expressions. Multi-line function definitions inside
Python.evaluate() cause a syntax error.

Error:

invalid syntax (<string>, line 2)

Wrong:

Python.evaluate("""
def my_func(arr):
    import numpy as np
    arr[:, :, 2] = 0
""")(img_array)

Correct:

# Place the function in a separate .py file and import it
# my_helpers.py:
#   def my_func(arr):
#       arr[:, :, 2] = 0

var helpers: PythonObject = Python.import_module("my_helpers")
helpers.my_func(img_array)

16. ‘import sys’
refers to Mojo’s sys, not Python’s

Problem: Mojo has its own sys module.
Writing import sys imports Mojo’s module, not Python’s.
Accessing sys.path then fails because Mojo’s
sys does not have a path attribute.

Error:

use of unknown declaration 'path'

Wrong:

import sys
sys.path.insert(0, ".")   # refers to Mojo's sys, not Python's

Correct:

# Import Python's sys explicitly via Python.import_module()
var sys: PythonObject = Python.import_module("sys")
sys.path.insert(0, ".")

This applies to any Python standard library module that shares a name
with a Mojo built-in module (e.g. sys,
math).


17. Docstrings must end with
a period

Problem: Mojo enforces a style rule that docstring
summary lines must end with a period. This triggers a lint warning if
omitted.

Warning:

doc string summary should end with a period '.', but this ends with '<last_word>'

Wrong:

fn load_image(path: String) raises -> PythonObject:
    """Load an image and return it as a NumPy array"""

Correct:

fn load_image(path: String) raises -> PythonObject:
    """Load an image and return it as a NumPy array."""

Applies to both single-line and multi-line docstrings — the closing
line before the """ must end with ..



18. Functions may
not have multiple out arguments

Problem: Mojo 0.26.1 allows at most one
out argument per function. Using multiple out
arguments to return several values causes a compile error.

Error:

function may not have multiple 'out' arguments

Wrong:

fn init_snake(
    out body_x: List[Int], out body_y: List[Int],
    out dir: Int, out dx: Int, out dy: Int,
    out score: Int, out alive: Bool
):
    ...

Correct:

# Store all mutable state in a single Python dict and pass it by reference.
# Python dicts are reference types — mutations inside the function
# are visible to the caller without any return value.

fn init_snake(s: PythonObject) raises:
    s["body_x"] = ...
    s["dir"]    = DIR_RIGHT
    s["score"]  = 0
    ...

# Create the dict once and pass it everywhere:
var s: PythonObject = Python.dict()
init_snake(s)
move_snake(s)
render(canvas, s)

19.
alias is deprecated — use comptime for global
constants

Problem: alias is deprecated in Mojo
0.26.1 and triggers a warning. comptime var causes an
invalid comptime declaration error. Global var
is not supported at module level.

Warning / Error:

'alias' is deprecated, use 'comptime' instead
invalid comptime declaration: expected an identifier or '_'   # for comptime var

Wrong:

alias  GRID_W = 30          # deprecated warning
comptime var GRID_W = 30    # compile error
var GRID_W = 30             # not allowed at module level

Correct:

# Global integer constants: use comptime without var
comptime GRID_W = 30
comptime GRID_H = 25

# Runtime variables that are not truly constant:
# define them inside the function where they are used
fn run_game() raises:
    var cell   = 20
    var grid_w = 30
    ...

20.
Python list creation with Python.evaluate("list")([...])
fails

Problem: Calling
Python.evaluate("list") returns the Python
list type, but passing a Mojo list literal to it as an
argument causes a type inference error.

Error:

invalid call to '__call__': could not infer type of parameter pack 'args'
given value with unresolved type

Wrong:

s["body_x"] = Python.evaluate("list")([cx, cx - 1, cx - 2])

Correct:

# Build the Python list by appending elements one by one
builtins: PythonObject = Python.import_module("builtins")
var bx: PythonObject = builtins.list()
bx.append(cx)
bx.append(cx - 1)
bx.append(cx - 2)
s["body_x"] = bx


21. str() is
not defined in Mojo — use String()

Problem: Python’s str() built-in does
not exist in Mojo. Using it to convert a value to string causes a
compile error.

Error:

use of unknown declaration 'str'

Wrong:

var s = str(some_value)
if guessed[i] == str(ch):

Correct:

var s = String(some_value)
if guessed[i] == String(ch):

22. Iterating over
a String — use codepoint_slices()

Problem: Iterating directly over a
String with for ch in my_string: is deprecated
in Mojo 0.26.1.

Warning:

Use `str.codepoints()` or `str.codepoint_slices()` instead.

Wrong:

for ch in word:
    if ch == "a":
        ...

Correct:

for ch in word.codepoint_slices():
    if String(ch) == "a":
        ...

Use codepoint_slices() when you need to compare or
convert the character to a String. Use
codepoints() when you need the integer codepoint value.


23. Functions cannot return
tuple types

Problem: Mojo does not support tuple return types
such as -> (Int, Int). Attempting to use them causes an
initialization error.

Error:

no matching function in initialization

Wrong:

fn card_xy(idx: Int) -> (Int, Int):
    return (x, y)

var xy = card_xy(i)
var x  = xy[0]

Correct:

# Split into separate functions, one per return value
fn card_x(idx: Int) -> Int:
    return PAD + (idx % COLS) * (CARD_W + PAD)

fn card_y(idx: Int) -> Int:
    return PAD + (idx // ROWS) * (CARD_H + PAD)

# Or pack multiple values into a Python dict
fn card_xy(idx: Int) raises -> PythonObject:
    var d = Python.dict()
    d["x"] = PAD + (idx % COLS) * (CARD_W + PAD)
    d["y"] = PAD + (idx // ROWS) * (CARD_H + PAD)
    return d

24. Unused variable warning
— assign to _

Problem: Mojo warns when a variable is declared but
never read.

Warning:

assignment to 'x' was never used; assign to '_' instead?

Wrong:

var locked = Bool(s["locked"])   # declared but never used below

Correct:

# Option 1: remove the unused variable entirely
# Option 2: assign to _ to explicitly discard the value
_ = Bool(s["locked"])

# Option 3: use _ directly when calling a function for side effects
_ = canvas.create_rectangle(x1, y1, x2, y2, fill=color)

25. Mojo
List len() should not be passed to Python
functions

Problem: Passing len(mojo_list)
directly to a Python function such as random.randint() can
produce unexpected results because Mojo’s len() returns a
Mojo Int, not a Python int, and the conversion may silently
produce a wrong value.

Wrong:

var words = List[String]()
# ... append words ...
var idx = Int(Float64(String(random.randint(0, len(words) - 1))))
# may always return the same index

Correct:

# Use a Python list and Python's builtins.len() to stay on the Python side
builtins: PythonObject = Python.import_module("builtins")
var py_words: PythonObject = builtins.list()
# ... append words ...
var n   = Int(Float64(String(builtins.len(py_words))))
var idx = Int(Float64(String(random.randint(0, n - 1))))

26. Flask
route handlers cannot be defined inline in Mojo

Problem: Flask’s @app.route() decorator
syntax requires multi-line Python function definitions. These cannot be
passed via Python.evaluate() (gotcha #15), and Mojo
closures cannot be registered as Flask route handlers.

Wrong:

# Python.evaluate() does not accept multi-line strings
var setup = Python.evaluate("""
def setup_routes(app):
    @app.route('/')
    def index():
        return 'Hello!'
setup_routes
""")(app)

Correct:

# Define all route handlers in a separate Python helper file
# flask_helpers.py:
#   from flask import jsonify
#   def setup_routes(app):
#       @app.route('/')
#       def index():
#           return 'Hello from Mojo + Flask!'

flask_helpers: PythonObject = Python.import_module("flask_helpers")
flask_helpers.setup_routes(app)

This pattern cleanly separates concerns: Mojo handles startup and
configuration, while Python handles Flask route definitions.


27. Flask app
name must be passed as a Python string

Problem: flask.Flask("__main__") passes
a Mojo StringLiteral to Flask’s constructor. Flask may not
recognize it correctly as the application name.

Wrong:

var app = flask.Flask("__main__")   # Mojo StringLiteral, not Python str

Correct:

builtins: PythonObject = Python.import_module("builtins")
var app = flask.Flask(builtins.str("__main__"))

28.
SQLite row access requires string keys via Python dict-style
indexing

Problem: When
conn.row_factory = sqlite3.Row is set, rows can be accessed
by column name. However, passing a Mojo String or
StringLiteral directly as the key may fail. The key must be
passed as a Python string.

Wrong:

var title = String(row["title"])   # may fail — Mojo string key

Correct:

# Wrap the key with String() to ensure proper PythonObject conversion,
# or use the column index instead
var title = String(row[String("title")])   # explicit String conversion
var title = String(row[0])                 # index-based access

29.
SQLite query parameters must be passed as Python tuples

Problem: SQLite’s execute() expects
query parameters as a Python tuple. Mojo tuples cannot be passed
directly (gotcha #1).

Wrong:

conn.execute("SELECT * FROM books WHERE id = ?", (book_id,))
# Mojo tuple — causes type error

Correct:

# Use Python.evaluate() to build the parameter tuple on the Python side
conn.execute(
    "SELECT * FROM books WHERE id = ?",
    Python.evaluate("lambda x: (x,)")(book_id)
)

Summary Table

Pitfall Wrong Correct
Tuple argument func(arg=(a, b)) var t = Python.evaluate("lambda a,b: (a,b)")(a,b)
Exception handling except Exception as e: except:
bytes() built-in bytes(obj) Python.import_module("builtins").bytes(obj)
Binary file write open(path, "wb") Python.import_module("builtins").open(path, "wb")
Python None value None or Python.None Python.none()
Dict for Python API Dict[String, String]() Python.dict()
Lambda / key functions (not possible inline) Python.evaluate("lambda ...")
Numeric conversion Int(py_obj) Int(String(py_obj))
None comparison Bool(obj == Python.none()) String(builtins.type(obj).__name__) == "NoneType"
List iteration for item in list: item[] for i in range(len(list)): list[i]
List initialization List[String]("a", "b") list.append("a"); list.append("b")
String alignment "{:<16}".format(val) Python.import_module("builtins").format(val, "<16")
Decimal int string Int(String("5.0")) Int(Float64(String(py_obj)))
NumPy slicing arr[:, :, 0] helper .py modülüne taşı
Multi-line evaluate Python.evaluate("""...""") helper .py modülüne taşı
Mojo sys vs Python sys import sys Python.import_module("sys")
Docstring period """Load image""" """Load image."""
Multiple out args fn f(out a: Int, out b: Int) tek Python.dict() içinde tut
Global constants alias X = 0 veya comptime var X = 0 comptime X = 0
Python list creation Python.evaluate("list")([a, b]) builtins.list() + .append()
str() not defined str(obj) String(obj)
String iteration for ch in s: for ch in s.codepoint_slices():
Tuple return type fn f() -> (Int, Int) iki ayrı fn veya Python.dict()
Unused variable var x = val (kullanılmıyor) _ = val veya tanımı kaldır
Mojo len() to Python random.randint(0, len(mojo_list)) builtins.len(py_list) kullan
Flask route handlers Python.evaluate("""@app.route...""") ayrı flask_helpers.py dosyası
Flask app name flask.Flask("__main__") flask.Flask(builtins.str("__main__"))
SQLite row key row["title"] row[String("title")] veya row[0]
SQLite parameters execute(sql, (val,)) execute(sql, Python.evaluate("lambda x: (x,)")(val))

Yorum bırakın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Scroll to Top