mojo-python-interop について
Mojo と Python の相互運用を扱う開発者向けのリファレンススキルです。値の変換、Python の呼び出し、例外処理、Python 拡張モジュールの構築を、現行の Mojo 構文に沿って説明します。
主な機能
- 01Mojo と Python の値の変換
- 02Python モジュールとコードのインポート・評価
- 03Python 拡張モジュールの構築
- 04Mojo での Python 例外処理
活用シーン
- 01Mojo コードから Python ライブラリを呼び出す
- 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 ファイル)をビルドできます。手順は次のとおりです。
@export def PyInit_<module_name>() abi("C") -> PythonObjectを定義しますPythonModuleBuilderを使用して関数、型、メソッドを登録しますmojo build --emit shared-libでコンパイルします- 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