整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

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

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

          • Python: 我們最常安裝的Python其實是cpython,就是基于C來運行的。除此之外還有像pypy這樣的自己寫了解釋器的,transcrypt這種轉成js之后再利用js的runtime的。基本上,不使用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. python和javascript的runtime(基本特指cpython[不是cython!]和Node.js)都裝好了
          2. 副語言用了一些復雜的包(例如python用了numpy、javascript用了一點Node.js的C++擴展等)
          3. 對運行效率有要求的話:
          4. python與javascript之間的交互不能太多,傳遞的對象不要太大、太復雜,最好都是可序列化的對象
          5. javascript占的比重不過小。否則,python調js的話,啟動Node.js子進程比實際跑程序還慢;js調python的話,因為js跑得快,要花很多時間在等python上。
          6. 因為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.Popensubprocess.callsubprocess.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) 。在 stdinstdoutstderr 三者之中至少挑一個建立管道。

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

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

          1. 新建一個副語言(假設為JS)文件 python-bridge.js :該文件不斷讀取 stdin 并根據發來的信息不同,進行不同的處理;同時如果需要打印信息或者傳遞object給主語言,將它們適當序列化后寫入 stdout 或者 stderrprocess.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異步打開一個子進程,并將子進程的之中的至少一個,用管道連接。大概類似于: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. 在需要調用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
          4. 對管道化的 stdout / stderr ,新建一個線程,專門負責讀取傳來的數據并進行處理。是對象的重新轉換成對象,是普通信息的直接打印回主進程的 stderr 或者 stdoutdef 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. 由于線程是異步進行的,什么時候知道一個函數返回的對象到了呢?答案是用線程同步手段,信號量(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 對應的結果。如果函數多起來的話,情況會更復雜。
          6. 如果想 取消對JS的連接 ,首先應該先關閉子進程,然后等待讀 stdout / stderr 的線程自己自然退出,最后 一定不要忘記關閉管道 。并且 這三步的順序不能換 ,如果先關了管道,讀線程會因為 stdout / stderr 已經關了而出錯。subproc.terminate() stderr_thread.join() subproc.stdin.close() subproc.stderr.close()

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

          優點

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

          缺點

          1. 當python與JavaScript交互頻繁,且交互的信息都很大的時候,可能會很影響程序效率。因為僅僅通過最多3個管道混合處理普通要打印的信息、python與js交互的對象、函數調用等,通信開銷很大。
          2. 要另起一個子進程運行副語言的runtime,會花一定時間和空間開銷。

          是一個非常有趣的 非主流前端領域,這個領域要探索的是如何用工程手段解決前端開發和部署優化的綜合問題,入行到現在一直在學習和實踐中。

          在我的印象中,facebook是這個領域的鼻祖,有興趣、有梯子的同學可以去看看facebook的頁面源代碼,體會一下什么叫工程化。

          接下來,我想從原理展開講述,多圖,較長,希望能有耐心看完。

          讓我們返璞歸真,從原始的前端開發講起。上圖是一個"可愛"的index.html頁面和它的樣式文件a.css,用文本編輯器寫代碼,無需編譯,本地預覽,確認OK,丟到服務器,等待用戶訪問。前端就是這么簡單,好好玩啊,門檻好低啊,分分鐘學會有木有!

          然后我們訪問頁面,看到效果,再查看一下網絡請求,200!不錯,太?完美了!那么,研發完成。。。。了么?

          等等,這還沒完呢!對于大公司來說,那些變態的訪問量和性能指標,將會讓前端一點也不"好玩"。

          看看那個a.css的請求吧,如果每次用戶訪問頁面都要加載,是不是很影響性能,很浪費帶寬啊,我們希望最好這樣:

          利用304,讓瀏覽器使用本地緩存。但,這樣也就夠了嗎?不成!304叫協商緩存,這玩意還是要和服務器通信一次,我們的優化級別是變態級,所以必須徹底滅掉這個請求,變成這樣:

          強制瀏覽器使用本地緩存(cache-control/expires),不要和服務器通信。好了,請求方面的優化已經達到變態級別,那問題來了:你都不讓瀏覽器發資源請求了,這緩存咋更新?

          很好,相信有人想到了辦法:通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源。好像這樣:

          下次上線,把鏈接地址改成新的版本,就更新資源了不是。OK,問題解決了么?!當然沒有!大公司的變態又來了,思考這種情況:

          頁面引用了3個css,而某次上線只改了其中的a.css,如果所有鏈接都更新版本,就會導致b.css,c.css的緩存也失效,那豈不是又有浪費了?!

          重新開啟變態模式,我們不難發現,要解決這種問題,必須讓url的修改與文件內容關聯,也就是說,只有文件內容變化,才會導致相應url的變更,從而實現文件級別的精確緩存控制。

          什么東西與文件內容相關呢?我們會很自然的聯想到利用 數據摘要要算法 對文件求摘要信息,摘要信息與文件內容一一對應,就有了一種可以精確到單個文件粒度的緩存控制依據了。好了,我們把url改成帶摘要信息的:

          這回再有文件修改,就只更新那個文件對應的url了,想到這里貌似很完美了。你覺得這就夠了么?大公司告訴你:圖樣圖森破!

          唉~~~~,讓我喘口氣

          現代互聯網企業,為了進一步提升網站性能,會把靜態資源和動態網頁分集群部署,靜態資源會被部署到CDN節點上,網頁中引用的資源也會變成對應的部署路徑:

          好了,當我要更新靜態資源的時候,同時也會更新html中的引用吧,就好像這樣:

          這次發布,同時改了頁面結構和樣式,也更新了靜態資源對應的url地址,現在要發布代碼上線,親愛的前端研發同學,你來告訴我,咱們是先上線頁面,還是先上線靜態資源?

          1. 先部署頁面,再部署資源:在二者部署的時間間隔內,如果有用戶訪問頁面,就會在新的頁面結構中加載舊的資源,并且把這個舊版本的資源當做新版本緩存起來,其結果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執行錯誤。
          2. 先部署資源,再部署頁面:在部署時間間隔之內,有舊版本資源本地緩存的用戶訪問網站,由于請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現正常;但沒有本地緩存或者緩存過期的用戶訪問網站,就會出現舊版本頁面加載新版本資源的情況,導致頁面執行錯誤,但當頁面完成部署,這部分用戶再次訪問頁面又會恢復正常了。 好的,上面一坨分析想說的就是:先部署誰都不成!都會導致部署過程中發生頁面錯亂的問題。所以,訪問量不大的項目,可以讓研發同學苦逼一把,等到半夜偷偷上線,先上靜態資源,再部署頁面,看起來問題少一些。

          但是,大公司超變態,沒有這樣的"絕對低峰期",只有"相對低峰期"。So,為了穩定的服務,還得繼續追求極致啊!

          這個奇葩問題,起源于資源的 覆蓋式發布,用 待發布資源 覆蓋 已發布資源,就有這種問題。解決它也好辦,就是實現 非覆蓋式發布。

          看上圖,用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發布路徑中,這樣,內容有修改的資源就變成了一個新的文件發布到線上,不會覆蓋已有的資源文件。上線過程中,先全量部署靜態資源,再灰度部署頁面,整個問題就比較完美的解決了。

          所以,大公司的靜態資源優化方案,基本上要實現這么幾個東西:

          • 配置超長時間的本地緩存 —— 節省帶寬,提高性能
          • 采用內容摘要作為緩存更新依據 —— 精確的緩存控制
          • 靜態資源CDN部署 —— 優化網絡請求
          • 更資源發布路徑實現非覆蓋式發布 —— 平滑升級

          全套做下來,就是相對比較完整的靜態資源緩存控制方案了,而且,還要注意的是,靜態資源的緩存控制要求在 前端所有靜態資源加載的位置都要做這樣的處理 。是的,所有!什么js、css自不必說,還要包括js、css文件中引用的資源路徑,由于涉及到摘要信息,引用資源的摘要信息也會引起引用文件本身的內容改變,從而形成級聯的摘要變化,大概示意圖就是:

          好了,目前我們快速的學習了一下前端工程中關于靜態資源緩存要面臨的優化和部署問題,新的問題又來了:這?讓工程師怎么寫碼啊!!!

          要解釋優化與工程的結合處理思路,又會扯出一堆有關模塊化開發、資源加載、請求合并、前端框架等等的工程問題,以上只是開了個頭,解決方案才是精髓,但要說的太多太多,有空再慢慢展開吧。

          總之,前端性能優化絕逼是一個工程問題!

          以上不是我YY的,可以觀察 百度 或者 facebook 的頁面以及靜態資源源代碼,查看它們的資源引用路徑處理,以及網絡請中靜態資源的緩存控制部分。再次贊嘆facebook的前端工程建設水平,跪舔了。

          建議前端工程師多多關注前端工程領域,也許有人會覺得自己的產品很小,不用這么變態,但很有可能說不定某天你就需要做出這樣的改變了。而且,如果我們能把事情做得更極致,為什么不去做呢?

          另外,也不要覺得這些是運維或者后端工程師要解決的問題。如果由其他角色來解決,大家總是把自己不關心的問題丟給別人,那么前端工程師的開發過程將受到極大的限制,這種情況甚至在某些大公司都不少見!

          yHTML 是個快速 HTML 解析器,使用線程來實現一個類似純 C99庫,無任何外部依賴。

          MyHTML 當前版本是 1.0.1,擴展了一個 MyCSS 開源庫。MyCSS 是個快速的 CSS 解析器,GitHub 地址:

          MyHTML 主要特性:

          • 異步解析,構建樹和指數

          • 和 HTML5 規范完全一致

          • 兩個 API – 高和低水平

          • 操作元素:添加,修改,刪除和其他

          • 操作元素屬性:添加,修改,刪除和其他

          • 支持 39 種字符編碼 encoding.spec.whatwg.org

          • 支持字符編碼檢測

          • 支持單模解析

          • 支持無 POSIX 線程構建

          • 支持片段解析

          • 支持 parsing by chunks

          • 無外部依賴

          • C99 支持

          • 通過了所有 html5lib-tests

          擴展庫

          • MyCSS— Fast C/C++ CSS Parser (Cascading Style Sheets Parser)

          支持的 InputStream 編碼

          X_USER_DEFINED, UTF_8, UTF_16LE, UTF_16BE, BIG5, EUC_KR, GB18030, IBM866, ISO_8859_10, ISO_8859_13, ISO_8859_14, ISO_8859_15, ISO_8859_16, ISO_8859_2, ISO_8859_3, ISO_8859_4, ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, KOI8_R, KOI8_U, MACINTOSH, WINDOWS_1250, WINDOWS_1251, WINDOWS_1252, WINDOWS_1253, WINDOWS_1254, WINDOWS_1255, WINDOWS_1256, WINDOWS_1257, WINDOWS_1258, WINDOWS_874, X_MAC_CYRILLIC, ISO_2022_JP, GBK, SHIFT_JIS, EUC_JP, ISO_8859_8_I

          支持 UTF-8 編碼

          可檢測的字符編碼

          UTF-8, UTF-16LE, UTF16BE 和 russian windows-1251, koi8-r, iso-8859-5, x-mac-cyrillic, ibm866

          構建和安裝

          Make

          make
          • MyHTML_OPTIMIZATION_LEVEL=-O2set compiler optimization level. Default: -O2

          • MyHTML_BUILD_WITHOUT_THREADS=YESbuild without POSIX Threads. Default: NO

          示例

          make MyHTML_BUILD_WITHOUT_THREADS=NO
          cp lib/* /usr/local/lib cp -r include/* /usr/local/include

          CMake

          在 myhtml/project 目錄:

          cmake .make sudo make install
          • MyHTML_OPTIMIZATION_LEVEL=-O2set compiler optimization level. Default: -O2

          • CMAKE_INSTALL_LIBDIR=libset path to install created library. Default: lib

          • MyHTML_BUILD_SHARED=ONbuild shared library. Default: ON

          • MyHTML_BUILD_STATIC=ONbuild static library. Default: ON

          • MyHTML_INSTALL_HEADER=OFFinstall header files. Default OFF

          • MyHTML_BUILD_WITHOUT_THREADS=YESbuild without POSIX Threads. Default: NO

          • MyHTML_EXTERN_MALLOC=my_malloc_funcset extern malloc function. Default: UNDEFINED

          • MyHTML_EXTERN_REALLOC=my_realloc_funcset extern realloc function. Default: UNDEFINED

          • MyHTML_EXTERN_CALLOC=my_calloc_funcset extern calloc function. Default: UNDEFINED

          • MyHTML_EXTERN_FREE=my_free_funcset extern free function. Default: UNDEFINED

          示例

          cmake . -DCMAKE_INSTALL_LIBDIR=lib64 -DMyHTML_INSTALL_HEADER=ON

          程序構建示例

          構建共享庫

          gcc -Wall -Werror -O2 -lmyhtml your_program.c -o your_program

          構建靜態庫

          gcc -Wall -Werror -O2 your_program.c /path/to/libmyhtml_static.a -o your_program

          其他語言綁定

          簡單示例

          #include <stdio.h>#include <stdlib.h>#include <string.h>#include <myhtml/api.h>int main(int argc, const char * argv[]) { char html = "<div><span>HTML</span></div>"; // basic init myhtml_t* myhtml = myhtml_create; myhtml_init(myhtml, MyHTML_OPTIONS_DEFAULT, 1, 0); // first tree init  myhtml_tree_t* tree = myhtml_tree_create; myhtml_tree_init(tree, myhtml); // parse html myhtml_parse(tree, MyHTML_ENCODING_UTF_8, html, strlen(html)); // release resources myhtml_tree_destroy(tree); myhtml_destroy(myhtml); return 0; }

          MyHTML 遵循 LGPL 開源授權協議.

          微信訂閱號:開源派 (opensourcepie)

          ↓點擊閱讀原文,查看相關鏈接


          主站蜘蛛池模板: 亚洲国产欧美日韩精品一区二区三区| 亚洲综合无码一区二区| 中文字幕亚洲一区二区va在线| 国产福利电影一区二区三区,亚洲国模精品一区 | 久久久久久人妻一区精品| 亚洲美女一区二区三区| 视频一区视频二区日韩专区| 免费播放一区二区三区| 久久久久人妻精品一区二区三区| 无码日韩人妻av一区免费| 久久免费视频一区| 黑巨人与欧美精品一区| 精品一区二区三区AV天堂| 日韩电影一区二区三区| 福利一区二区三区视频午夜观看| 日韩人妻一区二区三区免费 | 波多野结衣中文一区| 国产人妖视频一区二区破除| 亚洲av成人一区二区三区在线播放 | 精品国产福利在线观看一区| 2018高清国产一区二区三区| 中文字幕一区二区三区在线观看| 亚洲Av高清一区二区三区| 亚洲欧美成人一区二区三区| 国产伦精品一区二区三区在线观看| 人妻夜夜爽天天爽一区| 免费国产在线精品一区| 中文字幕一区二区三区精华液 | 亚洲av午夜精品一区二区三区| 精品国产日产一区二区三区 | 精品国产一区二区三区| 天堂一区二区三区精品| 久久婷婷久久一区二区三区| 亚欧在线精品免费观看一区| 日韩毛片一区视频免费| 韩国美女vip福利一区| 一区二区三区免费精品视频| 国产高清视频一区三区| 国产精品第一区揄拍| 一区二区三区人妻无码| 国产成人av一区二区三区在线观看 |