mcpField
M
mojo-python-interop

mojo-python-interop

github.com/modular開発2072.6k 回インストール3 日前に更新
リポジトリ

mojo-python-interop について

Mojo と Python の相互運用を扱う開発者向けのリファレンススキルです。値の変換、Python の呼び出し、例外処理、Python 拡張モジュールの構築を、現行の Mojo 構文に沿って説明します。

主な機能

  1. 01Mojo と Python の値の変換
  2. 02Python モジュールとコードのインポート・評価
  3. 03Python 拡張モジュールの構築
  4. 04Mojo での Python 例外処理

活用シーン

  1. 01Mojo コードから Python ライブラリを呼び出す
  2. 02Mojo の関数や型を Python に公開する

インストール

コマンドをコピーしてターミナルで実行します。

$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 が必要で、CPython ランタイムが C 境界を越えて直接呼び出すため、abi("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))
メソッドのシグネチャ — 2 つのパターン
パターン最初のパラメーター使用する場面
手動ダウンキャスト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.importer または --emit shared-lib)、.mojo ファイルに main() 関数を含めてはいけません。コンパイラーは error: shared library should not contain a 'main' function というエラーで拒否します。テスト/CLI コードは別のファイルに置いてください。

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

コメント