整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          Python與Javascript相互調(diào)用超詳細(xì)講解

          Python與Javascript相互調(diào)用超詳細(xì)講解(2022年1月最新)

          先要明白的是,javascript和python都是解釋型語言,它們的運(yùn)行是需要具體的runtime的。

          • Python: 我們最常安裝的Python其實(shí)是cpython,就是基于C來運(yùn)行的。除此之外還有像pypy這樣的自己寫了解釋器的,transcrypt這種轉(zhuǎn)成js之后再利用js的runtime的?;旧希皇褂胏python作為python的runtime的最大問題就是通過pypi安裝的那些外來包,甚至有一些cpython自己的原生包(像 collections 這種)都用不了。
          • JavaScript: 常見的運(yùn)行引擎有g(shù)oogle的V8,Mozilla的SpiderMonkey等等,這些引擎會把JavaScript代碼轉(zhuǎn)換成機(jī)器碼執(zhí)行。基于這些基礎(chǔ)的運(yùn)行引擎,我們可以開發(fā)支持JS的瀏覽器(比如Chrome的JS運(yùn)行引擎就是V8);也可以開發(fā)功能更多的JS運(yùn)行環(huán)境,比如Node.js,相當(dāng)于我們不需要一個(gè)瀏覽器,也可以跑JS代碼。有了Node.js,JS包管理也變得方便許多,如果我們想把開發(fā)好的Node.js包再給瀏覽器用,就需要把基于Node.js的源代碼編譯成瀏覽器支持的JS代碼。

          在本文敘述中,假定:

          • 主語言: 最終的主程序所用的語言
          • 副語言: 不是主語言的另一種語言

          例如,python調(diào)用js,python就是主語言,js是副語言

          TL; DR

          適用于:

          1. python和javascript的runtime(基本特指cpython[不是cython!]和Node.js)都裝好了
          2. 副語言用了一些復(fù)雜的包(例如python用了numpy、javascript用了一點(diǎn)Node.js的C++擴(kuò)展等)
          3. 對運(yùn)行效率有要求的話:
          4. python與javascript之間的交互不能太多,傳遞的對象不要太大、太復(fù)雜,最好都是可序列化的對象
          5. javascript占的比重不過小。否則,python調(diào)js的話,啟動Node.js子進(jìn)程比實(shí)際跑程序還慢;js調(diào)python的話,因?yàn)閖s跑得快,要花很多時(shí)間在等python上。
          6. 因?yàn)镮PC大概率會用線程同步輸入輸出,主語言少整啥多進(jìn)程多、線程之類的并發(fā)編程

          有庫!有庫!有庫!

          python調(diào)javascript

          • JSPyBridge : pip install javascript優(yōu)點(diǎn):作者還在維護(hù),回issue和更新蠻快的。支持比較新的python和node版本,安裝簡單基本支持互調(diào)用,包括綁定或者傳回調(diào)函數(shù)之類的。缺點(diǎn) :沒有合理的銷毀機(jī)制, import javascript 即視作連接JS端,會初始化所有要用的線程多線程。如果python主程序想重啟對JS的連接,或者主程序用了多進(jìn)程,想在每個(gè)進(jìn)程都連接一次JS,都很難做到,會容易出錯(cuò)。
          • PyExecJS : pip install PyExecJS ,比較老的技術(shù)文章都推的這個(gè)包優(yōu)點(diǎn): 支持除了Node.js以外的runtime,例如PhantomJS之類的缺點(diǎn): End of Life,作者停止維護(hù)了

          javascript調(diào)python

          (因?yàn)榕c我的項(xiàng)目需求不太符合,所以了解的不太多)

          • JSPyBridge : npm i pythonia
          • node-python-bridge : npm install python-bridge
          • python-shell : npm install python-shell

          原理

          首先,該方法的前提是兩種語言都要有安裝好的runtime,且能通過命令行調(diào)用runtime運(yùn)行文件或一串字符腳本。例如,裝好cpython后我們可以通過 python a.py 來運(yùn)行python程序,裝好Node.js之后我們可以通過 node a.js 或者 node -e "some script" 等來運(yùn)行JS程序。

          當(dāng)然,最簡單的情況下,如果我們只需要調(diào)用一次副語言,也沒有啥交互(或者最多只有一次交互),那直接找個(gè)方法調(diào)用CLI就OK了。把給副語言的輸入用stdin或者命令行參數(shù)傳遞,讀取命令的輸出當(dāng)作副語言的輸出。

          例如,python可以用 subprocess.Popen , subprocess.call , subprocess.check_output 或者 os.system 之類的,Node.js可以用 child_process 里的方法, exec 或者 fork 之類的。 需要注意的是,如果需要引用其他包,Node.js需要注意在 node_modules 所在的目錄下運(yùn)行指令,python需要注意設(shè)置好PYTHONPATH環(huán)境變量。

          # 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'3\n'
          >>> print(subprocess.check_output(["node", "-e", f"console.log({a}+)"]).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}+$)"`));
          //<Buffer 33 0a>
          console.log(execSync(`python -c "print(${a}+$)"`).toString());
          //3
          //

          如果有復(fù)雜的交互,要傳遞復(fù)雜的對象,有的倒還可以序列化,有的根本不能序列化,咋辦?

          這基本要利用 進(jìn)程間通信(IPC) ,通常情況下是用 管道(Pipe) 。在 stdin , stdoutstderr 三者之中至少挑一個(gè)建立管道。

          假設(shè)我用 stdin 從python向js傳數(shù)據(jù),用 stderr 接收數(shù)據(jù),模式大約會是這樣的:

          (以下偽代碼僅為示意,沒有嚴(yán)格測試過,實(shí)際使用建議直接用庫)

          1. 新建一個(gè)副語言(假設(shè)為JS)文件 python-bridge.js :該文件不斷讀取 stdin 并根據(jù)發(fā)來的信息不同,進(jìn)行不同的處理;同時(shí)如果需要打印信息或者傳遞object給主語言,將它們適當(dāng)序列化后寫入 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'); });
          2. 在python中,用Popen異步打開一個(gè)子進(jìn)程,并將子進(jìn)程的之中的至少一個(gè),用管道連接。大概類似于: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)
          3. 在需要調(diào)用JS,或者需要給JS傳遞數(shù)據(jù)的時(shí)候,往 subproc 寫入序列化好的信息,寫入后需要 flush ,不然可能會先寫入緩沖區(qū):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
          4. 對管道化的 stdout / stderr ,新建一個(gè)線程,專門負(fù)責(zé)讀取傳來的數(shù)據(jù)并進(jìn)行處理。是對象的重新轉(zhuǎn)換成對象,是普通信息的直接打印回主進(jìn)程的 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 里,不用自己處理。
          5. 由于線程是異步進(jìn)行的,什么時(shí)候知道一個(gè)函數(shù)返回的對象到了呢?答案是用線程同步手段,信號量(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同時(shí),需要在讀stderr的線程 read_stderr 里解除對這個(gè)返回值的阻塞。需要注意的是,如果JS端因?yàn)橐馔舛顺隽耍?subproc 也會死掉, 這時(shí)候也要記得取消主線程中的阻塞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()當(dāng)然這是比較簡單的版本,由于對JS的調(diào)用基本都是線性的,所以可以知道只要得到一個(gè)object的返回,那就一定是 func_name_wrapper 對應(yīng)的結(jié)果。如果函數(shù)多起來的話,情況會更復(fù)雜。
          6. 如果想 取消對JS的連接 ,首先應(yīng)該先關(guān)閉子進(jìn)程,然后等待讀 stdout / stderr 的線程自己自然退出,最后 一定不要忘記關(guān)閉管道 。并且 這三步的順序不能換 ,如果先關(guān)了管道,讀線程會因?yàn)?stdout / stderr 已經(jīng)關(guān)了而出錯(cuò)。subproc.terminate() stderr_thread.join() subproc.stdin.close() subproc.stderr.close()

          如果是通過這種原理javascript調(diào)用python,方法也差不多,javascript方是Node.js的話,用的是 child_process 里的指令。

          優(yōu)點(diǎn)

          1. 只需要正常裝好兩方的runtime就能實(shí)現(xiàn)交互,運(yùn)行環(huán)境相對比較好配。
          2. 只要python方和javascript方在各自的runtime里正常運(yùn)行沒問題,那么連上之后運(yùn)行也基本不會有問題。(除非涉及并發(fā))
          3. 對兩種語言的所有可用的擴(kuò)展包基本都能支持。

          缺點(diǎn)

          1. 當(dāng)python與JavaScript交互頻繁,且交互的信息都很大的時(shí)候,可能會很影響程序效率。因?yàn)閮H僅通過最多3個(gè)管道混合處理普通要打印的信息、python與js交互的對象、函數(shù)調(diào)用等,通信開銷很大。
          2. 要另起一個(gè)子進(jìn)程運(yùn)行副語言的runtime,會花一定時(shí)間和空間開銷。

          說明

          最近在項(xiàng)目上有個(gè)移動端(uni-app)的需求,就是要在移動端APP上的vue頁面中通過web-view組件來調(diào)用html頁面,并且要實(shí)現(xiàn)在html頁面中可以點(diǎn)擊一個(gè)元素來調(diào)用vue頁面中uni的API(掃碼接口),同時(shí)也可以在vue頁面中也可以調(diào)用html頁面中的js函數(shù)并進(jìn)行傳參。

          使用環(huán)境

          1. HBuilderX版本:2.8.11.20200907

          2. V3編譯器

          html頁面調(diào)用vue頁面中uni的API

          引用依賴的文件

          在 web-view 加載的 HTML 中調(diào)用 uni 的 API,需要在 HTML 中引用必要的 JS-SDK

          <script type="text/javascript" src="//js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.0.1.52.js"></script>

          注意:這些 JS 文件是在 web-view 加載的那個(gè) HTML 文件中引用的,而不是 uni-app 項(xiàng)目中的文件。

          監(jiān)聽 web-view 的 message 事件

          監(jiān)聽 web-view 組件的 message 事件,然后在事件回調(diào)的 event.detail.data 中接收傳遞過來的消息。

          <template>  
          
              <view>  
          
                  <web-view src="http://192.168.1.1:3000/test.html" @message="handleMessage"></web-view>  
          
              </view>  
          
          </template>  
          
          <script>  
          
              export default {  
          
                  methods: {  
          
                      handleMessage(evt) {  
          
                          console.log('接收到的消息:' + JSON.stringify(evt.detail.data));  
          
                      }  
          
                  }  
          
              }  
          
          </script>

          調(diào)用的時(shí)機(jī)

          在引入上面的依賴文件后,需要在HTML中監(jiān)聽UniAppJSBridgeReady,事件觸發(fā)后,

          才能安全調(diào)用uni的API。

          <script type="text/javascript" src="//js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.0.1.52.js"></script>
          
          <script>
          
              document.querySelector('.btn-list').addEventListener('click', function(evt) {  
          
                  var target=evt.target;  
          
                  if (target.tagName==='BUTTON') {  
          
                      var action=target.getAttribute('data-action');  
          
                      if(action==='navigateTo') {  
          
          				uni.postMessage({  
          
          					data: {  
          
          						action: 'postMessage'  
          
          					}  
          
          				});   
          
                      }  
          
                  }  
          
              }); 
          
          </script>

          上面代碼的意思就是在html頁面中點(diǎn)擊按鈕列表中的某個(gè)按鈕,

          觸發(fā)了uni.postMessage接口,進(jìn)而調(diào)用了vue頁面methods中的handleMessage方法,

          并將參數(shù)data傳給了vue頁面。

          在vue頁面中調(diào)用html頁面的js函數(shù)

          示例代碼:

          var currentWebview=this.$mp.page.$getAppWebview().children()[0];
          currentWebview.evalJS("htmljsfuc('"+res.result+"')");

          其中的htmljsfuc就是要在html頁面中定義的js函數(shù)。

          完整代碼示例:

          4 種方式可以在 HTML 中引入 CSS。其中有 2 種方式是在 HTML 文件中直接添加 CSS 代碼,另外兩種是引入 外部 CSS 文件。下面我們就來看看這些方式和它們的優(yōu)缺點(diǎn)。

          內(nèi)聯(lián)方式

          內(nèi)聯(lián)方式指的是直接在 HTML 標(biāo)簽中的 style 屬性中添加 CSS。

          示例:

          <div style="background: red"></div>
          

          這通常是個(gè)很糟糕的書寫方式,它只能改變當(dāng)前標(biāo)簽的樣式,如果想要多個(gè) <div> 擁有相同的樣式,你不得不重復(fù)地為每個(gè) <div> 添加相同的樣式,如果想要修改一種樣式,又不得不修改所有的 style 中的代碼。很顯然,內(nèi)聯(lián)方式引入 CSS 代碼會導(dǎo)致 HTML 代碼變得冗長,且使得網(wǎng)頁難以維護(hù)。

          嵌入方式

          嵌入方式指的是在 HTML 頭部中的 <style> 標(biāo)簽下書寫 CSS 代碼。

          示例:

          <head>
           <style>
           .content {
           background: red;
           }
           </style>
          </head>
          

          嵌入方式的 CSS 只對當(dāng)前的網(wǎng)頁有效。因?yàn)?CSS 代碼是在 HTML 文件中,所以會使得代碼比較集中,當(dāng)我們寫模板網(wǎng)頁時(shí)這通常比較有利。因?yàn)椴榭茨0宕a的人可以一目了然地查看 HTML 結(jié)構(gòu)和 CSS 樣式。因?yàn)榍度氲?CSS 只對當(dāng)前頁面有效,所以當(dāng)多個(gè)頁面需要引入相同的 CSS 代碼時(shí),這樣寫會導(dǎo)致代碼冗余,也不利于維護(hù)。

          鏈接方式

          鏈接方式指的是使用 HTML 頭部的 <head> 標(biāo)簽引入外部的 CSS 文件。

          示例:

          <head>
           <link rel="stylesheet" type="text/css" href="style.css">
          </head>
          

          這是最常見的也是最推薦的引入 CSS 的方式。使用這種方式,所有的 CSS 代碼只存在于單獨(dú)的 CSS 文件中,所以具有良好的可維護(hù)性。并且所有的 CSS 代碼只存在于 CSS 文件中,CSS 文件會在第一次加載時(shí)引入,以后切換頁面時(shí)只需加載 HTML 文件即可。

          導(dǎo)入方式

          導(dǎo)入方式指的是使用 CSS 規(guī)則引入外部 CSS 文件。

          示例:

          <style>
           @import url(style.css);
          </style>
          

          比較鏈接方式和導(dǎo)入方式

          鏈接方式(下面用 link 代替)和導(dǎo)入方式(下面用 @import 代替)都是引入外部的 CSS 文件的方式,下面我們來比較這兩種方式,并且說明為什么不推薦使用 @import。

          • link 屬于 HTML,通過 <link> 標(biāo)簽中的 href 屬性來引入外部文件,而 @import 屬于 CSS,所以導(dǎo)入語句應(yīng)寫在 CSS 中,要注意的是導(dǎo)入語句應(yīng)寫在樣式表的開頭,否則無法正確導(dǎo)入外部文件;
          • @import 是 CSS2.1 才出現(xiàn)的概念,所以如果瀏覽器版本較低,無法正確導(dǎo)入外部樣式文件;
          • 當(dāng) HTML 文件被加載時(shí),link 引用的文件會同時(shí)被加載,而 @import 引用的文件則會等頁面全部下載完畢再被加載;

          小結(jié):我們應(yīng)盡量使用 <link> 標(biāo)簽導(dǎo)入外部 CSS 文件,避免或者少用使用其他三種方式。


          主站蜘蛛池模板: 精品一区高潮喷吹在线播放| 亚洲欧洲一区二区三区| 国产伦精品一区二区三区免费迷| 一区二区三区在线播放| 日本在线视频一区二区| 91国在线啪精品一区| 国产激情一区二区三区在线观看 | 亚洲av无码一区二区三区四区| 在线精品国产一区二区三区| 国产一区二区精品在线观看| 亚州AV综合色区无码一区| 日韩电影一区二区| 国产在线aaa片一区二区99 | 中文字幕一区精品| 内射白浆一区二区在线观看| 无码一区二区三区爆白浆| 欲色aV无码一区二区人妻 | 亚洲国产欧美国产综合一区| 91视频一区二区| 精品福利一区二区三| 国产一在线精品一区在线观看| 国产精品成人一区二区| 国产A∨国片精品一区二区| 国产AV一区二区精品凹凸| 国产一区二区三区内射高清| 秋霞电影网一区二区三区| 午夜无码一区二区三区在线观看| 日韩三级一区二区三区| 免费人人潮人人爽一区二区| 国产伦精品一区二区三区视频猫咪 | 国产一区二区在线观看| 国产成人精品日本亚洲专一区 | 国产精品99无码一区二区| 亚洲一区二区高清| 日本在线一区二区| 亚洲乱色熟女一区二区三区蜜臀| 性色AV一区二区三区天美传媒| 无码aⅴ精品一区二区三区| 精品女同一区二区三区在线| 一区二区在线播放视频| 国产亚洲综合一区柠檬导航|