Yokan
Write Python. Ship native.
Yokan(羊羹)は、静的に型付けされた Python のサブセットをネイティブコードにコンパイルする処理系です。書けるのは Python の一部ですが、その範囲のコードは Python とまったく同じに動きます。開発中はアプリ全体が本物の CPython で動き、リリースするときに同じソースが機械語の実行ファイルになります。その二つが同じに動くかどうかは、yokan gate で確かめられます。
全体像
同じソースが、二つの道で動きます。
どちらの道も同じ Rust 製の土台の上です。
土台の名前は pixie(Yokan が経由する基盤言語)です。
ビルドの途中で書き出される .pix が、人の読める pixie のソースです。
生成された .pix を開けば、自分のアプリが何にコンパイルされたのかを目で確かめられます(yokan translate app.py がそれを出力します)。
どんな見た目になるか

demo/opsboard
— 3 モジュール構成のダッシュボード。
ストアが 2 つ、直和型のヘルスモデルをビューの match で分岐、チャート、仮想化アラートフィード、テーマ切替。
すべて Python で書かれ、1 つのネイティブバイナリになります。
書いて、動かして、配る
いちばん小さいアプリの全文がこちらです。
# /// script
# requires-python = ">=3.14"
# dependencies = ["yokan"]
# ///
from yokan import State, button, column, run, text
count: State[int] = State(0)
def view():
with column(spacing=12, padding=16):
text(f"count: {count()}", size=34)
button("+1", on_click=lambda: count.set(count() + 1))
if __name__ == "__main__":
run(view, title="counter")
uv run app.py で動かすと、この画面が開きます(描画エンジンは Zed エディタを支える gpui)。

実行したままソースを直して保存すると、状態はそのままに、画面もハンドラの挙動も新しいコードに入れ替わります。 下の GIF がその様子です(別の小さなデモを編集しています)。 編集の前後でティックが止まっていないことに注目してください。

配るときはこうします。
@py を使っていないアプリなら、できあがる実行ファイルに CPython は入りません。
Python へのリンクもありません。
大きさは 14.7 MB(strip 後 11.3 MB)、起動は数ミリ秒です。
受け取る側のマシンに Python も pip も要りません。
「手元では動いたのに」
クリックや入力の並びを渡すと、CPython 版と機械語版の両方でそれを再生して、画面の結果をバイト単位で突き合わせます。 Yokan ではこれをゲートと呼んでいます。
Yokan 自身のモジュール(ファイル、SQLite、HTTP、クリップボード)は、両方の実行が同じ実装を呼ぶので食い違いようがありません。
Python 自身のモジュール(math、re、datetime など)は、開発中は CPython が、コンパイル後は双子が答えます。
その二つが同じ答えを返すことを確かめるのがゲートです。
まだできないことは、ツアー末尾の今できないことに理由付きでまとまっています。
移植した二本
付属デモのうち二本は、Python で書かれたアプリを移植したものです。 配るアプリに Python をどれだけ積むかで見ると、この二本はちょうど両端です。
Pyxel の例のゲームは、ほぼ一行ずつ書き写して移植しました。 ドット絵のキャンバスも、30fps も、押しっぱなしのキーの読み方もそのままです。 できあがるバイナリに Python は一行も入りません。 キー操作とフレームを並べたスクリプトが、両方の実行を再生します。 ゲートはその全フレームを比べます。
demo/shooter
と demo/jump。
Pyxel の例を二本、キャンバスに移したものです。
もう一方の端が、Buzz の画面と流れを移したデモです。
録音のファイルを窓に落とし、モデルと言語を選びます。
進み具合を見て、区間の表を読み、TXT と SRT と VTT に書き出します。
文字起こしをするのは Whisper です。
@py の中で、埋め込みの CPython が動かす本物の Python です。
--bundle --app を付ければ、CPython を同梱した .app になります。
ゲートは文字起こしの本文も、書き出した SRT も、バイト単位で比べます。

demo/transcribe。
Buzz の画面と流れを、@py の中で動く mlx-whisper と組み合わせたものです。
エージェントに書かせるなら
エージェントはファイルを書き、返ってきたものを読みます。 だから、何が返ってくるかで往復の質が決まります。 最初の二つのコマンドは、コンパイラもウィンドウもなしに約1秒で答えます。 返ってくるのは、代わりに何を書けばよいかを示す拒否と、テキストになった画面です。 ゲートは最後の証明です。
往復の全体はエージェントと作るにあります。
エージェントに渡すガイドは skills/yokan/SKILL.md です。
ほかに入っているもの
-
残りの Python もそのまま
関数に
@pyを付ければ、その関数は実行ファイルに同梱された本物の CPython で動きます。 numpy も pandas も、手持ちのコードもそのままです。 -
Rust crate(crates.io も手元のも)
yokan add app.py deunicode 1— crates.io の version 指定でも手元の path でも、宣言すれば Yokan のコードから呼べます。 crate 側は普通の Rust のままで、Yokan のために書き足すものはありません。 -
型チェックが通る
同梱の型スタブで pyright / Pylance のチェックがそのまま通ります。
@storeのシングルトンも@model/@valueのコンストラクタもWeak[Node]も、実行時の形どおりに見えます。
次はどこへ
名前は和菓子の羊羹からとりました。 中身のぎっしり詰まったひと棹を、切り分けて配るお菓子です。 アプリをひとつの実行ファイルに詰めて配る、その姿と重なります。
"Python" は Python Software Foundation の商標です。