感測資料出錯怎麼辦?用例外處理守住智慧生活程式
錯誤不一定能避免,但可以被辨識、記錄、隔離,並交給正確的人處理。
完成本篇後,你能辨認常見例外,正確使用
try、except、else、finally 與 raise,建立自訂例外,並讓整批巡查資料在單筆失敗時仍能繼續處理。一、錯誤不是同一種錯誤
程式可能因語法、資料或執行環境而失敗。例外處理主要面對「語法正確,但執行時發生的異常狀況」。
| 類型 | 例子 | 處理方向 |
|---|---|---|
| 語法錯誤 | 漏掉冒號、括號不成對 | 先修改程式,不能靠 try 掩蓋 |
| 資料錯誤 | "故障" 無法轉成浮點數 | 驗證輸入並捕捉特定例外 |
| 資源錯誤 | 檔案不存在、網路逾時 | 提供重試、替代流程或清楚訊息 |
| 程式設計錯誤 | 變數名稱拼錯、邏輯判斷錯誤 | 修正與測試,不應靜默忽略 |
二、先看沒有保護的程式
raw_value = input("請輸入水溫:")
temperature = float(raw_value)
print(f"本次讀值:{temperature}°C")如果輸入「感測器故障」,float() 會產生 ValueError,程式立即中止。
三、try 與 except:接住預期中的問題
raw_value = input("請輸入水溫:")
try:
temperature = float(raw_value)
except ValueError:
print("讀值不是有效數字,請檢查輸入或感測器。")
else:
print(f"本次讀值:{temperature}°C")try 只放可能失敗的程式;except ValueError 只處理預期的轉換錯誤;else 則在沒有發生例外時執行。
四、分別處理不同例外
缺少字典欄位會產生 KeyError,內容不能轉成數字則是 ValueError 或 TypeError。分開處理,訊息才有診斷價值。
def parse_reading(record):
try:
site_name = record["場域"]
temperature = float(record["水溫"])
ph = float(record["pH"])
except KeyError as error:
missing_key = error.args[0]
return None, f"缺少欄位:{missing_key}"
except (TypeError, ValueError) as error:
return None, f"數值格式錯誤:{error}"
else:
reading = {
"場域": site_name,
"水溫": temperature,
"pH": ph,
}
return reading, None
data = {"場域": "示範池 A", "水溫": "27.2", "pH": "7.4"}
reading, error = parse_reading(data)
print(reading if error is None else error)五、finally:無論成功失敗都要收尾
finally 適合釋放資源,例如關閉設備連線。實際檔案處理優先使用 with,因為它會自動管理關閉動作。
class DemoConnection:
def close(self):
print("連線已關閉")
connection = DemoConnection()
try:
print("讀取示範感測資料")
value = float("27.2")
print(value)
except ValueError:
print("讀值格式錯誤")
finally:
connection.close()try 嘗試工作;except 處理失敗;else 處理成功後的工作;finally 無論如何都收尾。不是每次都要四段全部使用。六、raise:主動拒絕不合理資料
資料雖然可以轉成數字,仍可能不符合基本格式範圍。函式可主動拋出例外,要求呼叫端處理。
def validate_ph(value):
ph = float(value)
if not 0.0 <= ph <= 14.0:
raise ValueError("pH 讀值超出 0 到 14 的理論範圍")
return ph
try:
ph = validate_ph("15.2")
except ValueError as error:
print("資料未採用:", error)
else:
print("pH 格式驗證完成:", ph)這裡只驗證 pH 的基本理論範圍,不代表養殖安全門檻。業務規則應由養殖戶與專業人員另行確認。
七、建立有意義的自訂例外
自訂例外能區分「設備離線」與一般數值錯誤,也讓上層程式決定是否重試、通知或略過。
class SensorError(Exception):
"""感測器相關錯誤的基底類別。"""
class SensorOfflineError(SensorError):
"""感測器目前無法連線。"""
class InvalidReadingError(SensorError):
"""感測值存在,但內容不可信。"""
def read_demo_sensor(sensor_id, status, raw_value):
if status != "online":
raise SensorOfflineError(f"{sensor_id} 離線")
try:
return float(raw_value)
except (TypeError, ValueError) as error:
raise InvalidReadingError(
f"{sensor_id} 讀值無效:{raw_value!r}"
) from error
try:
value = read_demo_sensor("TEMP-01", "online", "故障")
except SensorOfflineError as error:
print("請檢查連線:", error)
except InvalidReadingError as error:
print("請檢查感測器:", error)raise ... from error 保留原始錯誤原因,除錯時能看到例外之間的關聯。
八、讓單筆失敗不拖垮整批巡查
批次處理時,把 try 放在迴圈內,個別資料失敗後仍可繼續;同時把成功資料與錯誤紀錄分開保存。
def parse_record(record):
site = record["場域"]
temperature = float(record["水溫"])
ph = float(record["pH"])
if not 0.0 <= ph <= 14.0:
raise ValueError("pH 超出理論範圍")
return {
"場域": site,
"水溫": temperature,
"pH": ph,
}
records = [
{"場域": "示範池 A", "水溫": "27.2", "pH": "7.4"},
{"場域": "示範池 B", "水溫": "離線", "pH": "7.1"},
{"場域": "示範池 C", "水溫": "26.8"},
]
valid_records = []
error_log = []
for index, record in enumerate(records, start=1):
try:
valid_records.append(parse_record(record))
except KeyError as error:
error_log.append(f"第 {index} 筆缺少欄位:{error.args[0]}")
except (TypeError, ValueError) as error:
error_log.append(f"第 {index} 筆格式錯誤:{error}")
print("成功筆數:", len(valid_records))
print("失敗筆數:", len(error_log))
for message in error_log:
print("-", message)九、檔案不存在時怎麼辦?
讀取設定檔時,處理可預期的 FileNotFoundError 與 json.JSONDecodeError。不要用空白的 except: 把所有錯誤吞掉。
import json
from pathlib import Path
def load_config(filename):
path = Path(filename)
try:
with path.open("r", encoding="utf-8") as file:
return json.load(file)
except FileNotFoundError:
print(f"找不到設定檔:{path}")
except json.JSONDecodeError as error:
print(f"JSON 格式錯誤,行 {error.lineno}:{error.msg}")
return None
config = load_config("site_config.json")
if config is None:
print("停止自動控制,改由人員確認設定。")十、記錄錯誤,不只 print
正式程式可使用標準函式庫 logging,保留時間、等級與訊息。公開紀錄前應移除帳號、Token、精確位置及個人資料。
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
)
try:
value = float("離線")
except ValueError:
logging.exception("示範感測值轉換失敗")
else:
logging.info("示範感測值已完成轉換:%s", value)十一、不要這樣捕捉例外
# 不建議:錯誤種類與原因全部消失
try:
value = float("離線")
except:
pass若確實需要在系統邊界捕捉未知例外,可用 except Exception as error 記錄後再拋出,或進入明確的安全狀態;不要假裝工作成功。
十二、例外處理決策表
| 問題 | 建議動作 | 不建議 |
|---|---|---|
| 使用者輸入格式錯誤 | 說明格式並允許重新輸入 | 默認為 0 |
| 單一感測器離線 | 標記未知、記錄、通知並繼續其他設備 | 沿用舊值卻不標示 |
| 關鍵設定檔損壞 | 停止自動控制並要求人工確認 | 自行猜測控制參數 |
| 程式設計錯誤 | 記錄完整資訊、測試並修正 | 用空白 except 隱藏 |
十三、USR 實作挑戰
建立十筆含空白、缺欄與錯誤文字的教學資料,輸出成功清單與錯誤報告,確認任何單筆問題都不會讓整批中止。
設計感測器離線時的流程:標示未知、停止自動判斷、通知巡查人員,但仍顯示其他設備的有效讀值。
請養殖戶協助檢視錯誤訊息是否看得懂、是否知道下一步怎麼做,再依回饋修改文字。
十四、與生成式 AI 協作
請擔任 Python 例外處理與安全設計助教。
請檢查我的智慧養殖資料處理程式:
1. 指出可能出現的具體例外;
2. 只捕捉可以合理處理的例外;
3. 不可使用 except: pass;
4. 單筆錯誤不能拖垮整批資料;
5. 無效讀值必須標示未知,不可假裝正常;
6. 建議應記錄哪些資訊,以及哪些敏感資訊不可寫入日誌。
請先說明風險,再提供修正版程式。好的例外處理不是讓程式「永遠不停止」,而是讓失敗變得可辨識、可追查且可安全處置。捕捉範圍愈精準,錯誤訊息愈具體,現場人員愈能做出正確決定。
沒有留言:
張貼留言