Che cos'è
PyMini è un interprete di un sottoinsieme di Python, scritto in Swift e integrato nella tastiera. Ogni tuo pannello e ogni tua azione gira lì sopra. Legge il Python che già conosci, e la stessa lingua serve sia a uno script che disegna un'interfaccia sia a uno che riscrive una parola.
# 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 statePerché usarlo
Abbiamo creato PyMini perché Clink aveva bisogno di un modo per programmare la propria tastiera con degli script, e niente di quello che abbiamo trovato ci stava dentro. Eppure non ha nulla di legato alle tastiere, quindi ora è open source come pacchetto Swift a sé, per qualsiasi app che voglia lasciare ai propri utenti scrivere un po' di Python.
Non è CPython e non lo incorpora. Un'estensione di tastiera riceve una frazione della memoria di un'app, e il binario e lo heap di CPython spenderebbero quel budget prima ancora che il tuo script parta. PyMini è qualche centinaio di kilobyte di Swift, senza dipendenze binarie.
Usalo ovunque i tuoi utenti vogliano una logica tutta loro: trasformazioni di testo in un'app di scrittura, regole in un'app di attività o di budget, formule, automazioni, mod per giochi o un sistema di plugin per un'app con una community. Tanta gente conosce già un po' di Python, quindi non c'è un nuovo linguaggio da insegnare, e uno script è testo semplice che puoi salvare, sincronizzare e condividere come qualsiasi altra impostazione.
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| Chiamata | Cosa fa |
|---|---|
import PyMini | Nessuna dipendenza e niente da includere a mano. Aggiungi il pacchetto Swift e funziona su iOS 15, macOS 12, tvOS 15, watchOS 8 e visionOS 1 o successivi. |
py.define("shout") { call in ... } | Le funzioni della tua app diventano funzioni e moduli Python. Gli argomenti arrivano come tipi Swift, e quello che restituisci torna allo script come valore Python. |
PyLimits(steps: 200_000) | Ogni chiamata gira con un budget di passi, ed esaurirlo è un errore che nessuno script può catturare. Un while True distratto finisce con un messaggio, non con un'app bloccata. |
PyRuntime(modules: ["math"]) | Gli script non hanno file, rete né processi. Raggiungono solo le funzioni che definisci e i moduli standard che lasci attivi. |
py.call(hook: "on_open") | Chiama uno script quando succede qualcosa nella tua app. Un hook che lo script non ha definito viene saltato, così ogni hook può essere facoltativo. |
catch let error as PyError | Gli errori tornano con il nome dell'eccezione Python e il numero di riga, pronti da mostrare a chi ha scritto lo script. |
import sys | sys.version dice il nome dell'interprete e la versione, e il comando pymini stampa la stessa riga. Così uno script può dire quale versione porta l'app che lo esegue. |
Il codice sorgente, il riferimento del linguaggio e i test sono su GitHub.
Che cosa garantisce
Non c'è filesystem, non c'è rete e non c'è processo che uno script possa raggiungere: la sandbox è la forma dell'interprete, non un elenco di cose che rifiuta. import è l'unica porta verso l'esterno, e si apre su un breve elenco di spazi dei nomi che si limitano a calcolare. Tutto il resto costa passi. Ogni espressione, ogni giro di un ciclo, ogni passo di un'espressione regolare e ogni riga di JSON ne spende uno su 2.000.000, e stringhe e liste hanno un tetto mentre crescono.
Importabile in un pannello condiviso
jsonmathrandomresystime
Il budget non si può catturare. Una clausola except prende un KeyError, ma non l'interruzione che chiude un ciclo impazzito: catturarla sarebbe proprio il modo in cui uno script impazzito continuerebbe a girare.
Registro delle modifiche
Ogni versione di PyMini, dalla più recente, e cosa ha aggiunto.
PyMini 2.0
La prima versione sapeva mettere una parola in maiuscolo e disporre una griglia di pulsanti. La seconda è una lingua in cui scrivere un piccolo programma, e un pannello che risponde a un tocco invece di portarne uno fisso.
| Esempio | Cosa è cambiato |
|---|---|
[word for word in words if word] | Comprensioni, lambda, tuple, insiemi e try / except / finally / raise. |
f"{total:,.2f}" | Conversioni f-string e specifiche di formato complete, condivise con str.format e format(). |
f"""Hello {name}""" | Stringhe tra virgolette triple, stringhe raw e f-string su più righe. |
import math, random, time, json, re | Cinque spazi dei nomi e nessun altro, tutti puri. |
re.sub(r"\s+", " ", text) | Espressioni regolari sul motore di Clink, che consuma lo stesso budget di passi. |
on_action(action, value, state) | I pannelli gestiscono i propri tocchi, tengono uno stato vero e hanno undici controlli in più. |
replace(len(word), word.upper()) | Gli script possono chiedere alla tastiera di agire e leggere ciò che sta attorno al cursore. |
Current sentence | Le azioni leggono qualunque porzione del campo, non solo la parola e ciò che la precede. |
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
La prima versione. Un'azione trasformava una stringa in un'altra, e un pannello era qualche controllo i cui pulsanti digitavano un testo fisso o impostavano un valore.
| Esempio | Cosa è cambiato |
|---|---|
def transform(text): | int, float, str, bool, None, list e dict, con if, while, for e def con argomenti predefiniti e per nome. |
f"{n} words" | f-string senza specifiche di formato, e indicizzazione e slicing con indici negativi. |
sorted(text.split()) | 24 funzioni integrate e i metodi più comuni di str, list e dict. |
button("+", set={"count": 1}) | Testo, pulsanti, campi, pile e una griglia. Un pulsante poteva digitare testo o impostare lo stato, niente di più. |
while True: | Nessun import, un budget di passi per ogni script e un tetto a quanto possono crescere stringhe e liste. |
Dove andare poi
Tutta la lingua, il vocabolario dei pannelli, e un costruttore che scrive entrambi senza che tu digiti una riga.
Azioni ›Pannelli ›Plugin ›Creatore ›