PyConUS2022公布的PyScript可以在浏览器执行Python代码,依赖于pyodide(WebAssembly的CPython)。相比pyodide更接近Web的开发体验。公布的 alpha 版本输出 Python 版本信息是: 3.10.2 (main, Apr 9 2022, 20:52:01) [Clang 14.0.0 (https://github.com/llvm/llvm-project 78e87970af888bbbd5652c31f3a8
下载项目源码,执行 npm run dev
运行的官方 demo 主要针对数据处理和 REPL,本文整理 Web 开发常见的几个代码片段:获取数据,读取本地数据库,事件绑定。
Install
只需要增加HTML标签引入文件:
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
但会从 cdn.jsdelivr.net 请求 pyodide.asm 相关文件。去年年底开始国内的jsdelivr访问时断时续,整个依赖有22MB,需要先设置好网络。
Usage
在HTML中使用<py-script>标签嵌入Python代码。和<script>标签一样,支持 src 属性直接引入 .py 文件。以Web开发很常用的两个场景,网络请求和访问数据库为例:
网络请求
这一版的pyscript还不能直接用Python库发起Web请求,如果调用 http.client 会报错:
File "/lib/python3.10/http/client.py", line 941, in connect
self.sock = self._create_connection(
File "/lib/python3.10/socket.py", line 845, in create_connection
raise err
File "/lib/python3.10/socket.py", line 833, in create_connection
sock.connect(sa)
BlockingIOError: [Errno 26] Operation in progress
即使用 asyncio 处理,也默认是浏览器发起 WebSocket 连接。
可以调用 pyodide.http.open_url 或使用 Javascript 的 fetch 从接口获取数据并在页面显示的代码:
<table class="table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody id="table-data-rows">
<tbody>
</table>
<py-script>
import asyncio
from pyodide.http import open_url
import json
from js import fetch, document #调用Javascript的 fetch方法和document
async def request_github_api():
url = "https://api.github.com/"
r = await fetch(url)
data = await r.json()
# data = json.loads(open_url(url).read()) # open_url 读取数据,json处理成 dict
rows = document.getElementById("table-data-rows") # 循环体的处理都是 Javascript 语法
for k, v in data.object_entries():
# for k, v in data.items(): # open_url 返回的 dict
tr = document.createElement('tr')
key_td, value_td = document.createElement('td'), document.createElement('td')
key_td.innerHTML = k
value_td.innerHTML = v
tr.append(key_td, value_td)
rows.appendChild(tr)
asyncio.ensure_future(request_github_api())
</py-script>
访问数据库
pyodide支持的 Python 包列表: https://github.com/pyodide/pyodide/tree/main/packages
已经支持 Sqlalchemy 这个主要的 ORM 包。因为缺少依赖且浏览器有限制,还不能连接远程数据库,但可以使用 Sqlite 的 inmemory 模式,代码如下:
<py-env>
- sqlalchemy # 其他包使用 <py-env> 引入
</py-env>
<py-script>
from sqlalchemy import create_engine, text
engine = create_engine("sqlite+pysqlite:///:memory:", future=True) # Sqlite 连接
with engine.connect() as conn:
conn.execute(text("CREATE TABLE sites (name string, domain string)")) # 建表,写数据,提交
conn.execute(
text("INSERT INTO sites (name, domain) VALUES (:name, :domain)"),
[{"name": "Dmyz1", "domain": "http://dmyz.org"}, {"name": "Dmyz2", "domain": "http://dmyz.net"}],
)
conn.commit()
result = conn.execute(text("SELECT * FROM sites"))
for row in result.all(): # 遍历结果。之前已经写过 DOM 的处理,这里直接 print
print("Site: {}, Domain: {}".format(row[0], row[1]))
</py-script>
事件绑定
PyScript 使用 pys-{EVENT} 属性在 HTML 标签上绑定事件,例如点击按钮获取时间戳:
<button id="time-btn" pys-onClick="get_time">Time</button>
<p id="current-time"></p>
<py-script>
import time
def get_time(*args, **kwargs):
Element("current-time").write(time.time())
</py-script>
Afterwords
WebAssembly也发展近十年了,“让浏览器运行C/C++代码”听起来如同 nodejs 一样的新颖,然而只有一些简单的游戏演示,在 Web 应用方面让人眼前一亮的项目并不多。前几年接触iodide和pyodide的时候,也只觉得和 Jupyterhub 比较像,没想到合适的用途。
这次再看到 PyScript ,似乎只是 pyodide 加了几个 HTML 标签,但用起来顺手很多。第一印象是 PHP/JSP 曾经的混合写法,实际上手就发现它已经推进了 WebAssembly 的目标:让 C(Python)/C++ 成为真正能直接被浏览器解析,简单易上手的语言。虽然受限于网速、浏览器性能等因素,还替代不了 Javascript,但对于一些特定领域尤其是 Python 擅长的数据处理/机器学习,PyScript 应该能从 Javascript 分走一些。而且 Python 工程师们,以后就是名副其实的 Web 全栈了。