AI智能
改变未来

Python与Javascript相互调用超详细讲解(2022年1月最新)(二)基本原理Part 2 – 通过翻译/解释副语言

[TOC]首先要明白的是,javascript和python都是解释型语言,它们的运行是需要具体的runtime的。

  • Python: 我们最常安装的Python其实是cpython,它有一个基于C的解释器。除此之外还有像pypy这种解释器,等等。基本上,不使用cpython作为python的runtime的最大问题就是通过pypi安装的那些外来包,甚至有一些cpython自己的原生包(像
    collections

    这种)都用不了。

  • JavaScript: 常见的运行引擎有google的V8,Mozilla的SpiderMonkey等等,这些引擎可以把JavaScript代码转换成机器码执行。基于这些基础的运行引擎,我们可以开发支持JS的浏览器(比如Chrome的JS运行引擎就是V8);也可以开发功能更多的JS运行环境,比如Node.js,相当于我们不需要一个浏览器,也可以跑JS代码。有了Node.js,JS包管理也变得方便许多,如果我们想把开发好的Node.js包再给浏览器用,就需要把基于Node.js的源代码编译成浏览器支持的JS代码。

在本文叙述中,假定:

  • 主语言: 最终的主程序所用的语言
  • 副语言: 不是主语言的另一种语言

例如,python调用js,python就是主语言,js是副语言

TL; DR

适用于:

  1. 希望自己的项目不要依赖副语言的runtime。换句话说,python调javascript的时候想只装一个python,javascript调用python的时候也不想装python。
  2. 副语言基本是纯代码,没有用其他底层实现,也没有引用太多复杂的包。比如,javascript调python,python用了numpy,打咩;python调用javascript,javascript用了一些C++的Node插件,打咩。
  3. 主语言和副语言交互很频繁,且交互的对象很难序列化(比如函数,复杂的类之类的)。
  4. 如果是python调用javascript,对运行效率不要有太多要求。

有库!有库!有库!

python调javascript

  • Js2Py:
    pip install js2py

    优点:

      目前为止最好的JavaScript to python翻译器了,除了依赖底层C++实现的包的Node.js包,应该基本都可以翻译,翻译得还蛮快。
    1. 有翻译成python的中间结果,翻译完之后以后再import同一个javascript包的话,会直接调用python版本。
    2. 看起来作者最近还在维护。
  • 缺点
      Javascript部分代码也比较复杂的时候,会比直接在Node.js里运行这部分js code慢很多。虽然这个库里也实现了一个python的javascript解释器,能比翻译器快,但目前它实现的版本里只支持运行一段给定的javascript代码,两种语言的交互部分的优势就没了,期待作者后面加更多新feature吧。
    1. 把它放到自己的应用场景的时候,总有一些小问题,可能需要稍微修改源码优化。比如:对Node.js包的安装-翻译-调用这部分的pipeline做得不是特别好,影响程序效率。比如说,它翻译之前要先把基于ES6的包用
      babel

      转成ES5,但像

      babel

      这样的依赖项被安装在了临时目录里,导致每次打开python进程执行翻译的时候都要安装一次。不过,同一个python进程里翻译多次不会重复安装,多个python进程里多次引用同一个包也不会,后面几次直接会导入翻译好的python版本。

    2. console.log(a, b, c)

      翻译之后只会打印

      a

      ,可能需要改一下

      js2py

      里console的实现。

javascript调python

  • Brython:是python3的javascript解释器,主要目的是让浏览器可以跑python,没太用过。
  • PScript: 把python代码翻译成javascript代码,但正如其包名所说,只能翻译纯python的简单脚本(换句话说,只能翻译python的一个子集)。
  • Transcrypt:python to javascript翻译器,也只能翻译一个子集。优点: 功能比较全的javascript to python翻译器。
      虽然只能翻译python语言的一个子集,但功能比

      PScript

      还是要全不少的。

    1. 翻译的时候还有许多宏可以决定要如何翻译。
  • 缺点:在我删光了我的python代码里对
    numpy

    的依赖后,发现

    collections

    包也翻译不了,遂放弃此路。

原理

由于python和javascript都不是强类型的语言,因此一定程度上也是可以互译的。其次,由于它们都属于解释型语言,因此也可以用主语言实现一个副语言的解释器,最后统一在主语言的runtime下运行。更深入的原理涉及一些编译原理的知识,咱也不太懂,就只按自己的粗浅理解简单讲一下,不详细展开了,有不对的地方欢迎指正。

这种打不过就加入(……)的方式有两种实现方法,一种是直接翻译成主语言,另一种是用主语言写一个副语言的解释器。翻译和解释的区别在哪儿呢?简单举个例子,假如你是个中文母语者:

  • 翻译: 有人跟你说“Go and fetch a book”(副语言),你先在脑中翻译成中文(主语言):“去拿本书”,哦……懂了(解析主语言),然后去拿书了(在主语言runtime下执行指令)
  • 解释: 有人跟你说“Go and fetch a book”(副语言),你直接懂了(解析副语言),然后就去拿书了(在主语言runtime下运行)。(这不是我们学外语的理想状态嘛!)

顺便一提,在这个例子里,编译型语言就像是,有人早上跟你说“Go and fetch a book”,经过漫长的训练(编译和链接),然后你就变成了一个只会拿书的机器(可执行文件),这一天里别人只要叫你(运行Run),你就会自动去拿书,不需要再跟你说“Go and fetch a book”(源代码)。

解析副语言

不管是翻译还是解释,首先的第一步应该都是要解析副语言。一般情况下可以选择解析出副语言的抽象语法树(AST),然后根据抽象语法树决定如何翻译,或者如何执行程序。

以副语言为js,即python调用javascript为例。

题外话: javascript有个专门的包esprima.js专门解析js code的AST。Js2Py的作者把它人工翻译成了Python(强!):pyjsparser,成功为js翻译成python打好了基石。

这是

pyjsparser

解析出来的一个AST:

>>> from pyjsparser import parse>>> parse(\'const abc = "Hello!"\\nconst c = abc\'){"type": "Program","body": [{"type": "VariableDeclaration","declarations": [{"type": "VariableDeclarator","id": {"type": "Identifier","name": "abc"},"init": {"type": "Literal","value": "Hello!","raw": "\\"Hello!\\""}}],"kind": "const"},{"type": "VariableDeclaration","declarations": [{"type": "VariableDeclarator","id": {"type": "Identifier","name": "c"},"init": {"type": "Identifier","name": "abc"}}],"kind": "const"}]}

我们就可以遍历这个AST,然后智能地翻译出等价的python程序。比如第一层是程序层,

body

只有一个元素,说明程序只有两行指令,都是定义变量(

"VariableDeclaration"

)。那么怎么定义的呢?从

declarations

可知,每个指令只定义了一个变量,且类型都是

const

常量(说明后面不允许改变值)。我们知道定义变量需要知道:(1)变量的名字(2)有无初始值。第一个变量的名字(

id

)是

abc

,有初始值(

init

),是一个字面量(

Literal

),转成python的字面量之后值在

value

字段里,声明时的原始代码在

raw

里。举个例子,可以简单地翻译为

abc = Constant("Hello")

,其中

Constant

是特地为JS定义的常量类型。第二个变量的名字(

id

)是

c

,有初始值(

init

),是通过变量定义的,变量的标识符(

Identifier

)为

abc

。同理,也可以翻译为

c = Constant(abc)

如果是解释器的话,最后python中只要有2个常量,名字分别是

abc

c

,且值都为

"Hello"

就行,不需要特地关心如何得到这个结果的,甚至于可能,我也不需要实际执行两次赋值。

当然实际使用的时候,翻译和解释的实现可能比这复杂得多,但基本都有前人已经做过相关的工作了。

优点

  1. 只需要装主语言的runtime。对于想把自己的项目作为主语言的插件分发,不要求目标机器一定得有副语言runtime的情况比较适用。举例子的话,比如想直接在线尝试python,可以用python到js的翻译库或者解释器,这样可以不需要装python也可以运行python,对于把python嵌入浏览器可能有不错的效果。
  2. 无缝交互!因为最后都在一个体系里了,再也不用为了传递函数和复杂类对象而烦恼。

缺点

  1. 副语言能支持的包有限。比如python转成js的时候,甚至很多原生包(如
    collections

    都用不了,更别说

    numpy

    之类的了);js转python还好,大概只要不是底层由C++实现的Node.js包的话,应该都可以翻译成功。这基本是通过这种原理javascript调用python的瓶颈

  2. 两种语言运行效率不同。由于Javascript运行速度比python快多了,如果是python调用javascript,在整个项目javascript比重很大,且运行效率又很重要的时候,会慢到不能接受(……),个人觉得这是python通过该原理调用javascript的最大瓶颈
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Python与Javascript相互调用超详细讲解(2022年1月最新)(二)基本原理Part 2 – 通过翻译/解释副语言