(Python 相互運用)= # Python 相互運用 ## この章で学ぶこと - Mojo から Python を呼ぶ方法 - Python から Mojo を呼ぶ方法 - 型の境界とオブジェクトの受け渡し方 この章のテーマは、**既存の Python 資産をどう活かすか** です。 Mojo は Python に似ていますが、同じ言語ではありません。 そのため、ここでは **Mojo と Python をどうつなぐか** を見ます。 まずは次の3つを押さえると読みやすいです。 - Mojo から Python を使える - Python から Mojo を呼ぶこともできる - その間には型や変換の境界がある ## Python interop この章でいちばん大事なのは、**Mojo と Python は双方向につながる** ということです。 ### 何ができるのか - Mojo から Python のモジュールを使う - Python から Mojo の機能を呼ぶ - NumPy など既存の Python 資産を活かす つまり、**Python の資産を捨てずに Mojo を使える** ということです。 ### どう考えるとよいか ここでのポイントは、 **Mojo は Python の延長ではなく、Python と橋をかけられる言語** だということです。 見た目が似ていても、型や実行の考え方は違います。 そのため、つなぐときには変換や境界を意識する必要があります。 詳細: [Python interop](https://docs.modular.com/mojo/manual/python/) 出典: [Mojo Manual — python](https://docs.modular.com/mojo/manual/python/) > **補足:** この章では、「同じもの」として見るより「橋を渡す」と捉えるほうが整理しやすいでしょう。 ## Calling Python from Mojo まずは、**Mojo から Python を呼ぶ** 場面です。 ### 基本 Mojo では、Python モジュールを読み込んで、その関数やオブジェクトを使えます。 たとえば次のような流れです。 - `Python.import_module` で Python モジュールを取得する - 関数や属性にアクセスする - 必要に応じて結果を受け取る ### コード例(標準ライブラリ `math`) 次の例は、**標準ライブラリの `math.sqrt`** を Mojo から呼び、戻り値を `Float64` に変換して表示します。外部の `.py` ファイルは不要で、Python 本体が利用できる Mojo 実行環境が前提です。 ```{literalinclude} ../../../src/part2/ch11/python_from_mojo_math.mojo :language: mojo ```

リスト-1: python_from_mojo_math.mojo

- `Python.import_module("math")` で Python 側のモジュールオブジェクトを取得する - 引数・戻り値は、多くの場合 **`PythonObject`** として受け渡しする - Mojo の数値型として使うには、`Float64(py=...)` のように **明示的な変換**が必要になる ### `PythonObject` とは Python 側の値を扱うときには、`PythonObject` が出てきます。 これは、**Python のオブジェクトを Mojo 側で受け取るための入れ物** と捉えるとよいでしょう。 ここで大事なのは、 **Mojo は静的型、Python は動的型** なので、その境目をはっきり意識することです。 ### コード例(`PythonObject` の生成と Mojo 型への変換) Mojo 側で `PythonObject(...)` を作り、`String(py=...)` などのコンストラクタで **Mojo のプリミティブ型へ変換**する流れです(公式ドキュメントのパターンに沿った最小例)。 ```{literalinclude} ../../../src/part2/ch11/python_object_wrap_and_convert.mojo :language: mojo ```

リスト-2: python_object_wrap_and_convert.mojo

### 速くならない部分 Python を呼んだ部分は **CPython の世界に戻る**ため、そこは Mojo の性能の恩恵を受けません。 - GIL の影響がそのまま残る - `PythonObject` の変換・受け渡し自体にコストが生じる - 呼び出しを増やすほど Mojo 側の利点が薄れる **「Mojo を使えば Python 呼び出しも速くなる」は誤り**です。 速さを得たいなら、ホットパスを Mojo 側に留め、Python を呼ぶ回数を減らすことが重要です。 詳細: [Calling Python from Mojo](https://docs.modular.com/mojo/manual/python/python-from-mojo/) 出典: [Mojo Manual — python-from-mojo](https://docs.modular.com/mojo/manual/python/python-from-mojo/) > **補足:** Python を呼べるのは強みですが、ホットパスを全部 Python に寄せると Mojo の利点が薄れます。 ## Calling Mojo from Python 次は、**Python から Mojo を呼ぶ** 場面です。 ### 何をするのか Mojo のコードを拡張モジュールとしてビルドし、 Python 側から読み込める形にします。 すると、Python から Mojo の処理を呼び出せます。 ### コード例(`PyInit_` と `PythonModuleBuilder`) Python が `import mojo_module` するとき、**`PyInit_mojo_module`** というエントリポイントを探します。そこで `PythonModuleBuilder` を使い、公開する関数(この例では `factorial`)を登録します。 **Mojo 側(拡張モジュールの定義):** ```{literalinclude} ../../../src/part2/ch11/mojo_module.mojo :language: mojo ```

リスト-3: mojo_module.mojo

- `factorial` は引数を `PythonObject` で受け取り、`Int(py=...)` で Mojo の `Int` に変換する。戻り値は再び `PythonObject` に包んで Python 側へ返す **Python 側(呼び出し):** ```{literalinclude} ../../../src/part2/ch11/call_mojo_factorial.py :language: python ```

リスト-4: call_mojo_factorial.py

実際に動かすには、**Mojo のビルド手順**(モジュール名と出力パス、`PYTHONPATH`、必要なら `mojo.importer` のセットアップ)を、[Calling Mojo from Python](https://docs.modular.com/mojo/manual/python/mojo-from-python/) の最新手順に沿ってください。リリースごとにコマンドやレイアウトが変わり得ます。 ### どう考えるとよいか この方向では、 **重い処理を Mojo に任せて使い慣れた Python から呼ぶ** という使い方がしやすいです。 つまり、 - Python は使いやすい入口 - Mojo は速く動かしたい中身 という分担を考えやすくなります。 ### 注意点 この仕組みは便利ですが、ビルド手順や対応状況は変わりやすいことがあります。 そのため、実際に使うときはマニュアルとリリースノートを確認するのが安全です。 また、Python 側に見せる API は、なるべくわかりやすく薄く保つほうが扱いやすいです。 詳細: [Calling Mojo from Python](https://docs.modular.com/mojo/manual/python/mojo-from-python/) 出典: [Mojo Manual — mojo-from-python](https://docs.modular.com/mojo/manual/python/mojo-from-python/) > **補足:** 配布方法やビルド周りは更新が早いので、実際に作るときは公式確認が前提です。 ## Python types 最後に、**型の境界** を見ます。 ### 基本 Python のオブジェクトを Mojo で扱うときは、`PythonObject` が中心になります。 これは、**Python の値をそのまま静的型の値として扱うのではなく、いったん橋渡し用の形で持つ** ための仕組みです。 ### 何が起こるのか この境界では、次のようなことを意識する必要があります。 - Mojo 型へ変換する - Python のオブジェクトとして扱う - 属性へアクセスする - 必要ならカスタム型を Python 側へ渡す ここで大事なのは、**型変換や受け渡しにはコストやルールがある** ことです。 見た目が簡単でも、内部では変換や橋渡しが入ることがあります。 リスト-1(`Float64(py=...)`)とリスト-2(`String(py=...)` など)は、この境界を越えるときの典型パターンです。 ### どう読むとよいか この節は、細かな API 名を覚えるよりも、 **静的型の Mojo と動的な Python の間に境界がある** と理解するのが先です。 そこがわかると、なぜ `PythonObject` のような仕組みが必要なのか見えやすくなります。 詳細: [Python types](https://docs.modular.com/mojo/manual/python/types/) 出典: [Mojo Manual — python/types](https://docs.modular.com/mojo/manual/python/types/) > **補足:** ここは、Mojo と Python の違いが最も見えやすい場所です。 ## この章を一文で言うと **Mojo と Python は双方向につながるが、その間には型と変換の境界があるので、それを意識して使うことが大切です。** ## まとめ - Mojo から Python のモジュールや関数を呼べる - Python から Mojo の処理を呼ぶこともできる - `PythonObject` は Python 側の値を扱う中心になる - 静的型の Mojo と動的な Python の間には境界がある - 変換やコピーのコストを意識すると設計しやすい - 速さが必要な部分では、境界のまたぎ方を減らすのが大事 この章は、**Python を捨てるための話ではなく、Python を活かしながら Mojo を使うための章** と捉えるとよいでしょう。