python?異常、捕獲和退出,進(jìn)程調(diào)用輸出示例解析
第一部分,說明 try … except … finally 之間的邏輯,并且在 使用 exit函數(shù)的區(qū)別。
第二部分,進(jìn)程調(diào)用,使用popen 解析標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出,獲取進(jìn)程返回值的問題。
1、異常和捕獲
1.1、常規(guī)示例
# problematic_subprocess.py
import sys
import os
import time
def func():
for i in range(3):
print(f"Iteration {i}")
# 正常輸出
print("This is a normal stdout message.")
print("Another stdout message.", file=sys.stdout)
time.sleep(1)
# 錯(cuò)誤輸出
print("This is a stderr message.", file=sys.stderr)
print("Another stderr message.", file=sys.stderr)
return 0
if __name__ == "__main__":
ret = 0 # 作為后面預(yù)留使用
try:
ret = func()
print(f"{__file__}: Return code: {ret}")
except Exception as e:
print(f"{__file__}: Exception: {e}")
ret = -1
finally:
print(f"{__file__}: Finally block executed.")
print(f"{__file__}: Exiting with return code: {ret}")這里使用try執(zhí)行func函數(shù), func函數(shù)中沒間隔1s秒使用標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出打印,最后返回0。 若有異常則打印異常信息。最后執(zhí)行finally邏輯,不論是否有異常,這里都會(huì)執(zhí)行。
結(jié)果輸出
Iteration 0
This is a normal stdout message.
Another stdout message.
This is a stderr message.
Another stderr message.
Iteration 1
This is a normal stdout message.
Another stdout message.
This is a stderr message.
Another stderr message.
Iteration 2
This is a normal stdout message.
Another stdout message.
This is a stderr message.
Another stderr message.
c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Return code: 0
c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Finally block executed.
c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Exiting with return code: 0
1.2、拋出異常
在前面例子上, 將 main 的返回語句修改
# return 0
raise Exception("An exception occurred.")
執(zhí)行結(jié)果,這里忽略func函數(shù)的輸出。 異常拋出被捕獲,之后,繼續(xù)執(zhí)行了finally部分。
c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Exception: An exception occurred. c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Finally block executed. c:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Exiting with return code: -1
1.3、進(jìn)程退出與異常
進(jìn)程退出,有 os._exit() 與 sys.exit() (不建議在生產(chǎn)中使用 exit()函數(shù) )兩種,可以附帶返回值,作為整個(gè)進(jìn)程的返回值。
首先。雖然主要是說明異常,但為了說明后續(xù)問題,這里提供一個(gè)進(jìn)程調(diào)用方式,來判斷進(jìn)程退出的返回值。
1.3.1、進(jìn)程調(diào)用獲取程序的返回值
import subprocess
if __name__ == "__main__":
process = subprocess.Popen(["python", "problematic_subprocess.py"])
ret_code = process.wait()
print("Return code: {}".format(ret_code))使用 subprocess 啟動(dòng)一個(gè)進(jìn)程, 使用python 執(zhí)行腳本 problematic_subprocess.py,之后使用 wait() 等待結(jié)束獲取返回值。
# problematic_subprocess.py
print("hello world!")運(yùn)行后,打印輸出信息, 獲取得到的返回值為 0 。 正常結(jié)束、且不執(zhí)行返回碼的進(jìn)程,返回值為 0。
hello world! Return code: 0
1.3.2、sys.exit(status: int)
繼續(xù)修改前述測(cè)試代碼,調(diào)用 sys.exit 中斷 func 函數(shù)執(zhí)行
# return 0
# raise Exception("An exception occurred.")
sys.exit(2)運(yùn)行進(jìn)程測(cè)試腳本,func程序提前退出,但執(zhí)行了try…finally… 的部分,且進(jìn)程返回碼為指定值。
C:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Finally block executed. C:\Users\admin\Desktop\process_popen\problematic_subprocess.py: Exiting with return code: 0 Return code: 2
1.3.3、os._exit(status: int)
繼續(xù)修改前述測(cè)試代碼,調(diào)用 os._exit 中斷 func 函數(shù)執(zhí)行
# return 0
# raise Exception("An exception occurred.")
# sys.exit(2)
os._exit(3)
執(zhí)行進(jìn)程測(cè)試腳本,func程序退出,沒有執(zhí)行了try…finally… 的部分,進(jìn)程返回碼為指定值
Return code: 3
1.3.4、os.abort()
abort比較特殊,主要是發(fā)送終止信號(hào),由于這里沒有編寫信號(hào)處理部分,不做討論。
繼續(xù)修改前述測(cè)試代碼,調(diào)用 os.abort() 中斷 func 函數(shù)執(zhí)行
# return 0
# raise Exception("An exception occurred.")
# sys.exit(2)
# os._exit(3)
os.abort()
執(zhí)行進(jìn)程測(cè)試腳本,func程序退出,沒有執(zhí)行了try…finally… 的部分,且返回指為一個(gè)很大的數(shù)值
Return code: 3221226505
1.4、sys.exit()、os._exit()與os.abort()對(duì)比表
| 特性 | sys.exit() | os._exit() | os.abort() |
|---|---|---|---|
| 模塊來源 | sys 模塊 | os 模塊 | os 模塊 |
| 實(shí)現(xiàn)機(jī)制 | 拋出 SystemExit 異常 | 直接調(diào)用系統(tǒng) _exit() 函數(shù) | 發(fā)送 SIGABRT 信號(hào) |
| 清理操作 | ? 執(zhí)行(finally 塊、上下文管理器等) | ? 不執(zhí)行任何清理 | ? 不執(zhí)行任何清理 |
| 可被捕獲 | ? 可通過捕獲 SystemExit 異常 | ? 無法捕獲 | ? 無法捕獲 |
| 退出碼控制 | ? 可指定(默認(rèn) 0) | ? 可指定 | ? 固定(通常為 134) |
| 核心轉(zhuǎn)儲(chǔ) | ? 不生成 | ? 不生成 | ? 可能生成(取決于系統(tǒng)配置) |
| 適用場(chǎng)景 | 正常程序退出、腳本終止 | 子進(jìn)程退出、避免資源泄漏 | 嚴(yán)重錯(cuò)誤、調(diào)試崩潰 |
| 跨平臺(tái)一致性 | ? 高 | ? 高 | ?? 行為可能因平臺(tái)而異 |
| Pythonic 程度 | ? 官方推薦方式 | ?? 特殊場(chǎng)景使用 | ?? 調(diào)試/異常場(chǎng)景使用 |
| 對(duì)緩沖區(qū)影響 | ? 正常刷新 stdout/stderr | ? 可能丟失未刷新的輸出 | ? 可能丟失未刷新的輸出 |
使用建議
- 日常開發(fā):優(yōu)先使用
sys.exit() - 多進(jìn)程編程:子進(jìn)程中使用
os._exit()避免干擾父進(jìn)程 - 調(diào)試/崩潰分析:使用
os.abort()觸發(fā)核心轉(zhuǎn)儲(chǔ)進(jìn)行事后分析
2、進(jìn)程調(diào)用與輸出解析
2.1、進(jìn)程調(diào)用
注意,這里在使用popen打開進(jìn)程,使用了2個(gè)線程讀取標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤輸出, 將原進(jìn)程輸出分別重定向到對(duì)應(yīng)的日志文件中。日志的的寫入,使用了logging庫。最后等待進(jìn)程退出。
日志控制中,可以要求按照指定格式進(jìn)行正則化匹配,例如 進(jìn)度,程序運(yùn)行結(jié)果,程序異常的結(jié)果等。自行擴(kuò)展。
import subprocess
import threading
import logging
def setup_loggers(stdout_log_file="stdout.log", stderr_log_file="stderr.log"):
"""
為 stdout 和 stderr 創(chuàng)建獨(dú)立的日志記錄器
"""
# 配置日志格式
log_format = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 設(shè)置 stdout 日志記錄器
stdout_logger = logging.getLogger("STDOUT")
stdout_logger.setLevel(logging.INFO)
stdout_handler = logging.FileHandler(stdout_log_file, mode='w', encoding='utf-8')
stdout_handler.setFormatter(log_format)
stdout_logger.addHandler(stdout_handler)
# 設(shè)置 stderr 日志記錄器
stderr_logger = logging.getLogger("STDERR")
stderr_logger.setLevel(logging.ERROR)
stderr_handler = logging.FileHandler(stderr_log_file, mode='w', encoding='utf-8')
stderr_handler.setFormatter(log_format)
stderr_logger.addHandler(stderr_handler)
# 防止日志傳播到根記錄器
stdout_logger.propagate = False
stderr_logger.propagate = False
return stdout_logger, stderr_logger
def write_stream_to_logger(stream, logger, level=logging.INFO):
"""將流中的數(shù)據(jù)實(shí)時(shí)寫入日志記錄器"""
print(f"write_stream_to_logger: {logger.name}")
for line in stream:
# 去除行尾的換行符
log_message = line.rstrip('\n\r')
logger.log(level, log_message)
def run_process_and_log_output(command, stdout_file="stdout.txt", stderr_file="stderr.txt"):
"""
使用 Popen 執(zhí)行命令,并實(shí)時(shí)將標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤寫入指定文件,
無論進(jìn)程是否正常結(jié)束。
"""
# 設(shè)置日志記錄器
stdout_logger, stderr_logger = setup_loggers(stdout_file, stderr_file)
try:
print(f"Starting process: {command}")
# 啟動(dòng)子進(jìn)程
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
# bufsize=1,
universal_newlines=True,
shell=isinstance(command, str)
)
# 輸出進(jìn)程相關(guān)信息
print(f"Process started with PID: {process.pid}")
# 創(chuàng)建線程來處理 stdout 和 stderr
stdout_thread = threading.Thread(
target=write_stream_to_logger,
args=(process.stdout, stdout_logger, logging.INFO)
)
stderr_thread = threading.Thread(
target=write_stream_to_logger,
args=(process.stderr, stderr_logger, logging.ERROR)
)
# 啟動(dòng)線程
stdout_thread.start()
stderr_thread.start()
# 等待進(jìn)程結(jié)束
return_code = process.wait()
# 等待線程完成
stdout_thread.join()
stderr_thread.join()
print(f"Process finished with return code: {return_code}")
return return_code
except Exception as e:
print(f"An error occurred: {e}")
with open(stdout_file, 'w', encoding='utf-8') as f:
f.write("")
with open(stderr_file, 'w', encoding='utf-8') as f:
f.write(f"An error occurred: {e}")
return -2
# raise
finally:
# 確保文件被關(guān)閉
if process.stdout:
process.stdout.close()
if process.stderr:
process.stderr.close()
# 示例用法
if __name__ == "__main__":
problematic_cmd = ["python", "problematic_subprocess.py"]
print("Running problematic subprocess...")
ret_code = run_process_and_log_output(problematic_cmd, "stdout.txt", "stderr.txt")
print(f"Problematic subprocess finished with return code: {ret_code}")
```
以上代碼中,不論前述problematic_subprocess.py是如何執(zhí)行,return_code = process.wait() 都會(huì)成功,接收到進(jìn)程的返回值。
例如在 func 中 拋出異常, 進(jìn)程不顯式指定返回指,那么進(jìn)程默認(rèn)返回值為0。 實(shí)際輸出也符合預(yù)期
```py
Running problematic subprocess...
Starting process: ['python', 'problematic_subprocess.py']
Process started with PID: 24808
write_stream_to_logger: STDOUT
write_stream_to_logger: STDERR
Process finished with return code: 0
Problematic subprocess finished with return code: 0其中2個(gè)文件內(nèi)容如下,也同樣滿足預(yù)期

2.2、進(jìn)程控制 psutil
進(jìn)程控制可以使用 psutil 庫
# 等待1s,通知退出
time.sleep(1)
import psutil
master_process = psutil.Process(process.pid)
if master_process.is_running():
print("Process is still running, sending SIGTERM...")
master_process.terminate()
time.sleep(1)
if master_process.is_running():
print("Process is still running, sending SIGKILL...")
master_process.kill()
time.sleep(1)
if master_process.is_running():
print("Process is still running, giving up...")
return -1
到此這篇關(guān)于python 異常、捕獲和退出,進(jìn)程調(diào)用輸出示例解析的文章就介紹到這了,更多相關(guān)python 異常捕獲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用py2exe在Windows下將Python程序轉(zhuǎn)為exe文件
這篇文章主要介紹了Windows下用py2exe將Python程序轉(zhuǎn)為exe文件的方法,注意py2exe只是負(fù)責(zé)文件格式的轉(zhuǎn)換,并不能將Python程序編譯為機(jī)器碼,要的朋友可以參考下2016-03-03
使用Python為Excel文件添加預(yù)設(shè)和自定義文檔屬性
向Excel文件添加文檔屬性是專業(yè)地組織和管理電子表格數(shù)據(jù)的關(guān)鍵步驟,這些屬性,如標(biāo)題、作者、主題和關(guān)鍵詞,增強(qiáng)了文件的元數(shù)據(jù),使得在大型數(shù)據(jù)庫或文件系統(tǒng)中跟蹤變得更加容易,本文將介紹如何使用Python高效地為Excel文件添加文檔屬性,需要的朋友可以參考下2024-05-05
Python梯度提升庫XGBoost解決機(jī)器學(xué)習(xí)問題使用探究
XGBoost是一個(gè)流行的梯度提升庫,特別適用于解決各種機(jī)器學(xué)習(xí)問題,它在性能和速度上表現(xiàn)出色,常被用于分類、回歸、排序、推薦系統(tǒng)等應(yīng)用,本文將介紹XGBoost的基本原理、核心功能以及一些詳細(xì)的示例代碼2024-01-01
python如何實(shí)現(xiàn)數(shù)組元素兩兩相加
這篇文章主要介紹了python如何實(shí)現(xiàn)數(shù)組元素兩兩相加,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
python nmap實(shí)現(xiàn)端口掃描器教程
這篇文章主要為大家詳細(xì)介紹了python nmap實(shí)現(xiàn)端口掃描器教程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
PyQt5 關(guān)于Qt Designer的初步應(yīng)用和打包過程詳解
Qt Designer中的操作方式十分靈活,其通過拖拽的方式放置控件可以隨時(shí)查看控件效果。這篇文章主要介紹了PyQt5 關(guān)于Qt Designer的初步應(yīng)用和打包,需要的朋友可以參考下2021-09-09
django將網(wǎng)絡(luò)中的圖片,保存成model中的ImageField的實(shí)例
今天小編就為大家分享一篇django將網(wǎng)絡(luò)中的圖片,保存成model中的ImageField的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08

