💡返回博客
Python 语言能力
Python 作为一种动态脚本语言,相对其他语言有如下优势:
- 适用范围广(Tuibe 排行第一)
- 入门门槛低
- 强大的标准库
- 扩展性强(通过 C/C++ 可以方便引入 Native Module)
- 语法灵活(生成器、装饰器、内省、协程等)
对比 JavaScript
- 更多的数据类型
- 更广泛的的标准库以及第三方模块
- 更高级的语法特性
示例
在Python中实现一个快排算法
def quick_sort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[0]
less = [x for x in arr[1:] if x <= pivot]
greater = [x for x in arr[1:] if x > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
只是为了排序功能,也可以直接调用标准库
arr.sort()
# or sorted(arr)
Python Runtime 实现
Python in Browser
Gandi 的主要场景是在浏览器中运行,如果将Python集成进来,则需要解决如何在浏览器中运行 Python 的问题。比较常见的方案有:
由于 Skulpt 只支持 Python 2.x,这里主要对比 Pyodide 和 Brython:
Brython | Pyodide | |
开发活跃度 | 活跃 | 活跃 |
语言能力完备性 | 较完备 | 完备 |
最新支持版本 | 3.11 | 3.10 |
标准库支持 | 支持 | 支持 |
第三方模块 | 支持 Pure-Python 模块 | 支持 Pure-Python 模块以及 C/C++ Extension 模块 |
调用 ECMAScript | 支持 | 支持 |
操作 DOM | 支持 | 支持 |
类型映射 | 无需映射 | 需要类型映射 |
License |
Pyodide 由于 Runtime 实现是基于 CPython,因此语言能力更接近原生的 Python,同时也支持调用浏览器 ECMAScript、DOM(理论上未来可以直接在 Python 中调用 Scratch 的 Runtime 和 Render)。
综上,采用 Pyodide 作为 Python in Browser 的技术选型。
运行架构
为了避免 Python Runtime 阻塞 Scratch VM 执行线程,采用 WebWorker 架构来并行的执行 Python 脚本
能力限制(对比CPython)
- 不支持的模块
- 功能不完整的模块
distutils、ssl、lzma、sqlite3
curses、dbm、ensurepip、idlelib、lib2to3、tkinter、turtle.py、turtledemo、venv、pwd
multiprocessing、threading、sockets
安全性
由于 Python 强大的语法特性(不像 Scratch 只提供了有限的能力),其创作的 Scratch 作品会运行在所有玩家的浏览器上,因此还需要考虑脚本的安全性,避免恶意脚本。
- 基于 Browser + WebAssembly 的沙箱机制
- 没有多线程、多进程、Socket等模块能力
- 对文件系统的访问是抽象可限制的(当前提供的是 MemFS + WorkerFS)
- 无法进行系统调用
- 独立的 WebWorker 限制了 JavaScript 的作用域
- 暂时限制了 Pyodide VM 层对 JS 的调用,屏蔽了 pyodide 内置的但 不安全的模块
抽象文件系统
在 Python 中如果需要访问项目中的资源文件(目前开放支持了 JSON 等文本类型文件)、或者 import 其他 Python 文件,则需要构建一个抽象的文件系统。
我们使用 Emscripten 的 FileSystem API,将项目中的 Assets 手动挂载到 FS 中,这样即可以通过 open
等系统调用访问文件内容。由于是 WorkerFS 的特性,文件内容也是只读的。
关于 WorkerFS 的实现原理参考 emscripten/library_workerfs.js
let mountDir = "/project";
self.pyodide.FS.mkdir(mountDir);
self.pyodide.FS.mount(self.pyodide.FS.filesystems.WORKERFS, {
blobs: fs.map(f => {
return {
name: f.name,
data: new Blob([f.data]),
}
}),
}, mountDir);
self.pyodide.FS.chdir(mountDir);
除了这里手动挂载的资源目录,外部的根文件系统 /
使用的是默认的 MemFS。即所有的文件存在于内存中,写入的文件内容在页面重新加载后会丢失。
文本编辑器
为了方便地在 Gandi IDE 中编写 Python 脚本,这里还需要在前端引入一套文本编辑器组件,针对它的选型,主要考虑以下几点:
- 开源且开发活跃
- 适用范围广、大量应用于生产环境
- 扩展性强(支持 Language Server 和 Collaborative Editing)
综合考虑,选用 Monaco Editor
Language Server
为了更好的接近原生的 VSCode/IDE 编辑体验,我们基于 Language Server Protocol,实现了在编辑器中 Python 脚本的高级编辑能力:
- 代码补全提示
- 语法或代码风格检测
- 高亮和格式化
- 跳转定义和查找引用
架构图
这里更优的实现方式可能是用 WebAssembly 实现 Language Server,通过嵌入到 WebWorker 与 Monaco Editor 进行通信,以减少对服务端的依赖和降低请求延迟。新的架构目前已经在技术预研阶段,并将在不久后开源。
协作?
目前该文本编辑器暂不支持协作,相关研发已经在规划中。
FAQ
- 脚本执行会保存状态吗?
- 支持加载外部第三方扩展吗?
- 支持脚本之间相互 import 吗?
- 支持在脚本中直接读写 Scratch 里面的变量吗?
- 支持脚本操作舞台吗?
- 可以写爬虫吗?
会的。单个脚本的多次脚本执行使用同一个 VM,即是可以在脚本里保存运行状态。比如多次调用可以共享同一组全局变量。
暂未开放。后续会根据用户反馈情况逐步白名单开放。
支持。比如 a.py 需要 import b.py 的方法 add,可以使用直接使用 from b import add
。
暂不支持。为了和 Scratch VM有较好的解耦合,避免在脚本内部隐形操作变量导致项目维护成本变高,暂时不支持直接读写变量,而是手动通过值积木在外层传值和赋值。
暂不支持。具体原因同上。
不行。为了安全考虑,暂不会放开网络库(urllib, requests等)以及提供其他类似库。
<iframe
width="100%"
height="800px"
scrolling="no"
src="https://www.ccw.site/embed?id=blog/posts/tech-behind-python-in-gandi&type=comment"
title="Python in Gandi 技术实现"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>