ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Brython 实现剖析:Python 语法如何翻译为 JavaScript 代码

Brython 实现剖析:Python 语法如何翻译为 JavaScript 代码 编程语言语言运行时编译器前端【免费下载链接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser项目地址https://gitcode.com/gh_mirrors/br/brython点击查看免费下载导读BrythonBrowser Python是一个运行在浏览器中的 Python 3 实现。本文基于官方文档 www/doc/en/implem.md 的翻译对照表逐条拆解变量赋值、属性访问、运算符、循环、函数定义、异常处理、类定义等 Python 语法在编译后被翻译成的 JavaScript 代码形态并结合 ast_to_js.js、py_type.js 等核心源码说明其底层实现原理。读完本文你将理解 Brython 的翻译而非解释实现策略、$globals/$locals命名空间机制、特殊方法驱动的运行时模型以及编译器在性能与语义一致性之间所做的取舍。一、从 Python 到 JavaScriptBrython 的翻译策略Brython 并不是在浏览器中内置一个 Python 解释器而是把 Python 源码先解析为 AST再由编译器把 AST 翻译为等价的 JavaScript 代码。这条编译链路在 www/src/py2js.js编译入口与 www/src/ast_to_js.jsAST→JS 代码生成器中实现最终产物是 www/src/brython.js 及打包后的 www/src/brython.min.js。翻译的核心难点在于Python 与 JavaScript 的对象模型并不一致。Python 的属性访问、索引、运算符、迭代都基于一套统一的特殊方法协议__getattr__、__setitem__、__add__、__next__等而 JavaScript 是原生语法。Brython 的解决方案是把 Python 语法翻译成对特殊方法的显式调用让运行时通过getattr/setattr这些内部函数统一派发。下面各节以官方文档的翻译对照表为基础逐一展开每种语法结构的翻译形态与设计考量。二、变量赋值与命名空间$globals与$locals最简单的赋值语句翻译如下PythonJavascriptx 1y 3.14z azertyvar $globals __BRYTHON__.vars[__main__]var $locals $globalsx$globals[x]Number(1)y$globals[y]float(3.14)z$globals[z]azerty每个脚本开头的两行是固定样板代码它们定义内部变量$globals与$locals供内置函数globals()和locals()使用。可见 Python 变量的存储方式是以名字为键存入命名空间对象而不是直接映射为 JavaScript 的var声明——这正是后续getattr/setattr派发机制能够成立的基础。注意数值的类型转换整数1被包成Number(1)浮点数3.14被包成float(3.14)。这里的float是定义在 www/src/py_float.js 中的 JavaScript 函数它返回的是一个 Python 浮点数对象而非原生 JS number从而保证 Python 语义如1.1 2.2的精度行为、与整数的区分在浏览器中得以保留。文档中明确注明float是__py_float.js中定义的 JavaScript 函数。三、属性访问与索引统一走特殊方法Python 中看似简单的属性读写和索引操作在 Brython 中一律翻译为对特殊方法的显式调用PythonJavascript说明x foo.barxgetattr(foo,bar)属性读取foo.bar xsetattr(foo,bar,x)属性写入x foo[bar]xgetattr(foo,__getitem__)(bar)索引读取foo[bar] xgetattr(foo,__setitem__)(bar,x)索引写入可以看出foo[bar]本质上被翻译为取到foo的__getitem__方法并调用它。这种统一派发的设计让所有 Python 对象——无论是内置类型、自定义类还是 DOM 封装对象——都遵循同一套访问协议也让运算符重载、切片等高级特性在浏览器中有了自然的落点。四、运算符以__add__为代表的特殊方法调用二元运算的翻译遵循同样的模式PythonJavascript说明xygetattr(x,__add__)(y)所有运算符均如此翻译文档特别指出same for all operators所有运算符都按此方式翻译并强调这种翻译necessary to implement such operations as2 * a——即2 * a这类跨类型运算必须依赖__mul__特殊方法在运行时决定语义而不是依赖 JavaScript 原生运算符原生 JS 中2 * a是NaN而 Python 中应得到aa。代价是性能每次二元运算都变成一次getattr 调用因此 Brython 在增强赋值上做了专门优化见下节。五、增强赋值性能与语义的折中x y的翻译是整个文档中最复杂的片段之一var $tempy; if(typeof $tempnumber typeof xnumber){ x$temp;$globals[x]x }else if(!hasattr(x,__iadd__)){ var x$globals[x]getattr(x,__add__)($temp); } else{ x$globals[x]getattr(x,__iadd__)($temp) }翻译呈现三级分支快路径若左右操作数都是 JSnumber类型直接使用 JavaScript 原生运算符避免getattr派发开销——文档注明第一个测试允许在两个变量都是整数时使用 JavaScript 的 运算符以提升性能兜底路径若对象没有__iadd__原地加法方法退化为__add__并重新绑定变量原地路径若有__iadd__调用它实现就地修改。无论走哪条分支最终都会把结果同步写回$globals[x]保证 Python 的命名空间语义不因优化而破坏。六、逻辑运算and/or保留短路求值a and b被翻译为$test_expr($test_item(a)$test_item(b))文档解释了两个关键点刻意保留 JavaScript 的运算符目的是不评估b当a为假时——即 Python 的短路求值语义$test_item返回一个 JavaScript 布尔值true或false并把求值结果存入一个全局变量$test_expr则取出并返回这个全局变量从而还原 Python 中and/or返回操作数值本身而非简单布尔值的行为。这套$test_item/$test_expr机制是 Brython 运行时www/src/py_utils.js 等内部模块中负责真值测试的基础设施也被if、while、三元表达式等条件场景共用。七、for循环基于迭代器协议与异常栈Python 的for循环在 Brython 中被翻译成一个显式的while(true)驱动迭代器的循环var $next9$locals[$next9]getattr(iter(iterable),__next__) var $no_break9true;while(true){ try{ var obj$globals[obj]$next9();None; } catch($err){ if(__BRYTHON__.is_exc($err,[StopIteration])){ __BRYTHON__.$pop_exc();break }else{ throw($err) } } (...) }对照官方文档的注释这段代码揭示了几个运行时约定迭代器协议iter(iterable)取得迭代器getattr(...,__next__)取出其__next__方法每次循环调用一次取得下一个值$no_break一个布尔变量专门用于支持for ... else子句——只有循环自然结束未 break时才执行else块$pop_exc()内部函数把最后一个异常从异常栈中弹出由于StopIteration是 Python 内部机制捕获后需要手动清理异常栈避免污染后续的异常状态is_exc(exc, classes)内部函数检查异常exc是否为classes中某个异常类的实例承担 Pythonexcept子句的类型匹配职责。八、解包赋值运行时才做的异常检查x, y iterable的翻译相当长因为解包的错误必须发生在运行时var $right9getattr(iter(iterable),__next__); var $rlist9[];while(true){ try{$rlist9.push($right9())} catch(err){__BRYTHON__.$pop_exc();break} } if($rlist9.length2){ throw ValueError(need more than $rlist9.length values to unpack) } if($rlist9.length2){ throw ValueError(too many values to unpack (expected 2))} var x$globals[x]$rlist9[0] var y$globals[y]$rlist9[1]翻译逻辑分三步先耗尽迭代器把所有值收进$rlist9遇到StopIteration即$pop_exc()后跳出然后检查元素个数是否匹配目标变量个数——不足时抛ValueError(need more than ...)过多时抛ValueError(too many values to unpack (expected 2))最后按位置把值赋给各变量。文档对此的注释是翻译相当长但异常处理必须在运行时进行——因为迭代器长度在编译期不可知。九、函数定义$MakeArgs与命名空间构造def foo(): x3的翻译展示了函数定义的核心模板var foo (function (){ var $defaults {} return function(){ try{ var $locals __BRYTHON__.vars[__main__-foo-a8mk6bg2]{} for(var $var in $defaults){ eval(var $var$locals[$var]$defaults[$var]) } var $ns__BRYTHON__.$MakeArgs(foo,arguments, [],[],null,null,[]) for(var $var in $ns){ eval(var $var$ns[$var]) $locals[$var]$ns[$var] } var x$locals[x]3;None; } catch(err10){ throw __BRYTHON__.exception(err10) } } } )() foo.__name__foo $globals[foo]foo foo.$typefunction foo.__module__ __main__ foo.__doc__ foo.__code__ {__class__:__BRYTHON__.$CodeDict}对照文档注释可以拆解出函数实现的完整模型IIFE 包裹函数体被包在一个立即执行函数里外层先建立$defaults默认参数表内层才是真正的函数体函数命名空间$locals指向__BRYTHON__.vars[__main__-foo-a8mk6bg2]其中function_id由模块名、函数名和一段随机字符串如a8mk6bg2拼接而成保证函数局部命名空间全局唯一、互不干扰$MakeArgs内置函数负责检查调用时传入的实参并依据函数签名把实参与形参名一一对应返回一个对象$ns随后循环eval把$ns中的每个名字声明为局部变量并同步写入$localseval(var $var ...)用动态var声明局部变量是 Brython 让局部变量真正成为 JavaScript 局部作用域变量的手段元数据属性翻译后立即为函数对象设置__name__、__module__、__doc__并把$globals[foo]foo把函数名登记进模块命名空间$typefunction属性供内部区分模块级函数与类中定义的方法__code__指向$CodeDict类实例承载函数字节码层面的元信息。十、global声明跳过var当函数内声明了global x时赋值x3的翻译变为x$globals[x]3与普通局部赋值相比唯一区别是不用var关键字——文档注释明确写道for a global variable, we do not use thevarkeyword。这样x就不会成为函数局部变量而是直接读写模块命名空间$globals实现了 Python 的global语义。十一、复杂签名默认值、*args与**kwdef foo(x, y3, *args, **kw)的翻译展示了$MakeArgs的完整形态var foo (function (){ var $defaults {y:3} return function(){ try{ var $locals __BRYTHON__.vars[__main__-foo-6f58vupa]{} for(var $var in $defaults){ eval(var $var$locals[$var]$defaults[$var]) } var $ns__BRYTHON__.$MakeArgs(foo,arguments, [x],[y],args,kw,[]) for(var $var in $ns){ eval(var $var$ns[$var]) $locals[$var]$ns[$var] } (...) } catch(err9){throw __BRYTHON__.exception(err9)} } } )()要点默认参数被收集进$defaults对象此处为{y:3}在函数体开头通过循环eval逐一声明并写入$locals$MakeArgs的调用参数与函数签名一一对应[x]是位置参数列表[y]是带默认值的参数列表args是*args变量名kw是**kw变量名最后一个数组可携带额外参数如 keyword-only 参数。它负责把实参按签名规则组装为$ns对象文档注释总结$MakeArgs构建一个与函数签名中名字相匹配的 JavaScript 对象接下来的循环则构建函数的命名空间局部变量。十二、函数调用与关键字参数PythonJavascript说明foo(x)getattr(foo,__call__)(x)调用统一走对象的__call__方法foo(x,y1)getattr(foo,__call__)(x,$Kw(y,Number(1)))关键字实参被$Kw函数包装为对象两条规则对应两个机制__call__派发任何可调用对象函数、类、实现了__call__的实例都通过其__call__方法被调用这让类即可调用和实例可调用在翻译层面完全统一$Kw关键字包装y1这类关键字实参被转换为$Kw创建的对象其中携带形参名y与值Number(1)供$MakeArgs在运行时做名字匹配。十三、try/except翻译为catch加is_exc类型匹配try: x[2]a except TypeError: ... except: ...的翻译揭示了异常处理的翻译模式__BRYTHON__.$failed9false try{ getattr(x,__setitem__)(2,a) } catch($err9){ __BRYTHON__.$failed9true if(false){void(0)} else if(__BRYTHON__.is_exc($err9,[TypeError])){ getattr($print,__call__)(erreur) } else{ getattr($print,__call__)(autre erreur) } }文档注释指出在所有except子句之前都会插入这样两行catch($err51){ if(false){void(0)}这两行是翻译器的占位机制每个except子句被翻译成else if当指定了异常名时或else未指定异常名时而if(false){void(0)}是第一个分支前的空占位保证else if/else链结构在语法上总是成立。异常是否匹配由is_exc($err9,[TypeError])完成——它检查被捕获的 JS 错误是否为TypeError异常类的实例实现 Python 的except类型匹配语义。$failed9布尔标记则记录 try 块是否失败供try/finally、try/except/else等复杂结构的后续判断使用。十四、类定义$class_constructor组装类对象最简单的class foo: pass翻译为var $foo(function(){ var $class {$def_line:__BRYTHON__.line_info} void(0) return $class } )() $foo.__doc__ $foo.__module____main__ var foo__BRYTHON__.$class_constructor(foo,$foo,tuple([]),[],[]) __BRYTHON__.vars[__main__][foo]foo对照文档注释类体在一个以$为前缀的函数中执行该函数返回对象$class持有类的属性和方法{$def_line:__BRYTHON__.line_info}记录类定义的行号信息$class_constructor是定义在 py_type.js 中的核心工厂函数它会构建两个 JavaScript 对象一个用于创建类实例的工厂以及一个保存类属性与方法的对象传给$class_constructor的参数依次是类名foo、$前缀函数产生的$foo字典、父类元组tuple([])可选父类、父类名列表[]、以及可选的metaclass关键字参数最后__BRYTHON__.vars[__main__][foo]foo把类登记进模块命名空间。从 py_type.js 的实现可以看到$class_constructor在运行时承担的完整职责校验基类是否为可派生类型BASETYPE标志、处理__module__/__qualname__、在覆盖__eq__而未定义__hash__时隐式置__hash__ None、规范化__slots__条目、把__new__强制转为staticmethod最终通过metaclass.tp_new创建类对象并必要时调用 metaclass 的__init__。这些正是 ast_to_js.js 在生成$B.$class_constructor(类名, ...)调用时翻译器与运行时之间约定的完整接口。十五、带方法与继承的类self.x x的翻译class foo(A): def __init__(self,x): self.x x的翻译是最完整的类示例var $foo(function(){ var $class {$def_line:__BRYTHON__.line_info} $class.__init__ (function (){ var $defaults {} return function(){ try{ var $locals __BRYTHON__.vars[__main__-__init__-kdc7mc5z]{} for(var $var in $defaults){ eval(var $var$locals[$var]$defaults[$var]) } var $ns__BRYTHON__.$MakeArgs(__init__,arguments, [self,x],[],null,null,[]) for(var $var in $ns){ eval(var $var$ns[$var]) $locals[$var]$ns[$var] } setattr(self,x,x) } catch(err10){throw __BRYTHON__.exception(err10)} } } )() $class.__init__.__name____init__ $class.__init__.__module__ __main__ $class.__init__.__doc__ $class.__init__.__code__ {__class__:__BRYTHON__.$CodeDict} return $class } )() $foo.__doc__ $foo.__module____main__ var foo__BRYTHON__.$class_constructor(foo,$foo,tuple([A]),[A],[]) __BRYTHON__.vars[__main__][foo]foo文档注释指出了两个关键点方法__init__被作为属性挂到$class对象上其函数体翻译与模块级函数完全同构同样使用$MakeArgs解析[self,x]形参但self.x x被翻译为setattr(self,x,x)再一次印证属性赋值 setattr 派发的统一原则继承体现在$class_constructor的第三个参数tuple([A])表示父类元组中包含类A[A]是父类名字符串列表——$class_constructor据此解析基类、构建 MRO完成继承链的运行时组装。十六、总结翻译式实现的三个设计支柱回顾整份翻译对照表Brython 的实现可以归纳为三个支柱命名空间驱动所有变量、函数、类都以名字为键存入__BRYTHON__.vars下的模块/函数命名空间$globals/$locals保证 Python 的globals()、locals()、global等语义可完整实现特殊方法派发属性访问getattr/setattr、索引__getitem__/__setitem__、运算符__add__等、调用__call__、迭代__next__全部翻译为对特殊方法的显式调用让 Python 对象模型在 JS 侧获得统一且完整的表达运行时辅助函数$MakeArgs实参解析、$class_constructor类组装、is_exc/$pop_exc异常栈与类型匹配、$test_item/$test_expr真值测试、$Kw关键字实参包装等内部函数承担语义落地的重任编译期只负责生成对它们的调用从而在编译期翻译与运行时语义之间划出清晰的边界。同时翻译并非机械替换的整数快路径、and/or保留 JS实现短路、for循环用$no_break支持else子句等设计都体现了 Brython 在保持 Python 语义的前提下对浏览器运行时性能的务实取舍。对希望深入理解编译型 Python 实现、或在 JS 侧构建语言运行时的读者而言这份翻译对照表与 ast_to_js.js、py_type.js、py_exceptions.js 等源码相互印证是绝佳的入门与参考材料。赞分享编程语言语言运行时编译器前端【免费下载链接】brythonBrython (Browser Python) is an implementation of Python 3 running in the browser项目地址https://gitcode.com/gh_mirrors/br/brython点击查看免费下载相关推荐免费开源音乐播放器LX Music Desktop 桌面版使用指南免费开源音乐播放器LX Music Desktop 桌面版使用指南 厌倦了各种音乐平台繁琐的会员订阅和广告打扰想要一个纯净、免费且功能强大的桌面音乐播放器桌面应用音视频前端为什么选择raft-boltdb深入对比Raft存储解决方案为什么选择raft boltdb深入对比Raft存储解决方案 在分布式系统中一致性算法是确保数据可靠性的核心组件而Raft作为最受欢迎的一致性算法之一其TypeSpec编译器深度剖析如何将代码转化为多语言API文档TypeSpec编译器深度剖析如何将代码转化为多语言API文档 你是否还在为手动编写多语言API文档而烦恼TypeSpec编译器通过一次定义自动生成多种语编程语言编译器后端上一篇WeMod专业版功能解锁的模块化解决方案下一篇WeMod增强工具终极指南5步实现高级游戏修改体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表