TL; DR
適用於:
- python和javascript的runtime(基本特指cpython[不是cython!]和Node.js)都裝好了
- 副語言用了一些複雜的包(例如python用了numpy、javascript用了一點Node.js的C++擴展等)
- 對運行效率有要求的話:
- python與javascript之間的交互不能太多,傳遞的對象不要太大、太複雜,最好都是可序列化的對象
- javascript占的比重不過小。否則,python調js的話,啟動Node.js子進程比實際跑程序還慢;js調python的話,因為js跑得快,要花很多時間在等python上。
- 因為IPC大概率會用線程同步輸入輸出,主語言少整啥多進程多、線程之類的並發編程
有庫!有庫!有庫!
python調javascript
- JSPyBridge:
pip install javascript
- 優點:
- 作者還在維護,回issue和更新蠻快的。
- 支持比較新的python和node版本,安裝簡單
- 基本支持互調用,包括綁定或者傳回調函數之類的。
- 缺點:沒有合理的銷毀機制,
import javascript
即視作連接JS端,會初始化所有要用的線程多線程。如果python主程序想重啟對JS的連接,或者主程序用了多進程,想在每個進程都連接一次JS,都很難做到,會容易出錯。
- 優點:
- PyExecJS:
pip install PyExecJS
,比較老的技術文章都推的這個包- 優點: 支持除了Node.js以外的runtime,例如PhantomJS之類的
- 缺點: End of Life,作者停止維護了
javascript調python
(因為與我的項目需求不太符合,所以了解不太多)
- JSPyBridge:
npm i pythonia
- node-python-bridge:
npm install python-bridge
- python-shell:
npm install python-shell
原理
首先,該方法的前提是兩種語言都要有安裝好的runtime,且能通過命令行調用runtime運行文件或一串字符脚本。例如,裝好cpython後我們可以通過python a.py
來運行python程序,裝好Node.js之後我們可以通過node a.js
或者node -e "some script"
等來運行JS程序。
當然,最簡單的情况下,如果我們只需要調用一次副語言,也沒有啥交互(或者最多只有一次交互),那直接找個方法調用CLI就OK了。把給副語言的輸入用stdin或者命令行參數傳遞,讀取命令的輸出當作副語言的輸出。
例如,python可以用subprocess.Popen
,subprocess.call
,subprocess.check_output
或者os.system
之類的,Node.js可以用child_process
裏的方法,exec
或者fork
之類的。需要注意的是,如果需要引用其他包,Node.js需要注意在node_modules
所在的目錄下運行指令,python需要注意設置好PYTHONPATH環境變量。
# Need to set the working directory to the directory where `node_modules` resides if necessary
>>> import subprocess
>>> a, b = 1, 2
>>> print(subprocess.check_output(["node", "-e", f"console.log({a}+{b})"]))
b'3\n'
>>> print(subprocess.check_output(["node", "-e", f"console.log({a}+{b})"]).decode('utf-8'))
3
// Need to set PYTHONPATH in advance if necessary
const a = 1;
const b = 2;
const { execSync } = require("child_process");
console.log(execSync(`python -c "print(${a}+${b})"`));
//<Buffer 33 0a>
console.log(execSync(`python -c "print(${a}+${b})"`).toString());
//3
//
如果有複雜的交互,要傳遞複雜的對象,有的倒還可以序列化,有的根本不能序列化,咋辦?
這基本要利用進程間通信(IPC),通常情况下是用管道(Pipe)。在stdin
,stdout
和stderr
三者之中至少挑一個建立管道。
假設我用stdin
從python向js傳數據,用stderr
接收數據,模式大約會是這樣的:
(以下偽代碼僅為示意,沒有嚴格測試過,實際使用建議直接用庫)
- 新建一個副語言(假設為JS)文件
python-bridge.js
:該文件不斷讀取stdin
並根據發來的信息不同,進行不同處理;同時如果需要打印信息或者傳遞object給主語言,將它們適當序列化後寫入stdout
或者stderr
。process.stdin.on('data', data => {
data.split('\n').forEach(line => {
// Deal with each line
// write message
process.stdout.write(message + "\n");
// deliver object, "$j2p" can be any prefix predefined and agreed upon with the Python side
// just to tell python side that this is an object needs parsing
process.stderr.write("$j2p sendObj "+JSON.stringify(obj)+"\n);
});
}
process.on('exit', () => {
console.debug('** Node exiting');
});
- 在python中,用Popen异步打開一個子進程,並將子進程的之中的至少一個,用管道連接。大概類似於:
cmd = ["node", "--trace-uncaught", f"{os.path.dirname(__file__)}/python-bridge.js"]
kwargs = dict(
stdin=subprocess.PIPE,
stdout=sys.stdout,
stderr=subprocess.PIPE,
)
if os.name == 'nt':
kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW
subproc = subprocess.Popen(cmd, **kwargs)
- 在需要調用JS,或者需要給JS傳遞數據的時候,往
subproc
寫入序列化好的信息,寫入後需要flush
,不然可能會先寫入緩沖區:subproc.stdin.write(f"$p2j call funcName {json.dumps([arg1, arg2])}".encode())
subproc.stdin.flush() # write immediately, not writing to the buffer of the stream
- 對管道化的
stdout
/stderr
,新建一個線程,專門負責讀取傳來的數據並進行處理。是對象的重新轉換成對象,是普通信息的直接打印回主進程的stderr
或者stdout
。def read_stderr():
while subproc.poll() is None:
# when the subprocess is still alive, keep reading
line = self.subproc.stderr.readline().decode('utf-8')
if line.startswith('$j2p'):
# receive special information
_, cmd, line = line.split(' ', maxsplit=2)
if cmd == 'sendObj':
# For example, received an object
obj = json.loads(line)
else:
# otherwise, write to stderr as it is
sys.stderr.write(line) stderr_thread = threading.Thread(target=read_stderr, args=(), daemon=True)
stderr_thread.start()
這裏由於我們的
stdout
沒有建立管道,所以node那邊往stdout
裏打印的東西會直接打印到python的sys.stdout
裏,不用自己處理。 - 由於線程是异步進行的,什麼時候知道一個函數返回的對象到了呢?答案是用線程同步手段,信號量(Semaphore)、條件(Condition),事件(Event)等等,都可以。以python的條件為例:
func_name_cv = threading.Condition()
# use a flag and a result object in case some function has no result
func_name_result_returned = False
func_name_result = None def func_name_wrapper(arg1, arg2):
# send arguments
subproc.stdin.write(f"$p2j call funcName {json.dumps([arg1, arg2])}".encode())
subproc.stdin.flush()
# wait for the result
with func_name_cv:
if not func_name_result_returned:
func_name_cv.wait(timeout=10000)
# when result finally returned, reset the flag
func_name_result_returned = False
return func_name_result
同時,需要在讀stderr的線程
read_stderr
裏解除對這個返回值的阻塞。需要注意的是,如果JS端因為意外而退出了,subproc
也會死掉,這時候也要記得取消主線程中的阻塞。def read_stderr():
while subproc.poll() is None:
# when the subprocess is still alive, keep reading
# Deal with a line
line = self.subproc.stderr.readline().decode('utf-8')
if line.startswith('$j2p'):
# receive special information
_, cmd, line = line.split(' ', maxsplit=2)
if cmd == 'sendObj':
# acquire lock here to ensure the editing of func_name_result is mutex
with func_name_cv:
# For example, received an object
func_name_result = json.loads(line)
func_name_result_returned = True
# unblock func_name_wrapper when receiving the result
func_name_cv.notify()
else:
# otherwise, write to stderr as it is
sys.stderr.write(line)
# If subproc is terminated (mainly due to error), still need to unblock func_name_wrapper
func_name_cv.notify()
當然這是比較簡單的版本,由於對JS的調用基本都是線性的,所以可以知道只要得到一個object的返回,那就一定是
func_name_wrapper
對應的結果。如果函數多起來的話,情况會更複雜。 - 如果想取消對JS的連接,首先應該先關閉子進程,然後等待讀
stdout
/stderr
的線程自己自然退出,最後一定不要忘記關閉管道。並且這三步的順序不能換,如果先關了管道,讀線程會因為stdout
/stderr
已經關了而出錯。subproc.terminate()
stderr_thread.join()
subproc.stdin.close()
subproc.stderr.close()
如果是通過這種原理javascript調用python,方法也差不多,javascript方是Node.js的話,用的是child_process
裏的指令。
優點
- 只需要正常裝好兩方的runtime就能實現交互,運行環境相對比較好配。
- 只要python方和javascript方在各自的runtime裏正常運行沒問題,那麼連上之後運行也基本不會有問題。(除非涉及並發)
- 對兩種語言的所有可用的擴展包基本都能支持。
缺點
- 當python與JavaScript交互頻繁,且交互的信息都很大的時候,可能會很影響程序效率。因為僅僅通過最多3個管道混合處理普通要打印的信息、python與js交互的對象、函數調用等,通信開銷很大。
- 要另起一個子進程運行副語言的runtime,會花一定時間和空間開銷。
Python與Javascript相互調用超詳細講解(2022年1月最新)(一)基本原理 Part 1 - 通過子進程和進程間通信(IPC)的更多相關文章
- IOS Object和javaScript相互調用
在IOS開發中有時會用到Object和javaScript相互調用,詳細過程例如以下: 1. Object中運行javascript代碼,這個比較簡單,蘋果提供了非常好的方法 - (NSString ...
- Hybrid App開發模式中, IOS/Android 和 JavaScript相互調用方式
IOS:Objective-C 和 JavaScript 的相互調用 iOS7以前,iOS SDK 並沒有原生提供 js 調用 native 代碼的 API.但是 UIWebView 的一個 dele ...
- Android和JavaScript相互調用的方法
轉載地址:http://www.jb51.net/article/77206.htm 這篇文章主要介紹了Android和JavaScript相互調用的方法,實例分析了Android的WebView執行 ...
- android與javascript相互調用
下面這一節來介紹android和javascript是怎麼相互調用的,這樣我們的UI界面設計起來就簡單多了,而且UI設計起來也可以跨平臺.現在有好多web app前臺框架了,比如sencha和jque ...
- Python 基礎學習筆記(超詳細版)
1.變量 python中變量很簡單,不需要指定數據類型,直接使用等號定義就好.python變量裏面存的是內存地址,也就是這個值存在內存裏面的哪個地方,如果再把這個變量賦值給另一個變量,新的變量通過之前 ...
- 開源項目ScriptGate,Delphi與JavaScript相互調用的神器
ScriptGate是一個實現TWebBrowser上的JavaScript和Delphi代碼相互調用的庫,具體在這裏:https://bitbucket.org/freeonterminate/sc ...
- python - 函數的相互調用 及 變量的作用域
# -*- coding:utf-8 -*- '''@project: [email protected]: [email protected]: study_函數的相互調用及變量的作用域[email protected]: PyCharm C ...
- python模塊--如何相互調用自己寫的模塊
一.模塊相互調用同級目錄調用時的兩種方法 import module print(module.add(3,8)) from module import add print(add(2,4)) 同級目 ...
- UIWebView與JavaScript相互調用
UIWebView與JavaScript的那些事兒 UIWebView是IOS SDK中渲染網面的控件,在顯示網頁的時候,我們可以hack網頁然後顯示想顯示的內容.其中就要用到javascript的知 ...
- Keras代碼超詳細講解LSTM實現細節
1.首先我們了解一下keras中的Embedding層:from keras.layers.embeddings import Embedding: Embedding參數如下: 輸入尺寸:(batc ...
隨機推薦
- SQL索引添加
EXPLAIN select * from view_agzsaycommont where id >0
- [deviceone開發]-拼圖小遊戲
一.簡介 九宮格小遊戲,可從本地圖庫載入一張圖片,填充到9個ImageView,另涉及Timer計時.圖庫控件. 每個格子都是相同的控件,動態添加到首頁中的,在初始化後,響應touch事件,之後通過多 ...
- 修改xubuntu14.04(同適用ubuntu)下Eclipse默認的黑色注釋
終端輸入:sudo gedit /usr/share/themes/Ambiance/gtk-2.0/gtkrc 第一行將看到如下內容: gtk-color-scheme = "base_c ...
- JSP的7個動作指令
動作指令與編譯指令不同,編譯指令是通知Servlet引擎的處理信息,而動作指令知識運行時的動作.編譯指令在將JSP編譯成Servlet時起作用,而處理指令通常可替換成JSP脚本,它知識JSP脚本的標准 ...
- Oracle 分析函數 "ORA-30485: 在窗口說明中丟失 ORDER BY 錶達式"
跟順序有關的幾個分析函數row_number.rank.dense_rank.lead和lag的over窗口裏,都必須有order_by_clause.其他幾個如:first_value.last_v ...
- wsdlLocation可以寫成項目的相對路勁嗎
如果客戶端的代碼使用wsdl生成的話,這個地址是從wsdl描述的<service>裏的<location>獲取的,如果開發過程中服務地址換了,那只能手工來修改了,好像只有一個地 ...
- Google的Java經常使用類庫 Guava
Guava 中文是石榴的意思,該項目是 Google 的一個開源項目,包括很多 Google 核心的 Java 經常使用庫. 1. 基本工具 [Basic utilities] 讓使用Java ...
- Insertion Sort List Leetcode
Sort a linked list using insertion sort. 這個題我巧妙的設置了一個臨時頭結點 class Solution { public: ListNode* insert ...
- DOCKER 從入門到放弃(三)
使用docker create [image-name] 創建一個容器 創建一個nginx鏡像的容器,由於沒有指定各項參數,容器實用默認參數,創建後並不會啟動,並將容器的ID輸出到終端,如果本地沒有鏡 ...
- animate.css的使用
前面的話 animate.css是一個使用CSS3的animation制作的動畫效果的CSS集合,裏面預設了很多種常用的動畫,且使用非常簡單.本文將詳細介紹animate.css的使用 引入 anim ...