どんなものか
PyMini は Swift で書かれた Python サブセットのインタプリタで、キーボードに組み込まれています。あなたが作るパネルもアクションも、すべてこの上で動きます。読むのはすでにご存じの形の Python で、画面を描くスクリプトも語を書き換えるスクリプトも、同じ言語で書けます。
# an action: text in, text out
def transform(text):
return text.upper()
# a panel: a view, and what a tap does
def view(state):
return button("Shout", "shout")
def on_action(action, state):
insert(context()["word"].upper())
return state使う理由
PyMini を作ったのは、Clink に自分のキーボードをスクリプトで拡張できる仕組みが必要だったのに、キーボードの中に収まるものが見つからなかったからです。とはいえキーボードに縛られた部分は何もありません。そこで今は独立した Swift パッケージとしてオープンソースになり、ユーザーに少し Python を書いてもらいたいどんなアプリでも使えます。
CPython ではありませんし、組み込んでもいません。キーボード拡張に与えられるメモリはアプリのごく一部で、CPython のバイナリとヒープだけで、あなたのスクリプトが走る前に使い切ってしまいます。PyMini は数百キロバイトの Swift だけで、バイナリ依存はありません。
ユーザーが自分なりのロジックを持ちたい場面ならどこでも使えます。文章アプリのテキスト変換、ToDo や家計簿アプリのルール、数式、自動化、ゲームの MOD、コミュニティのあるアプリのプラグインの仕組みなど。Python を少し知っている人は多いので新しい言語を教える必要がなく、スクリプトはただのテキストなので、ほかの設定と同じように保存、同期、共有できます。
import PyMini
let py = PyRuntime(modules: ["math", "json", "re"])
// your app, as a Python function
py.define("shout") { call in
try call.argument(0, as: String.self).uppercased()
}
// a script one of your users wrote
try py.load("""
def tidy(text):
return shout(" ".join(text.split()))
""")
try py.call("tidy", " hello there ") // HELLO THERE| 呼び出し | 動作 |
|---|---|
import PyMini | 依存関係はなく、手で同梱するものもありません。Swift パッケージを追加すれば、iOS 15、macOS 12、tvOS 15、watchOS 8、visionOS 1 以降で動きます。 |
py.define("shout") { call in ... } | アプリの機能が Python の関数やモジュールになります。引数は Swift の型で届き、返した値は Python の値としてスクリプトに戻ります。 |
PyLimits(steps: 200_000) | すべての呼び出しはステップ予算の中で動き、使い切るとどのスクリプトにも捕まえられないエラーになります。うっかり書いた while True も、アプリを固まらせずメッセージで終わります。 |
PyRuntime(modules: ["math"]) | スクリプトはファイルにもネットワークにもプロセスにも触れられません。届くのは、あなたが定義した関数と、有効のままにした標準モジュールだけです。 |
py.call(hook: "on_open") | アプリで何かが起きたときにスクリプトを呼び出せます。スクリプトが定義していないフックは飛ばされるので、どのフックも省略可能にできます。 |
catch let error as PyError | エラーは Python の例外名と行番号付きで返ってくるので、スクリプトを書いた本人にそのまま見せられます。 |
import sys | sys.version はインタプリタの名前とバージョンを返し、pymini コマンドも同じ行を表示します。スクリプトは、自分を動かしているアプリがどのビルドを積んでいるかを報告できます。 |
ソースコード、言語リファレンス、テストは GitHub にあります。
何を約束するか
スクリプトから触れるファイルシステムもネットワークもプロセスもありません。サンドボックスは「断るものの一覧」ではなく、インタプリタそのものの形です。外に出る扉は import だけで、その先にあるのは計算しかしない少数の名前空間です。ほかはすべてステップを消費します。式 1 つ、ループ 1 周、正規表現の 1 ステップ、JSON の 1 行がそれぞれ 2,000,000 のうちの 1 を使い、文字列とリストは伸びるそばから上限がかかります。
共有パネルでインポートできるもの
jsonmathrandomresystime
この予算は捕まえられません。except 節は KeyError を捕まえますが、暴走したループを終わらせる中断は捕まえられません。それを捕まえることこそ、暴走したスクリプトが走り続ける方法だからです。
変更履歴
PyMini の各版を新しい順に並べ、それぞれで加わったものをまとめています。
PyMini 2.0
第 1 版にできたのは、語を大文字にすることと、ボタンを格子に並べることでした。第 2 版は、小さなプログラムを書ける言語であり、決まった反応を持ち歩くのではなくタップに答えるパネルです。
| 例 | 変更点 |
|---|---|
[word for word in words if word] | 内包表記、lambda、タプル、集合、そして try / except / finally / raise。 |
f"{total:,.2f}" | f 文字列の変換と完全な書式指定。str.format と format() でも同じものが使えます。 |
f"""Hello {name}""" | 三重引用符の文字列、raw 文字列、複数行にまたがる f 文字列。 |
import math, random, time, json, re | 名前空間は 5 つだけ。どれも副作用のないものです。 |
re.sub(r"\s+", " ", text) | 正規表現は Clink 自前のエンジンで動き、同じステップ予算を使います。 |
on_action(action, value, state) | パネルはタップを自分で処理し、本物の状態を持ち、コントロールが 11 個増えました。 |
replace(len(word), word.upper()) | スクリプトからキーボードに動作を頼めるようになり、カーソル周辺も読めます。 |
Current sentence | アクションは入力欄のどの範囲でも読めます。語とその手前だけではありません。 |
import re
def initial():
return {"words": []}
def view(state):
top = sorted(set(state["words"]), key=lambda w: -len(w))[:3]
return vstack([text(f"{len(state['words']):,} words", mono=True)]
+ [row(w, detail=f"{len(w)}") for w in top])
def on_open(state):
state["words"] = re.findall(r"\w+", context()["before"])
return statePyMini 1.0
最初の版です。アクションは文字列を別の文字列に変えるもので、パネルはいくつかのコントロールを並べ、ボタンで決まった文字を入力するか値を設定するだけでした。
| 例 | 変更点 |
|---|---|
def transform(text): | int、float、str、bool、None、list、dict。if、while、for、そして既定値とキーワード引数を持てる def。 |
f"{n} words" | 書式指定のない f 文字列、負のインデックスを含むインデックス指定とスライス。 |
sorted(text.split()) | 組み込み関数 24 個と、str・list・dict のよく使うメソッド。 |
button("+", set={"count": 1}) | テキスト、ボタン、入力欄、スタック、グリッド。ボタンにできたのは文字の入力か状態の設定だけでした。 |
while True: | import はまったくなし。どのスクリプトにもステップ予算があり、文字列とリストの伸びにも上限がありました。 |
次に読むもの
言語の全体像、パネルの語彙、そして一行も打たずに両方を書いてくれるビルダー。
アクション ›パネル ›プラグイン ›クリエイター ›