1. 簡(jiǎn)介
subprocess 是 Python 的一個(gè)標(biāo)準(zhǔn)庫(kù),它允許你啟動(dòng)新的進(jìn)程、連接到它們的輸入/輸出/錯(cuò)誤管道,并且獲取它們的返回值。
該庫(kù)使 Python 程序能夠方便地與操作系統(tǒng)交互,執(zhí)行系統(tǒng)命令,調(diào)用外部程序,處理子進(jìn)程,使得在 Python 程序中運(yùn)行外部命令和程序變得簡(jiǎn)單和靈活。
2. 主要方法
-
subprocess.run():運(yùn)行指定的命令并等待其完成,返回一個(gè) CompletedProcess 實(shí)例。 -
subprocess.Popen():創(chuàng)建一個(gè)新的進(jìn)程,可以進(jìn)行更復(fù)雜的交互; -
subprocess.call():運(yùn)行命令并等待其完成,命令出錯(cuò)時(shí)拋出異常; -
subprocess.check_call():運(yùn)行命令并等待其完成,命令返回非零退出狀態(tài)時(shí)拋出異常; -
subprocess.check_output():運(yùn)行命令,捕獲輸出,如果命令失敗則拋出異常。
注意:
subprocess.run() 默認(rèn)情況下不會(huì)捕獲實(shí)時(shí)輸出,它會(huì)等待命令執(zhí)行完成后才返回。
subprocess.Popen() 允許你實(shí)時(shí)地從 stdout 和 stderr 中讀取輸出,但要寫一個(gè)簡(jiǎn)單的循環(huán)代碼,這個(gè)循環(huán)會(huì)持續(xù)從 stdout 中讀取,直到?jīng)]有更多輸出,并且進(jìn)程已經(jīng)結(jié)束。
3. 簡(jiǎn)單實(shí)例
import subprocess
3.1. 執(zhí)行ls -l命令
# 執(zhí)行 ls -l 命令
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
# 打印輸出結(jié)果
print("STDOUT:", result.stdout)
if result.stderr: # 如果存在錯(cuò)誤則輸出
print("STDERR:", result.stderr)

3.2. 執(zhí)行echo命令并傳遞參數(shù)
# 執(zhí)行 echo 命令并傳遞字符串參數(shù)
result = subprocess.run(['echo', 'Hello, World!'], capture_output=True, text=True)
# 打印輸出結(jié)果
print("STDOUT:", result.stdout)

3.3. 在 Python 腳本中執(zhí)行其他 Python 腳本
# 執(zhí)行另一個(gè) Python 腳本
result = subprocess.run(['python', 'other_script.py'], capture_output=True, text=True)
# 打印其他腳本的輸出
print("STDOUT:", result.stdout)
3.4. 實(shí)時(shí)執(zhí)行命令并獲取輸出
# 實(shí)時(shí)執(zhí)行 tail -f 命令(假設(shè)有一個(gè)日志文件 log.txt)
with subprocess.Popen(['tail', '-f', 'log.txt'], stdout=subprocess.PIPE, text=True) as process:
for line in process.stdout:
print(line.strip()) # 實(shí)時(shí)打印每一行輸出
# 如果需要停止實(shí)時(shí)輸出,可以使用 process.terminate() 或 process.kill()
4. 復(fù)雜實(shí)例
4.1 非實(shí)時(shí)輸出中間信息
LigandMPNN是一個(gè)項(xiàng)目的根路徑,內(nèi)部包含了可執(zhí)行python文件run.py,其命令行模式的執(zhí)行方式如下:
conda activate 對(duì)應(yīng)python環(huán)境 # 進(jìn)入針對(duì)該項(xiàng)目的python環(huán)境(如果有的話)
cd /home/houliya/LigandMPNN # 進(jìn)入項(xiàng)目根路徑
# 直接linux終端執(zhí)行或把下面代碼寫入shell文件再執(zhí)行
python run.py \
--model_type "ligand_mpnn" \
--seed 111 \
--pdb_path "/home/houliya/protein_desig_cgx/output_cgx/output_luciferase_RFDAA_all_new/nnluz_RFAA_RFDAA_4.pdb" \
--out_folder "/home/houliya/protein_desig_cgx/output_test" \
--pack_side_chains 1 \
--number_of_packs_per_design 2 \
--pack_with_ligand_context 1 \
--file_ending "_ligandMPNN" \
--batch_size 1 \
--number_of_batches 2
如何將其在python中執(zhí)行這些代碼,并得到相同的結(jié)果?由于不需要輸出實(shí)時(shí)的計(jì)算信息,因此利用subprocess.run()方法:
result = subprocess.run(['python','run.py',
"--model_type", "ligand_mpnn",
"--seed", "111",
"--pdb_path", "/home/houliya/protein_desig_cgx/output_cgx/output_luciferase_RFDAA_all_new/nnluz_RFAA_RFDAA_4.pdb",
"--out_folder", "/home/houliya/protein_desig_cgx/output_test",
"--pack_side_chains", "1",
"--number_of_packs_per_design", '2',
"--file_ending", "_ligandMPNN",
"--batch_size", "1",
"--number_of_batches", "2"], capture_output=True, text=True)
if result.stderr: # 判斷是否有錯(cuò)誤信息
print(result.stderr)
if result.stdout: # 輸出一些結(jié)果信息
print(result.stdout)
if result.check_returncode: # 輸出一些返回信息
print(result.check_returncode)
4.2 實(shí)時(shí)輸出計(jì)算過程中的每一步信息
rf_diffusion_all_atom是一個(gè)項(xiàng)目的根路徑,該項(xiàng)目包含了一個(gè)名為rf_se3_diffusion.sif的apptainer容器文件,要執(zhí)行對(duì)應(yīng)的python代碼run_inference.py,必須先啟動(dòng)容器,且代碼執(zhí)行過程中會(huì)實(shí)時(shí)輸出相應(yīng)信息,因此整體比例4.1更復(fù)雜,先來看常規(guī)命令行的執(zhí)行方式:
conda activate 對(duì)應(yīng)python環(huán)境 # 進(jìn)入針對(duì)該項(xiàng)目的python環(huán)境(如果有的話)
cd /home/houliya/rf_diffusion_all_atom # 進(jìn)入項(xiàng)目根路徑
# 直接linux終端執(zhí)行或把下面代碼寫入shell文件再執(zhí)行
apptainer run --nv rf_se3_diffusion.sif -u run_inference.py \
inference.deterministic=True \
diffuser.T=100 \
inference.output_prefix=/home/houliya/protein_desig_cgx/output_test/nnluz_RFAA_RFDAA \
inference.input_pdb=/home/houliya/protein_desig_cgx/input_cgx/input_luciferase/nnluz_RFAA.pdb \
contigmap.contigs=[\'266-266\'] \
inference.ligand=LG1 \
inference.num_designs=500 \
inference.design_startnum=1
如何將其在python中執(zhí)行這些代碼,并得到相同的結(jié)果?由于需要輸出實(shí)時(shí)的計(jì)算信息,因此利用subprocess.Popen()方法:
process = subprocess.Popen(['apptainer','run',
"--nv", "rf_se3_diffusion.sif",
"-u", "run_inference.py",
"inference.deterministic=True",
"diffuser.T=100",
"inference.output_prefix=/home/houliya/protein_desig_cgx/output_test/nnluz_RFAA_RFDAA",
"inference.input_pdb=/home/houliya/protein_desig_cgx/input_cgx/input_luciferase/nnluz_RFAA.pdb",
"contigmap.contigs=[\'266-266\']",
"inference.ligand=LG1",
"inference.num_designs=500",
"inference.design_startnum=1"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 循環(huán)讀取輸出直到?jīng)]有更多輸出
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip()) # 打印輸出,去除末尾的換行符
# 等待進(jìn)程結(jié)束
stdout, stderr = process.communicate()
# 檢查是否有錯(cuò)誤輸出
if stderr:
print("STDERR:")
print(stderr)

5. 需要注意的地方:
從shell代碼轉(zhuǎn)換到subprocess.run()或subprocess.Popen()代碼時(shí)的輸入?yún)?shù)的寫法。
例如shell中某個(gè)輸入?yún)?shù)寫為--seed 111,而在subprocess.run()中寫成了"--seed", "111";
又例如在shell中某個(gè)輸入?yún)?shù)寫為diffuser.T=100,而在subprocess.Popen()中寫成了"diffuser.T=100";
subprocess.run()或subprocess.Popen()中只能輸入字符串,而不能輸入int、float等其他類型!
6. 其他
https://cloud.tencent.com/developer/article/2291501
https://blog.csdn.net/Htojk/article/details/134048843