O que é
O PyMini é um interpretador de um subconjunto de Python, escrito em Swift e integrado no teclado. Todos os seus painéis e todas as suas ações correm nele. Lê o Python que já conhece, e a mesma linguagem serve quer um script desenhe uma interface quer reescreva uma palavra.
# 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 statePorquê usá-lo
Criámos o PyMini porque o Clink precisava de uma forma de as pessoas programarem o seu próprio teclado com scripts, e nada do que encontrámos cabia dentro de um. Mas nada nele está preso a teclados, por isso agora é open source, como pacote Swift independente, para qualquer app que queira deixar os seus utilizadores escrever um pouco de Python.
Não é CPython, nem o traz dentro. Uma extensão de teclado recebe uma fração da memória de uma app, e o binário e o heap do CPython gastariam esse orçamento antes de o seu script correr. O PyMini são umas centenas de kilobytes de Swift, sem dependências binárias.
Serve em qualquer sítio onde os utilizadores queiram lógica própria: transformações de texto numa app de escrita, regras numa app de tarefas ou de orçamento, fórmulas, automatizações, mods de jogos, ou um sistema de plugins para uma app com comunidade. Muita gente já sabe algum Python, por isso não há uma linguagem nova para ensinar, e um script é texto simples que pode guardar, sincronizar e partilhar como qualquer outra definição.
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| Chamada | O que faz |
|---|---|
import PyMini | Sem dependências e nada para incluir à mão. Adicione o pacote Swift e funciona no iOS 15, macOS 12, tvOS 15, watchOS 8 e visionOS 1 ou posterior. |
py.define("shout") { call in ... } | As funcionalidades da sua app tornam-se funções e módulos Python. Os argumentos chegam como tipos Swift, e o que devolver volta ao script como valor Python. |
PyLimits(steps: 200_000) | Cada chamada corre com um orçamento de passos, e esgotá-lo é um erro que nenhum script consegue apanhar. Um while True descuidado termina com uma mensagem, não com uma app congelada. |
PyRuntime(modules: ["math"]) | Os scripts não têm ficheiros, rede nem processos. Só chegam às funções que definir e aos módulos padrão que deixar ativos. |
py.call(hook: "on_open") | Chame um script quando algo acontece na sua app. Um hook que o script não definiu é ignorado, por isso todos os hooks podem ser opcionais. |
catch let error as PyError | Os erros voltam com o nome da exceção Python e o número da linha, prontos a mostrar a quem escreveu o script. |
import sys | sys.version diz o nome do interpretador e a versão, e o comando pymini imprime a mesma linha. Um script pode assim dizer que versão a app à sua volta traz. |
O código-fonte, a referência da linguagem e os testes estão no GitHub.
O que garante
Não há sistema de ficheiros, nem rede, nem processo ao alcance de um script: a caixa de areia é a forma do interpretador, não uma lista de coisas que recusa. import é a única porta para fora, e dá para uma lista curta de espaços de nomes que apenas calculam. Tudo o resto custa passos. Cada expressão, cada volta de um ciclo, cada passo de uma expressão regular e cada linha de JSON gasta um de 2.000.000, e as cadeias e as listas têm um limite à medida que crescem.
Importável num painel partilhado
jsonmathrandomresystime
O orçamento não se pode apanhar. Uma cláusula except apanha um KeyError, mas não o aborto que põe fim a um ciclo desgovernado, porque apanhá-lo seria justamente como um script desgovernado continuaria a correr.
Registo de alterações
Cada versão do PyMini, da mais recente para a mais antiga, e o que acrescentou.
PyMini 2.0
A primeira versão sabia pôr uma palavra em maiúsculas e dispor uma grelha de botões. A segunda é uma linguagem em que se escreve um programa pequeno, e um painel que responde a um toque em vez de levar um fixo.
| Exemplo | O que mudou |
|---|---|
[word for word in words if word] | Compreensões, lambdas, tuplos, conjuntos e try / except / finally / raise. |
f"{total:,.2f}" | Conversões de f-string e especificações de formato completas, partilhadas com str.format e format(). |
f"""Hello {name}""" | Cadeias entre aspas triplas, cadeias raw e f-strings que ocupam várias linhas. |
import math, random, time, json, re | Cinco espaços de nomes e mais nenhum, todos eles puros. |
re.sub(r"\s+", " ", text) | Expressões regulares no motor do próprio Clink, que gasta o mesmo orçamento de passos. |
on_action(action, value, state) | Os painéis tratam dos seus próprios toques, guardam estado a sério, e têm mais onze controlos. |
replace(len(word), word.upper()) | Os scripts podem pedir ao teclado que atue, e ler o que está à volta do cursor. |
Current sentence | As ações leem qualquer parte do campo, não apenas a palavra e o que a antecede. |
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
A primeira versão. Uma ação transformava uma cadeia noutra, e um painel era um punhado de controlos cujos botões escreviam um texto fixo ou definiam um valor.
| Exemplo | O que mudou |
|---|---|
def transform(text): | int, float, str, bool, None, list e dict, com if, while, for e def com argumentos predefinidos e nomeados. |
f"{n} words" | f-strings sem especificações de formato, e indexação e fatias com índices negativos. |
sorted(text.split()) | 24 funções integradas e os métodos habituais de str, list e dict. |
button("+", set={"count": 1}) | Texto, botões, campos, pilhas e uma grelha. Um botão podia escrever texto ou definir estado, e mais nada. |
while True: | Nenhum import, um orçamento de passos para cada script e limites ao quanto as cadeias e as listas podem crescer. |
Para onde ir a seguir
A linguagem por inteiro, o vocabulário dos painéis, e um construtor que escreve ambos sem que escreva uma linha.
Ações ›Painéis ›Plugins ›Criador ›