題目與範圍
Python 3.14 增加了用於讀取延遲註解的 annotationlib。載入器需要支援前向引用,避免發現階段執行任意註解表達式,並為舊 Python 保留相容路徑。核心能力是執行時內省與副作用控制,因此分類為 coding。
面試官考察點
應區分讀取註解與求值註解,依消費者選擇 VALUE、FORWARDREF 或 STRING,並明確可信程式碼何時可以求值。還要討論快取失效、模組重載、巢狀型別別名和版本化回退,不能依賴直譯器私有屬性。
先釐清的問題
- 載入的是可信應用程式碼還是第三方外掛?
- Schema 產生器需要執行時物件、字串還是未解析引用?
- 需要支援哪些 Python 版本?
- 註解表達式是否允許匯入模組或呼叫函式?
- 外掛重載後如何報告、快取和重試失敗?
- 註解資料是否屬於公開相容契約?
30 秒答題框架
「先停止呼叫總是求值註解的輔助函式。Python 3.14 上對不可信發現使用 annotationlib.get_annotations() 的 Format.FORWARDREF 或 Format.STRING,只在明確可信階段使用 Format.VALUE。將結果正規化為內部 Schema,按物件隔離失敗,並對舊版本保留版本判斷的回退。測試涵蓋前向引用、可選匯入、巢狀別名、重載和確定性錯誤。」
分步作答
步驟 1:分離發現與求值
定義兩個階段:發現階段記錄名稱或 ForwardRef,不解析;解析階段只處理批准的模組和符號。這樣 Schema 發現不會因註解存在而匯入可選依賴或執行表達式。
步驟 2:選擇註解格式
Schema 需要來源文字時用 Format.STRING,需要結構化未解析名稱時用 Format.FORWARDREF,只有確實需要執行時物件且程式碼可信時才用 Format.VALUE。把格式選擇作為載入器 API 契約的一部分。
步驟 3:隔離失敗與副作用
按函式或類別捕獲解析錯誤,診斷中附帶物件路徑和格式,同時繼續發現獨立外掛。不要把未解析引用靜默轉為 Any,否則相容缺陷會被隱藏。匯入和符號求值前執行允許列表檢查。
步驟 4:加入版本相容
Python 3.14 及以上使用文件化的 annotationlib API。舊版本使用專案已有相容層並記錄較弱保證。不要把私有 annotate 或直譯器內部結構當成長期介面。
步驟 5:測試確定性和生命週期
測試引用後定義、缺失模組、巢狀泛型別名、字串和模組重載。斷言發現階段不會匯入被阻止的模組,重複讀取產生相同正規化 Schema,且外掛身分或原始碼變化時快取失效。
參考答案
「問題是階段混用:發現階段求值了本應只描述 Schema 的資料。我會使用 Python 3.14 的文件化 annotationlib,對不可信內省選擇 STRING 或 FORWARDREF,只在明確可信解析器中使用 VALUE。每個物件隔離診斷,保留未解析引用,並用允許列表控制求值。版本適配器保持舊 Python 行為,測試涵蓋前向引用、缺失可選模組、別名、重載、副作用匯入和穩定輸出。」
常見錯誤
- 總是求值註解 → 發現階段執行可選匯入和表達式 → 分離格式與階段。
- 把失敗轉成
Any→ Schema 漂移被靜默掩蓋 → 保留未解析引用和診斷。 - 使用直譯器私有欄位 → 升級破壞載入器 → 依賴文件化
annotationlib。 - 共用全域快取 → 重載後回傳舊 Schema → 按外掛身分和原始碼建立失效規則。
- 給所有外掛完整求值權限 → 不可信程式碼產生副作用 → 使用允許列表和可信階段。
- 只測簡單內建型別 → 前向引用在生產失敗 → 涵蓋缺失名稱和巢狀別名。
追問
追問 1:何時適合使用 VALUE?
消費者需要執行時型別物件,且模組和註解表達式可信時。它應屬於明確解析階段,不應成為預設發現行為。
追問 2:為什麼保留 FORWARDREF 而不是字串?
它以結構化值保留未解析引用,讓後續解析器區分缺失名稱與普通字串註解。
追問 3:Python 3.14 改變了什麼?
註解預設延遲求值,並由 annotationlib 提供文件化讀取格式。依賴立即求值的程式碼必須明確目標格式。
追問 4:如何支援舊版本?
透過版本判斷適配專案已有回退,記錄差異,並在所有支援的直譯器上執行相同契約測試。