HOWTO · Python
在 Python 中呼叫外部程式
本教學示範如何在 Python 中執行外部程式
本頁內容
對於大多數常見的外部命令,使用 subprocess.run()。將可執行檔及其引數以清單形式傳遞,這樣 Python 就不會讓 shell 來解析它們。當你需要可讀的輸出時,設定 capture_output=True 和 text=True,然後在已完成的程序物件上檢查 stdout、stderr 和 returncode。下面的範例啟動的是 Python 本身,因此不依賴於是否安裝了 shell 工具。
執行此範例:
本節中的程式碼以及顯示的標籤或輸出保持英文。
import subprocess
import sys
result = subprocess.run(
[sys.executable, "-c", "print('Hello from Python')"],
capture_output=True,
text=True,
check=True,
)
print(result.stdout, end="")
print("Return code:", result.returncode)
Hello from Python
Return code: 0
清單使每個引數保持獨立,包括引數內部的空格。text=True 使用平台的文字編碼將擷取的資料流解碼為字串;沒有它時,stdout 和 stderr 是位元組。check=True 會使非零的命令狀態引發 subprocess.CalledProcessError。當你想自行處理狀態時,不要設定它。擷取的輸出與命令的狀態是分開的:命令可以寫入任一資料流,但仍以非零的 returncode 結束。
使用 os.system() 執行 shell 命令
os.system() 透過命令處理器(在常見平台上是 shell)執行其字串,並傳回命令處理器的狀態。命令的標準輸出和標準錯誤通常仍與父程序相連,因此此呼叫不會提供擷取的文字。當有意使用 shell 行為時,它是執行簡單 shell 命令的舊式選擇。如果需要引數清單、擷取的資料流或結構化的狀態處理,請優先使用 subprocess.run()。
由於輸入是 shell 語法,不要透過插入不受信任的文字來建構字串。Shell 中繼字元可以改變實際執行的內容。確切的命令處理器和狀態編碼因平台而異,因此不要假設傳回值始終是子程式未經修改的結束代碼。例如,可攜的概念性呼叫是 os.system("some-command");請為它將執行的作業系統選擇具體的命令和引號規則。
使用 os.popen() 讀取命令輸出
os.popen() 開啟一條通向透過 shell 執行的命令的管道,並傳回一個類似檔案的物件。讀取該資料流,並在完成後將其關閉;close() 還可以提供命令的狀態。此介面是用於簡單的以管道為基礎的互動的舊式選項,它不提供透過 subprocess 可用的對 stdout 和 stderr 的分開處理。
例如,其形式為 pipe = os.popen(command),然後是 output = pipe.read() 和 status = pipe.close()。命令字串由 shell 解釋,因此對不受信任的輸入要像對 os.system() 一樣小心。close() 的傳回值不能代替擷取的輸出,其狀態表示可能取決於平台。對於既需要輸出又需要明確傳回代碼的新程式碼,請優先使用 subprocess.run()。
使用 subprocess.Popen() 進行更底層的程序控制
subprocess.Popen() 是當程式需要在程序執行時與其互動時使用的類別建構子。傳遞引數清單以直接執行。僅當你打算讀取這些資料流時,才設定 stdout=subprocess.PIPE 或 stderr=subprocess.PIPE。呼叫 communicate() 來收集已設定的管道並等待完成,或者在不需要收集管道輸出時呼叫 wait()。
當子程序大量寫入另一個管道時,手動讀取一個管道可能會填滿作業系統的緩衝區並使程序停滯。communicate() 是將兩個資料流一起收集的常用方法;除非啟用了文字模式,否則它傳回位元組。對於大量或串流輸出,應有意識地處理這些資料流,而不是將所有內容都緩衝在記憶體中。如果你只需要等待並檢查已完成的結果,subprocess.run() 更簡單。
使用 subprocess.call() 執行命令並等待
subprocess.call() 執行一個命令,等待它完成,並傳回其狀態。提供一個清單,例如 subprocess.call(["program", "--version"]),以避免 shell 解析。這個便利函式預設不擷取輸出;子程序的資料流是繼承的。當你需要擷取的資料流、文字解碼或 CompletedProcess 欄位時,使用 subprocess.run()。call() 在現有程式碼只需要同步狀態時仍然有用,但新程式碼通常能從更清晰的 run() 介面中受益。
狀態為零通常表示成功,而非零狀態表示命令報告了問題。特定非零值的含義屬於被呼叫的程式,狀態慣例可能因平台而異。如果你需要將非零狀態視為錯誤,subprocess.run(..., check=True) 會引發 CalledProcessError;否則,檢查 returncode 並決定你的應用程式應如何處理。
大多數外部命令使用 subprocess.run()
對於常見任務,subprocess.run() 會等待完成並傳回一個 CompletedProcess。使用 capture_output=True 時,結果包含 stdout 和 stderr;使用 text=True 時,這些值是字串。該範例列印標準輸出和傳回代碼,而標準錯誤仍可在 result.stderr 中取得。如果程式將輸出寫入標準錯誤,它不會出現在 result.stdout 中。
優先使用引數清單,並為直接呼叫程式保持預設的 shell=False。這樣可以防止引數中的 shell 中繼字元被解釋為 shell 語法,儘管目標程式本身仍有其輸入和選項處理方面的風險。僅當需要管道或重導向等 shell 功能時,才使用 shell=True,並且此時應保持 shell 命令固定,或使用適合平台的謹慎引號方式來建構它。引數清單是可攜的預設選擇,但可執行檔的名稱和命令列選項在不同作業系統之間仍然不同。
當命令失敗時,選擇如何處理該結果:使用 check=True 以 CalledProcessError 停止,或將其保持為 false 並根據 result.returncode 進行分支處理。可執行檔缺失或無法啟動程序會在存在已完成的結果之前引發作業系統錯誤。不要僅憑輸出文字推斷成功;應使用狀態,並在相關時檢查兩個資料流。
結論
大多數外部命令都使用 subprocess.run():它使引數保持獨立,等待程序完成,並提供包含狀態和任何擷取的資料流的結果。當你需要在程序執行時進行更底層的互動時,使用 subprocess.Popen()。subprocess.call() 是一個只傳回狀態的小型同步便利函式。os.system() 和 os.popen() 是以 shell 為基礎的舊式介面;僅當它們的行為適合任務時,才選擇它們。保持 shell 輸入受信任,只擷取你需要的資料流,並考慮不同平台之間可執行檔名稱和狀態慣例的差異。