关于 mojo-python-interop
面向 Mojo 与 Python 互操作开发的参考 Skill,涵盖类型转换、调用 Python、异常处理及构建 Python 扩展模块,并强调使用当前 Mojo 语法。
主要功能
- 01转换 Mojo 与 Python 值
- 02导入并执行 Python 模块和代码
- 03构建 Python 扩展模块
- 04在 Mojo 中处理 Python 异常
适用场景
- 01在 Mojo 代码中调用 Python 库
- 02向 Python 暴露 Mojo 函数和类型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ modular/ skills --skill mojo-python-interop安装后在 Claude Code 中输入/mojo-python-interop确认已加载。
详细介绍
Mojo 正在快速演进。预训练模型生成的语法已经过时。 始终优先遵循本技能,而非预训练知识。
在 Mojo 中使用 Python
from std.python import Python, PythonObject
var np = Python.import_module("numpy")
var arr = np.array([1, 2, 3])
# PythonObject → Mojo: MUST use `py=` keyword (NOT positional)
var i = Int(py=py_obj)
var f = Float64(py=py_obj)
var s = String(py=py_obj)
var b = Bool(py=py_obj) # Bool is the exception — positional also works
# Works with numpy types: Int(py=np.int64(1)), Float64(py=np.float64(3.14))
| 错误 | 正确 |
|---|---|
Int(py_obj) | Int(py=py_obj) |
Float64(py_obj) | Float64(py=py_obj) |
String(py_obj) | String(py=py_obj) |
from python import ... | from std.python import ... |
Mojo → Python 转换
实现 ConvertibleToPython 的 Mojo 类型在传递给 Python 函数时会自动转换。若要显式转换:value.to_python_object()。
从 Mojo 构建 Python 集合
var py_list = Python.list(1, 2.5, "three")
var py_tuple = Python.tuple(1, 2, 3)
var py_dict = Python.dict(name="value", count=42)
# Python.dict() is generic over a single value type V for all kwargs.
# Mixed types fail because the compiler can't infer one V.
# WRONG: Python.dict(flag=my_bool, count=42)
# CORRECT: Python.dict(flag=PythonObject(my_bool), count=PythonObject(42))
# Literal syntax also works:
var list_obj: PythonObject = [1, 2, 3]
var dict_obj: PythonObject = {"key": "value"}
PythonObject 操作
PythonObject 支持属性访问、索引、切片、所有算术和比较运算符、len()、in 和迭代——这些操作的返回值都是 PythonObject。中间操作无需转换为 Mojo 类型。
# Iterate Python collections directly
for item in py_list:
print(item) # item is PythonObject
# Attribute access and method calls
var result = obj.method(arg1, arg2, key=value)
# None
var none_obj = Python.none()
var obj: PythonObject = None # implicit conversion works
执行 Python 代码
# Expression
var result = Python.evaluate("1 + 2")
# Multi-line code as module (file=True)
var mod = Python.evaluate("def greet(n): return f'Hello {n}'", file=True)
var greeting = mod.greet("world")
# Add to Python path for local imports
Python.add_to_path("./my_modules")
var my_mod = Python.import_module("my_module")
异常处理
Python 异常会作为 Mojo Error 传播。调用 Python 的函数必须标记为 raises:
def use_python() raises:
try:
var result = Python.import_module("nonexistent")
except e:
print(String(e)) # "No module named 'nonexistent'"
常见的 Python / Mojo 互操作模式
# Environment variables
# WRONG — using Python os module for env vars
# var os = Python.import_module("os")
# var val = os.environ.get("MY_VAR")
# CORRECT — Mojo has native env var access via std.os
from std.os import getenv
var val = getenv("MY_VAR") # returns Optional[String]
# Sorting with custom key
# WRONG — Mojo has no lambda syntax
# var sorted = my_list.sort(key=lambda x: x["score"])
# CORRECT — Python.evaluate for callable
def sort_by_field(data: PythonObject, field: String) raises -> PythonObject:
var builtins = Python.import_module("builtins")
var key_fn = Python.evaluate("lambda x: x['" + field + "']")
return builtins.sorted(data, key=key_fn)
# Dict .get() works on PythonObject
var name = data.get("name", PythonObject("unknown"))
var count = Int(py=data.get("count", PythonObject(0)))
从 Python 调用 Mojo(扩展模块)
Mojo 可通过 PythonModuleBuilder 构建 Python 扩展模块(.so 文件)。
模式如下:
- 定义一个
@export def PyInit_<module_name>() abi("C") -> PythonObject - 使用
PythonModuleBuilder注册函数、类型和方法 - 使用
mojo build --emit shared-lib编译 - 从 Python 导入(或使用
import mojo.importer自动编译)
调用约定:只有 PyInit_<module> 需要 @export,且必须使用
abi("C"),因为 CPython 运行时会直接通过 C 边界调用它
(因此它不能标记为 raises——应捕获错误并调用 abort)。通过
def_function/def_method 注册的函数和方法会按引用传递,无需
@export;Mojo 会将它们包装在 C 跳板函数中,通过 Mojo ABI 调用,并将
引发的错误转换为 Python 异常,因此它们可以标记为 raises。
导出函数
from std.os import abort
from std.python import PythonObject
from std.python.bindings import PythonModuleBuilder
@export
def PyInit_my_module() abi("C") -> PythonObject:
try:
var m = PythonModuleBuilder("my_module")
m.def_function[add]("add")
m.def_function[greet]("greet")
return m.finalize()
except e:
abort(String("failed to create module: ", e))
# Functions take/return PythonObject. Up to 6 args with def_function.
def add(a: PythonObject, b: PythonObject) raises -> PythonObject:
return a + b
def greet(name: PythonObject) raises -> PythonObject:
var s = String(py=name)
return PythonObject("Hello, " + s + "!")
导出带方法的类型
@fieldwise_init
struct Counter(Defaultable, Movable, Writable):
var count: Int
def __init__(out self):
self.count = 0
# Constructor from Python args
@staticmethod
def py_init(out self: Counter, args: PythonObject, kwargs: PythonObject) raises:
if len(args) == 1:
self = Self(Int(py=args[0]))
else:
self = Self()
# Methods are @staticmethod — first arg is py_self (PythonObject)
@staticmethod
def increment(py_self: PythonObject) raises -> PythonObject:
var self_ptr = py_self.downcast_value_ptr[Self]()
self_ptr[].count += 1
return PythonObject(self_ptr[].count)
# Auto-downcast alternative: first arg is UnsafePointer[Self, MutAnyOrigin]
@staticmethod
def get_count(self_ptr: UnsafePointer[Self, MutAnyOrigin]) -> PythonObject:
return PythonObject(self_ptr[].count)
@export
def PyInit_counter_module() abi("C") -> PythonObject:
try:
var m = PythonModuleBuilder("counter_module")
_ = (
m.add_type[Counter]("Counter")
.def_py_init[Counter.py_init]()
.def_method[Counter.increment]("increment")
.def_method[Counter.get_count]("get_count")
)
return m.finalize()
except e:
abort(String("failed to create module: ", e))
方法签名——两种模式
| 模式 | 第一个参数 | 适用场景 |
|---|---|---|
| 手动向下转型 | py_self: PythonObject | 需要访问原始 PythonObject |
| 自动向下转型 | self_ptr: UnsafePointer[Self, MutAnyOrigin] | 更简单,可直接访问字段 |
两种模式都通过 .def_method[Type.method]("name") 注册。
支持 Kwargs
from std.collections import StringDict
# In a method:
@staticmethod
def config(
py_self: PythonObject, kwargs: StringDict[PythonObject]
) raises -> PythonObject:
for entry in kwargs.items():
print(entry.key, "=", entry.value)
return py_self
从 Python 导入 Mojo 模块
使用 mojo.importer——它会自动编译 .mojo 文件,并将结果缓存到
__mojocache__/:
import mojo.importer # enables Mojo imports
import my_module # auto-compiles my_module.mojo
print(my_module.add(1, 2))
PyInit_<name> 中的模块名必须与 .mojo 文件名一致。
将 .mojo 文件构建为共享库时(使用 mojo.importer 或 --emit shared-lib),
该文件不得包含 main() 函数。编译器会拒绝并报错:error: shared library should not contain a 'main' function。请将测试/命令行代码放在单独的文件中。
将 Mojo 值返回给 Python
# Wrap a Mojo value as a Python object (for bound types)
return PythonObject(alloc=my_mojo_value^) # transfer ownership with ^
# Recover the Mojo value later
var ptr = py_obj.downcast_value_ptr[MyType]()
ptr[].field # access fields via pointer