skulpt模块认知 通过上一篇的讲解我们应该知道skulpt实现python模块是用javascript写的并且由skulpt提供的一些jsAPI来使python能够以想要的形式来调用。
比如我想在python调用模块内的某个方法是这么写的:
那么js编写的模块就可以这么声明这个add函数:
1 2 3 4 5 6 7 8 var $builtinmodule = function (name ) { var mod = {__name__ : new Sk .builtin .str ("mod" )} mod.add = new Sk .builtin .func (function (a, b ) { return Sk .ffi .remapToJs (a) + Sk .ffi .remapToJs (b); }); return mod; }
这里要讲两个非常重要的api:Sk.ffi.remapToJs
和Sk.ffi.remapToPy
在上面的这段函数声明里,参数a和b都是从python传进来的,可能会存在与js语言数据类型不一致导致的错乱,所以需要通过Sk.ffi.remapToJs
将python的参数转成js再来进行js的运算
1 2 3 mod.add = new Sk .builtin .func (function (a, b ) { return Sk .ffi .remapToJs (a) + Sk .ffi .remapToJs (b); });
而js的逻辑想要给python用的话最好也要用Sk.ffi.remapToPy
转一下:
1 2 3 4 mod.add = new Sk .builtin .func (function (a, b ) { const result = Sk .ffi .remapToJs (a) + Sk .ffi .remapToJs (b) return Sk .ffi .remapToPy (result); });
加载模块 在上一篇中有讲到最简单的模块实现,其中html的部分就包含了加载外部模块的能力
在下面的这段skulpt初始化配置的代码里有一个read钩子函数builtinRead
,这个钩子函数就是用来处理python代码中加载模块的具体实现:
1 2 3 4 5 Sk .configure ({ output : outf, read : builtinRead, __future__ : Sk .python3 , });
再来看看builtinRead
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var externalLibs = { "./mod/__init__.js" : "./mod.js" , }; function builtinRead (file ) { console .log ("Attempting file: " + Sk .ffi .remapToJs (file)); if (externalLibs[file] !== undefined ) { return Sk .misceval .promiseToSuspension ( fetch (externalLibs[file]).then ( function (resp ){ return resp.text (); } )); } if (Sk .builtinFiles === undefined || Sk .builtinFiles .files [file] === undefined ) { throw "File not found: '" + file + "'" ; } return Sk .builtinFiles .files [file]; }
在上面代码中的Sk.misceval.promiseToSuspension
API会非常有用,在javascript中经常有依赖的异步任务(如加载外链的js、实现sleep函数等)需要等待回调完成才能继续后面的python逻辑,Sk.misceval.promiseToSuspension
就是处理这种问题的API,它的传参是一个promise对象,只要promise内部在什么时候resolve就可以决定在什么时候交回主线程。
python调用js模块 python调用js模块功能的方式肯定不是只有单纯的函数调用一种方式,下面我会列出常见的python调用方式对应的js模块的写法:
函数声明
1 2 3 mod.add = new Sk .builtin .func (function (a, b ) { return Sk .ffi .remapToJs (a) + Sk .ffi .remapToJs (b); });
类声明
1 2 3 stack = mod.Stack() stack.push(1 ) stack.pop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mod.Stack = Sk .misceval .buildClass (mod, function ($gbl, $loc ) { $loc.__init__ = new Sk .builtin .func (function (self ) { self.stack = []; }); $loc.push = new Sk .builtin .func (function (self,x ) { self.stack .push (x); }); $loc.pop = new Sk .builtin .func (function (self ) { return self.stack .pop (); }); }, "Stack" , []);
PS: 类声明里的函数声明第一个参数被self保留,注意是第二个参数开始才是真的参数,与正常的函数声明有所不同
实例对象属性监听 当我的js模块想要监听在python实例化对象的某个属性变化时:
1 2 3 4 5 6 7 stack = mod.Stack() stack.push(1) stack.push(2) print(stack.stack) // [1,2] stack.stack = [2,3,4] print(stack.stack) // [2,3,4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 mod.Stack = Sk .misceval .buildClass (mod, function ($gbl, $loc ) { $loc.__init__ = new Sk .builtin .func (function (self ) { self.stack = []; }); $loc.push = new Sk .builtin .func (function (self,x ) { self.stack .push (x); }); $loc.pop = new Sk .builtin .func (function (self ) { return self.stack .pop (); }); const stackGetter = new Sk .builtin .func (function (self ) { return Sk .ffi .remapToPy (self.stack ) }) const stackSetter = new Sk .builtin .func (function (self, val ) { const newStack = Sk .ffi .remapToJs (val) self.stack = newStack; }) $loc.stack = Sk .misceval .callsim (Sk .builtins .property , stackGetter, stackSetter); }, "Stack" , []);
异步任务 当python需要等待js的异步任务完成才继续的时候可以用Sk.misceval.promiseToSuspension
来实现,比如我想在python实现一个sleep函数:
1 2 3 4 5 6 7 mod.sleep = new Sk .builtin .func (function (delay ) { return new Sk .misceval .promiseToSuspension (new Promise (function (resolve ) { Sk .setTimeout (function ( ) { resolve (Sk .builtin .none .none$ ); }, Sk .ffi .remapToJs (delay)*1000 ); })); });
Sk.misceval.promiseToSuspension
接收一个promise,只要promise内部在什么时候resolve就可以决定在什么时候交回主线程。
js模块调用python 调用python全局变量 有些python库会需要主程序声明一些全局函数来让python库能够作为回调函数告知外部触发,比如pygame-zero的update事件:
1 2 3 4 5 star=Actor('star' ) def update (): star.x+=1 star.y+=1
skulpt提供Sk.globals
使js能调用python主程序的全局变量
1 2 Sk .globals .update && Sk .misceval .callsimAsync (null , Sk .globals .update );
Sk.misceval.callsimAsync
可以作为异步任务触发python的函数,防止阻塞主线程
调用python回调函数
1 2 3 4 def callback(result): print(result) // 3 mod.add(1,2,callback);
1 2 3 4 5 mod.add = new Sk .builtin .func (function (a, b, cb ) { const result = Sk .ffi .remapToJs (a) + Sk .ffi .remapToJs (b); Sk .misceval .callsim (cb, result); });
类型校验 类型判断 Sk.abstr.typeName
用于判断变量类型,返回字符串格式的类型名,已知的类型名如下:
int
str
list
tuple
dict
float
lng
set
bool
1 2 3 4 5 6 new Sk .builtin .func (function (description ) { if (Sk .abstr .typeName (description) !== "str" ) { throw new Sk .builtin .TypeError ("Error description should be a string" ); } })
Sk.abstr.typeName
还可以得到实例对象所属的类名,类似js的xxx.constructor.name
1 2 3 4 import modstack = mod.Stack() mod.typeName(stack) // 'Stack'
1 2 3 mod.typeName = new Sk .builtin .func (function (arg ) { return Sk .abstr .typeName (arg) })
检查传参数量
1 2 Sk .builtin .pyCheckArgs ('__init__' , arguments , 3 , 5 , false , false );
如果不符合规则会抛出一段内部错误:
1 TypeError: __init__() takes at least 3 arguments (1 given) on line 4
错误处理 对于未实现的功能抛出错误 1 throw new Sk.builtin.NotImplementedError("Not yet implemented");
正常的抛出错误 1 throw new Sk.builtin.TypeError("Something went wrong");
小结 这篇的内容全部都掌握了的话对于开发一个能适应各种python调用形式的skulpt模块基本是完全足够的,你甚至可以拿现有的js库封装成相对应同样功能的python库,比如用echarts封装成pyecharts的用法,这样你就可以很快的实现了一个能够在线运行的pyecharts了,还有各种游戏库、3D库都是可以实现的。
用javascript的生态开拓python在线运行的市场,也不失为一种优秀的策略。