Python混合編程--呼叫可執行檔案

2020-08-13 20:15:48

原文鏈接: http://www.juzicode.com/archives/1089

在計算機語言的世界裏,各種程式語言百花齊放,爭相鬥艳,並不存在某一種語言一統天下的情景,各種語言各有其優勢和應用場景,所以就存在多語言混合編程的需求,各種語言得以優勢互補。比如影象處理庫OpenCV的底層是效率更高的C++寫的,上層提供了更友好易用的Python介面;科學計算用到的numpy底層是C和Fortran寫的,這些底層庫一來經過了多年的實踐檢驗已經非常成熟,二來編譯型語言效率更高,沒有必要又用Python重寫,直接封裝好Python介面即可;另外像Python內建的sqlite3模組,也是用C語言寫的底層,上層封裝了Python介面。

 

在Python中也有多種方法實現多語言混合編程:1.可執行檔案級別的擴充套件,可以呼叫任何其他程式語言生成的可執行檔案;2.可以對現有的C語言編寫的動態鏈接庫API函數實現封裝;3.利用Python的擴充套件庫實現程式碼級別的擴充套件。分別對應了檔案—>API—>程式碼3種層級。

本文將從檔案級別的擴充套件開始講解Python的多語言擴充套件功能,接下來的幾篇文章將從API和程式碼級別介紹其擴充套件功能。

 

1 os.popen()方法

使用popen()方法可以呼叫其他可執行檔案或者系統命令,可執行檔案輸出到標準輸出的內容會重新定向到一個「管道」中,呼叫結束後會返回一個檔案物件,可以用readlines(),readline()或read()讀出其內容。

比如要顯示當前目錄下的檔案列表,在windows系統中可以直接用dir命令顯示當前目錄內容,我們可以用os.popen(‘dir’)實現同樣的功能:

print('執行popen......')
ret = os.popen('dir')
print('執行popen結果:',ret)
print('顯示popen內容:')
buffers = ret.readlines()
print(buffers)
執行popen......
執行popen結果: <os._wrap_close object at 0x00000154AA847EB8>
顯示popen內容:
[' 驅動器 E 中的卷是 data\n', ' 卷的序列號是 4AC8-575E\n', '\n', ' E:\\juzicode\\py3study\\m07-呼叫exe 的目錄\n', '\n', '2020/08/10  01:38    <DIR>          .\n', '2020/08/10  01:38    <DIR>          ..\n', '2020/08/10  01:47               601 popen-system.py\n', '               1 個檔案            601 位元組\n', '               2  個目錄 27,096,033,280 可用位元組\n']

os.popen()的第一個入參是可帶入參的可執行檔案或系統命令,參數型別爲str型別,另外可選有mode入參,可以是’r’或’w’,預設爲’r’,當mode=’r’時可以使用類似open()函數建立檔案物件的read類方法,如果使用mode=’w’入參,將會在標準輸出上列印執行過程中的輸出內容,這時不可以再使用read類方法獲取這些輸出內容。

使用read類方法時也需要注意可執行檔案或系統命令的某些輸出雖然是可見的,但是並不一定能被read類方法讀出,比如用mkdir命令建立一個已經存在的資料夾時,報錯資訊就不能被捕獲到。下面 下麪的例子就是連續2次呼叫’mkdir abc’試圖建立名稱爲abc的資料夾,第二次呼叫時出現失敗了,這時的失敗資訊並不能被readlines()獲取到。

另外在混合使用了printf和cout的程式中,其列印輸出的順序並非是嚴格對應的,如果需要解析read類方法讀出的內容,並且對順序有嚴格要求時需要特別注意。

使用os.popen()返回物件的close()方法可以檢查popen()執行的命令是否成功,如果執行成功則返回None,否則返回其他值。但是需要注意的是使用ret=os.popen(cmd)執行某些可執行檔案或者系統命令後,需要加入延時再呼叫ret.close()才能 纔能得到正確的結果,下面 下麪的例子還是用dir獲取當前目錄結構,對比是否加入延時會得到不同的返回結果,但是執行ping命令雖然耗時很長卻不需要加入延時:

 

2 os.system()

os.system()也可以用來執行可執行檔案或者系統命令,在Windows系統裡,根據環境變數COMSPEC定義的shell(Windows下一般就是cmd.exe)執行命令同時會返回執行結果,這個返回結果一般是個整型變數,表示可執行檔案或系統命令的退出狀態,對應到C語言main()函數的return值,但是輸出到stdout內容也同時列印在stdout上,並不能被Python捕獲到。 os.system()方法實際上直接呼叫了標準C庫函數system()。

print('執行system......')
ret = os.system('dir')
print('執行system結果:',ret)
執行system......
 驅動器 E 中的卷是 data
 卷的序列號是 4AC8-575E

 E:\juzicode\py3study\m07-呼叫exe 的目錄

2020/08/10  01:38    <DIR>          .
2020/08/10  01:38    <DIR>          ..
2020/08/10  01:47               601 popen-system.py
               1 個檔案            601 位元組
               2 個目錄 27,096,033,280 可用位元組

執行system結果: 0

從上面的例子可以看到os.system()執行後返回的結果是一個整型值,不能像os.popen()那樣有其他的方法獲取標準輸出上的內容。

 

3 subprocess.run()

從前面的os.popen()和os.system()可以看出,使用起來還是不太方便,os.system()不捕獲stdout和stderr的列印,os.popen()在某些場合還需要等待不定時長的延時才能 纔能得到最終的結果,那有沒有一種更強大的方法來解決這些問題呢?當然有了!接下來出場的是subprocess,subprocess一出現就說要替代他們,在它的規範文件PEP324中就是這麼霸氣宣稱的:

先看一個簡單的例子,還是顯示當前目錄下的檔案清單,不過入參中需要額外增加shell=True才能 纔能在windows的cmd命令列中執行:

import subprocess
ret = subprocess.run('dir', shell=True)
print(ret)
==========結果:
 
驅動器 E 中的卷是 data
 卷的序列號是 4AC8-575E

 E:\juzicode\py3study\m07-extend-file 的目錄

2020/08/12  07:55    <DIR>          .
2020/08/12  07:55    <DIR>          ..
2020/08/10  19:18               857 popen-system-test.py
2020/08/12  22:31               450 subprocess-test.py
               4 個檔案          2,357 位元組
               3 個目錄 27,089,352,704 可用位元組

CompletedProcess(args='dir', returncode=0)

也可以將輸入指令和入參組成一個list或者tuple傳入:

import subprocess
ret = subprocess.run(['dir'], shell=True)
ret = subprocess.run(['ping','www.baidu.com'],shell=True)

但是這種方法有非常嚴格的格式要求,不能有任何多餘的空格。下面 下麪這個例子中將dir命令後面增加了空格,ping命令的主機參數前也增加了空格,都會導致執行執行錯誤:

從前面的例子可以看到,subprocess.run()返回的是一個CompletedProcess(args=’dir’, returncode=0)範例,從範例屬性中可以得到執行命令的最後結果。 這個範例只包含了2個有效的屬性,通過修改入參設定 capture_output=True可以捕獲輸出到stdout和stderr的值,得到更多屬性的值。 另外如果設定了text=True,返回範例的stdout和stderr屬性的值爲str型別,否則爲bytes型別。

import subprocess
 
print('執行 dir')
ret = subprocess.run('dir', shell=True,capture_output=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)

print('執行 dir --notfound')
ret = subprocess.run('dir --notfound', shell=True, capture_output=True, text=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)
==========結果:
執行 dir
args: dir
returncode: 0
stdout: b' \xc7\xfd\xb6\xaf\xc6\xf7 E \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 data\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 4AC8-575E\r\n\r\n E:\\juzicode\\py3study\\m07-extend-file \xb5\xc4\xc4\xbf\xc2\xbc\r\n\r\n2020/08/13  00:55    <DIR>          .\r\n2020/08/13  00:55    <DIR>          ..\r\n2020/08/11  20:04    <DIR>          abc\r\n2020/08/11  19:11               570 popen-delay.py\r\n2020/08/11  20:03               480 popen-read-invalid.py\r\n2020/08/10  19:18               857 popen-system-test.py\r\n2020/08/13  01:16               673 subprocess-return-value.py\r\n2020/08/13  00:55               587 subprocess-test.py\r\n               5 \xb8\xf6\xce\xc4\xbc\xfe          3,167 \xd7\xd6\xbd\xda\r\n               3 \xb8\xf6\xc4\xbf\xc2\xbc 27,089,123,328 \xbf\xc9\xd3\xc3\xd7\xd6\xbd\xda\r\n'
stderr: b''

執行 dir --notfound
args: dir --notfound
returncode: 1
stdout:  驅動器 E 中的卷是 data
 卷的序列號是 4AC8-575E

 E:\juzicode\py3study\m07-extend-file 的目錄


stderr: 找不到檔案

從上面的例子可以看到增加capture_output=True可以將stdout和stderr的內容捕獲到返回範例的stdout和stderr屬性中,這時在命令列中將看不到這些輸出內容。

我們又回到最開始os.popen()執行mkdir abc命令時,當資料夾已經存在的情況下,未能捕獲到提示出錯的內容,我們用subprocess.run()方式實驗下,可以看到出錯的列印已經被捕獲到返回範例的stderr屬性中了:

print('執行 mkdir abc') #假設已經存在了abc資料夾
ret = subprocess.run(' mkdir abc', shell=True, capture_output=True, text=True)
print('args:',ret.args)
print('returncode:',ret.returncode) 
print('stdout:',ret.stdout)
print('stderr:',ret.stderr)
=========結果:
執行 mkdir abc
args:  mkdir abc
returncode: 1
stdout:
stderr: 子目錄或檔案 abc 已經存在。

 

本文介紹了幾種在Python中執行可執行檔案或者系統命令的方法,這幾種方法比較起來,subprocess.run()一次呼叫多種結果同時返回,使用起來更方便。

 


更多精彩在這裏: