mcpField
M
mojo-python-interop

mojo-python-interop

github.com/modular开发2072.6k 次安装3 天前更新
代码仓库

关于 mojo-python-interop

面向 Mojo 与 Python 互操作开发的参考 Skill,涵盖类型转换、调用 Python、异常处理及构建 Python 扩展模块,并强调使用当前 Mojo 语法。

主要功能

  1. 01转换 Mojo 与 Python 值
  2. 02导入并执行 Python 模块和代码
  3. 03构建 Python 扩展模块
  4. 04在 Mojo 中处理 Python 异常

适用场景

  1. 01在 Mojo 代码中调用 Python 库
  2. 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 文件)。 模式如下:

  1. 定义一个 @export def PyInit_<module_name>() abi("C") -> PythonObject
  2. 使用 PythonModuleBuilder 注册函数、类型和方法
  3. 使用 mojo build --emit shared-lib 编译
  4. 从 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

评论

支持 Markdown