PPython:PHP擁抱Python的利器
介紹
Python 與 PHP 都是廣泛使用的語(yǔ)言,各有所長(zhǎng),讓人期待兩者結(jié)合可以實(shí)現(xiàn)更豐富的效果。
在 PHP 中調(diào)用 Python 實(shí)現(xiàn)某些處理,這種需求雖然比較小眾,還是實(shí)用的。目前網(wǎng)上可以查到很多資料仍在探討 exec()
(也包括 system()
、shell_exec()
、passthru()
等)執(zhí)行外部的 Python 文件,但這只是一種通用的方式,調(diào)用成本比較高,在每次調(diào)用時(shí),需要裝載整個(gè) Python 解釋環(huán)境。
有此類需求的開(kāi)發(fā)者非常適合看一下 PPython,這是一種從根本上將 PHP 與 Python 有效結(jié)合的技術(shù)。
PPython 最初見(jiàn)于 https://code.google.com/p/ppython,該作者將 lajp(一種 PHP 結(jié)合 Java 的技術(shù))移植到了 Python 上。
該項(xiàng)目最初建立于 2012 年,而且似乎已經(jīng)停止維護(hù)多年,不過(guò)目前來(lái)看其思路及效果還是值得肯定的,因此將此項(xiàng)目從停止運(yùn)營(yíng)的 Google Code 上遷移到了 GitHub,并遵循原 Apache 許可證重新發(fā)布和維護(hù)。
日前筆者對(duì)此作了一番嘗試,對(duì) PPython 的方便易用有所體會(huì)。
原理與架構(gòu)
PHP 與 Python 通訊有兩種不同的套接字機(jī)制:TCP 套接字和 UNIX 套接字。UNIX 套接字是 Unix/Linux 本地套接字,相對(duì)于 TCP 套接字,具有以下特點(diǎn):
- 只能在同一臺(tái)主機(jī)中通訊(IPC),不能跨主機(jī);
- 傳輸速度大于 TCP 套接字;
- 服務(wù)端只向本機(jī)提供服務(wù)(沒(méi)有對(duì)外偵聽(tīng)端口),相對(duì)安全,易于管理。
PHP 和 Python 各有其語(yǔ)言內(nèi)部定義的數(shù)據(jù)類型,通常 PHP 進(jìn)程與 Python 進(jìn)程進(jìn)行數(shù)據(jù)交互時(shí),需要進(jìn)行轉(zhuǎn)碼處理。此類轉(zhuǎn)換如由應(yīng)用自行實(shí)現(xiàn),從開(kāi)發(fā)效率到運(yùn)行性能都會(huì)增加不少額外負(fù)擔(dān)。
PPython 對(duì) PHP 和 Python 間的通訊方式的處理支持 TCP 套接字和 UNIX 套接字兩種機(jī)制,兼顧通訊效率和分布式,轉(zhuǎn)碼由服務(wù)統(tǒng)一處理,Python 為 PHP 的數(shù)據(jù)類型提供格式兼容,使 PHP 端開(kāi)發(fā)無(wú)須為底層通訊擔(dān)心。
Python 因其語(yǔ)言 GIL 特性,同一進(jìn)程內(nèi)多線程效率不高。PPython 可根據(jù)項(xiàng)目需要部署服務(wù),多進(jìn)程運(yùn)行 Python,提高應(yīng)用綜合性能。
使用方法
PPython 的代碼可從上述項(xiàng)目倉(cāng)庫(kù)中下載。
下載得到的文件中,以下三個(gè)是 PPython 的核心代碼,作用如下:
php_python.py
,Python 進(jìn)程主文件,完成 Python 端監(jiān)聽(tīng)請(qǐng)求并運(yùn)行返回process.py
,Python 端核心類,實(shí)現(xiàn) Python 內(nèi)部進(jìn)程調(diào)用及 PHP 與 Python 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化等關(guān)鍵處理php_python.php
,PPython 客戶端,PHP 端引用此文件,可直接使用 PPython 函數(shù)實(shí)現(xiàn)調(diào)用。
將以上文件放置到任意目錄。先修改準(zhǔn)備運(yùn)行 PPython 的端口,監(jiān)聽(tīng)端口不限,只要 php_python.py
和 php_python.php
兩端修改一致。筆者統(tǒng)一改為 10240。
在當(dāng)前目錄下運(yùn)行 php_python.py
,只要 Python 環(huán)境正常,便將運(yùn)行起一個(gè) PPython 的服務(wù)。
-------------------------------------------
- PPython Service
- Time: 2019-05-13 22:24:09
-------------------------------------------
Listen port: 10240
charset: utf-8
Server startup...
PHP 端引入 php_python.php
,就可以用 ppython
函數(shù)與之前啟動(dòng)的 PPython 服務(wù)通訊,傳入請(qǐng)求由 PPython 服務(wù)調(diào)用 Python 處理后返回結(jié)果,如 $res = ppython('test::go')
是調(diào)用test.py
中的 go
函數(shù),也可加上更多參數(shù),第二個(gè)參數(shù)起將為被調(diào)的函數(shù)傳遞更多參數(shù)。
php_python.py
是 PPython 啟動(dòng)后直接運(yùn)行的全局代碼,有全局配置或進(jìn)程啟動(dòng)后的通用處理都寫在這里,如原生代碼中建立了數(shù)據(jù)庫(kù)連接等,項(xiàng)目中應(yīng)視情況作優(yōu)化。
但 Python 令人感興趣的主要方面不只是像 PHP 那樣描述業(yè)務(wù)功能,它可以在人工智能等領(lǐng)域所需要的計(jì)算型任務(wù)提供對(duì)更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的處理,因此二者的結(jié)合可以給 PHP 帶來(lái)更多應(yīng)用場(chǎng)景。
改進(jìn)
此外,原生的 php_python.py
還有些不足。筆者用 ppython
調(diào)用自定義代碼中遇到了三個(gè)問(wèn)題,并相應(yīng)做了解決:
-
不支持
complex
(復(fù)數(shù)類),復(fù)數(shù)是數(shù)學(xué)上的一種數(shù)據(jù)類型,主要包括real
(實(shí)部)和imag
(虛部)數(shù)據(jù),雖然日常生活中遇到較少,但 AI 和各種專業(yè)研究領(lǐng)域或并不罕見(jiàn)。Python 里有complex
類,對(duì)復(fù)數(shù)可以直接進(jìn)行各種計(jì)算,但PPython
序列化和反序列化對(duì)complex
沒(méi)有處理。為了能讓complex
包括的數(shù)據(jù)能正常返回,只要在process.py
的z_encode()
方法中加上符合 PHP 要求的序列化處理,代碼如下:elif isinstance(p, numpy.complex128):
t1 = str(p.real)
t2 = str(p.imag)
return 'O:7:"complex":2:{s:4:"real";d:%s;s:4:"imag";d:%s;}' % (t1,t2)
-
不支持
ndarray
(多維數(shù)組)。相比complex
,ndarray
要普通得多,相信凡使用到 Python 的各種計(jì)算功能,ndarray
是無(wú)法回避的,甚至ndarray
在一定程度上成就了 Python。但原php_python.py
不能識(shí)別ndarray
。不過(guò)解決起來(lái)并不難,在process.py
里找到z_encode()
方法,加上下面這段,可以直接將ndarray
轉(zhuǎn)化為符合 PHP 要求的數(shù)組(數(shù)字索引)。elif isinstance(p, numpy.ndarray):
s = ''
i = 0
for d in p:
s += 'i:%d;%s' % (i,z_encode(d))
i += 1
return "a:%d:{%s}" % (len(p),s)
-
原代碼不太穩(wěn)健,如數(shù)據(jù)為
ndarray
類型,if p == None:
報(bào)錯(cuò)ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
,因?yàn)?code>p == None的結(jié)果也是ndarray
,不返回false
,將判斷方法改為if p is None:
可避免出錯(cuò)。相應(yīng)地 PHP 端也要注意一下序列化和反序列化的處理。
處理回復(fù)中類似 complex
這樣的對(duì)象數(shù)據(jù)時(shí),如系統(tǒng)中沒(méi)有定義相應(yīng)的類,PHP 是可以反序列化的,但將顯示為 “incomplete object”,vardump
看得到 real
和 imag
數(shù)據(jù),但不能直接操作,自行定義 complex
類后,則按指定的類進(jìn)行解析,與 PHP 內(nèi)的一般對(duì)象無(wú)異,可以輕松進(jìn)行所有操作。
至此,PHP 與 Python 的功能調(diào)訊已無(wú)問(wèn)題。
補(bǔ)充:注冊(cè)為服務(wù)
命令行下啟動(dòng) php_python.py
主要是方便調(diào)試,可以看到觀察反饋信息等,生產(chǎn)環(huán)境中手工啟動(dòng) PPython 畢竟不太方便。可以將 PPython 配置成服務(wù),修改端口也可以為不同的應(yīng)用配置不同的 PPython 端。
Linux 下將一個(gè)進(jìn)程注冊(cè)為服務(wù)很簡(jiǎn)單,只要建立 /usr/lib/systemd/system/ppython.service
,內(nèi)容如下:
[Unit]
Description=PHP-Python Service
After=network.target remote-fs.target nss-lookup.target
[Service]
ExecStart={PPYTHON_PATH}/php_python.py
[Install]
WantedBy=multi-user.target
其中 {PPYTHON_PATH}
要改成實(shí)際路徑。
總結(jié)
有了 PPython,可以摒棄 exec()
這種 shell 調(diào)用,使開(kāi)發(fā)回歸到邏輯本身。