天天看點

Python與Javascript互相調用超詳細講解(2022年1月最新)(二)基本原理Part 2 - 通過翻譯/解釋副語言

目錄

  • TL; DR
    • python調javascript
    • javascript調python
  • 原理
    • 解析副語言
  • 優點
  • 缺點

首先要明白的是,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是副語言
Python與Javascript互相調用超詳細講解(2022年1月最新)(二)基本原理Part 2 - 通過翻譯/解釋副語言

适用于:

  1. 希望自己的項目不要依賴副語言的runtime。換句話說,python調javascript的時候想隻裝一個python,javascript調用python的時候也不想裝python。
  2. 副語言基本是純代碼,沒有用其他底層實作,也沒有引用太多複雜的包。比如,javascript調python,python用了numpy,打咩;python調用javascript,javascript用了一些C++的Node插件,打咩。
  3. 主語言和副語言互動很頻繁,且互動的對象很難序列化(比如函數,複雜的類之類的)。
  4. 如果是python調用javascript,對運作效率不要有太多要求。

有庫!有庫!有庫!

  • Js2Py:

    pip install js2py

    • 優點:
      1. 目前為止最好的JavaScript to python翻譯器了,除了依賴底層C++實作的包的Node.js包,應該基本都可以翻譯,翻譯得還蠻快。
      2. 有翻譯成python的中間結果,翻譯完之後以後再import同一個javascript包的話,會直接調用python版本。
      3. 看起來作者最近還在維護。
    • 缺點:
      1. Javascript部分代碼也比較複雜的時候,會比直接在Node.js裡運作這部分js code慢很多。雖然這個庫裡也實作了一個python的javascript解釋器,能比翻譯器快,但目前它實作的版本裡隻支援運作一段給定的javascript代碼,兩種語言的互動部分的優勢就沒了,期待作者後面加更多新feature吧。
      2. 把它放到自己的應用場景的時候,總有一些小問題,可能需要稍微修改源碼優化。比如:
        • 對Node.js包的安裝-翻譯-調用這部分的pipeline做得不是特别好,影響程式效率。比如說,它翻譯之前要先把基于ES6的包用

          babel

          轉成ES5,但像

          babel

          這樣的依賴項被安裝在了臨時目錄裡,導緻每次打開python程序執行翻譯的時候都要安裝一次。不過,同一個python程序裡翻譯多次不會重複安裝,多個python程序裡多次引用同一個包也不會,後面幾次直接會導入翻譯好的python版本。
        • console.log(a, b, c)

          翻譯之後隻會列印

          a

          ,可能需要改一下

          js2py

          裡console的實作。

  • Brython:是python3的javascript解釋器,主要目的是讓浏覽器可以跑python,沒太用過。
  • PScript: 把python代碼翻譯成javascript代碼,但正如其包名所說,隻能翻譯純python的簡單腳本(換句話說,隻能翻譯python的一個子集)。
  • Transcrypt:python to javascript翻譯器,也隻能翻譯一個子集。
    • 優點: 功能比較全的javascript to python翻譯器。
      1. 雖然隻能翻譯python語言的一個子集,但功能比

        PScript

        還是要全不少的。
      2. 翻譯的時候還有許多宏可以決定要如何翻譯。
    • 缺點:在我删光了我的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”(源代碼)。
Python與Javascript互相調用超詳細講解(2022年1月最新)(二)基本原理Part 2 - 通過翻譯/解釋副語言

不管是翻譯還是解釋,首先的第一步應該都是要解析副語言。一般情況下可以選擇解析出副語言的抽象文法樹(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的最大瓶頸。