<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Optimization on Tarragon</title><link>https://tarrragon.github.io/blog/tags/optimization/</link><description>Recent content in Optimization on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/optimization/index.xml" rel="self" type="application/rss+xml"/><item><title>Flash Attention</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/flash-attention/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/flash-attention/</guid><description>&lt;p>Flash Attention 的核心概念是「重新組織 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">Attention&lt;/a> 計算的順序、把中間結果留在 GPU 高速 cache、減少對 GPU memory 的讀寫往返」。它不改變 attention 的數學定義（輸出跟原始實作在浮點誤差範圍內一致）、但實作層面對長 context 推論吞吐有明顯提升、且是部分 &lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">KV cache 量化&lt;/a> 組合在 llama.cpp 上的必要前置。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Flash Attention 在推論架構中的角色：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">推論時的 attention 計算：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ├── 原始實作：Q · K^T 整個算完、寫進 memory、再讀出來做 softmax、再算 · V
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> │ └── 多次 memory 讀寫、長 context 下 IO 成為瓶頸
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> └── Flash Attention：用 tiling 把計算切塊、中間結果留在 SRAM / register
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> └── 減少 memory 讀寫、長 context 加速明顯&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跟 attention 變體的關係：&lt;/p>
&lt;ul>
&lt;li>Flash Attention 是&lt;strong>實作層&lt;/strong>的優化、跟 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">MHA / GQA / MLA&lt;/a> 等&lt;strong>架構層&lt;/strong>變體是兩個獨立維度。&lt;/li>
&lt;li>不同變體都能搭配 Flash Attention 的實作技巧。&lt;/li>
&lt;/ul>
&lt;p>在 llama.cpp 中的旗標：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">llama-server -fa &lt;span class="c1"># 啟用 flash attention&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># 或&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">llama-server --flash-attn&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：Flash Attention 的版本演進快（Flash Attention 1 / 2 / 3）、不同推論引擎的支援度依版本變化。具體限制（如「V cache Q4 量化要 -fa 才能啟用」）依 llama.cpp 版本變動、引用前以 &lt;code>llama-server --help&lt;/code> 跟 release notes 為準。&lt;/p>&lt;/blockquote>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 Flash Attention 後可以解釋兩個現象：為什麼啟用 &lt;code>-fa&lt;/code> 後長 context 推論速度提升明顯（IO bound 變 compute bound）、為什麼部分 KV cache 量化組合（如 V=Q4_0）在 llama.cpp 上需要 flash attention 才能跑（實作層面的耦合）。&lt;/p>
&lt;p>工程實務上、啟用 flash attention 通常沒副作用（數學上等價、品質不變）、是 PC 場景長 context 推論的預設啟用旗標。詳見 &lt;a href="https://tarrragon.github.io/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2 KV cache 量化策略&lt;/a> 的 flash attention 段落。&lt;/p></description><content:encoded><![CDATA[<p>Flash Attention 的核心概念是「重新組織 <a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">Attention</a> 計算的順序、把中間結果留在 GPU 高速 cache、減少對 GPU memory 的讀寫往返」。它不改變 attention 的數學定義（輸出跟原始實作在浮點誤差範圍內一致）、但實作層面對長 context 推論吞吐有明顯提升、且是部分 <a href="/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">KV cache 量化</a> 組合在 llama.cpp 上的必要前置。</p>
<h2 id="概念位置">概念位置</h2>
<p>Flash Attention 在推論架構中的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">推論時的 attention 計算：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  ├── 原始實作：Q · K^T 整個算完、寫進 memory、再讀出來做 softmax、再算 · V
</span></span><span class="line"><span class="ln">3</span><span class="cl">  │     └── 多次 memory 讀寫、長 context 下 IO 成為瓶頸
</span></span><span class="line"><span class="ln">4</span><span class="cl">  └── Flash Attention：用 tiling 把計算切塊、中間結果留在 SRAM / register
</span></span><span class="line"><span class="ln">5</span><span class="cl">        └── 減少 memory 讀寫、長 context 加速明顯</span></span></code></pre></div><p>跟 attention 變體的關係：</p>
<ul>
<li>Flash Attention 是<strong>實作層</strong>的優化、跟 <a href="/blog/llm/knowledge-cards/attention/" data-link-title="Attention" data-link-desc="Transformer 內部讓每個 token 對其他 token 加權平均的核心機制、形成 KV cache 跟 context window 的計算基礎">MHA / GQA / MLA</a> 等<strong>架構層</strong>變體是兩個獨立維度。</li>
<li>不同變體都能搭配 Flash Attention 的實作技巧。</li>
</ul>
<p>在 llama.cpp 中的旗標：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">llama-server -fa  <span class="c1"># 啟用 flash attention</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 或</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">llama-server --flash-attn</span></span></code></pre></div><blockquote>
<p><strong>事實查核註</strong>：Flash Attention 的版本演進快（Flash Attention 1 / 2 / 3）、不同推論引擎的支援度依版本變化。具體限制（如「V cache Q4 量化要 -fa 才能啟用」）依 llama.cpp 版本變動、引用前以 <code>llama-server --help</code> 跟 release notes 為準。</p></blockquote>
<h2 id="設計責任">設計責任</h2>
<p>理解 Flash Attention 後可以解釋兩個現象：為什麼啟用 <code>-fa</code> 後長 context 推論速度提升明顯（IO bound 變 compute bound）、為什麼部分 KV cache 量化組合（如 V=Q4_0）在 llama.cpp 上需要 flash attention 才能跑（實作層面的耦合）。</p>
<p>工程實務上、啟用 flash attention 通常沒副作用（數學上等價、品質不變）、是 PC 場景長 context 推論的預設啟用旗標。詳見 <a href="/blog/llm/05-discrete-gpu/kv-cache-quantization-strategy/" data-link-title="5.2 KV cache 量化策略" data-link-desc="PC 場景用 K=Q8 / V=Q4 等量化把 KV cache 壓縮、騰出 VRAM 開大 context window 或加併發數的判讀">5.2 KV cache 量化策略</a> 的 flash attention 段落。</p>
]]></content:encoded></item><item><title>Prefix Cache</title><link>https://tarrragon.github.io/blog/llm/knowledge-cards/prefix-cache/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/knowledge-cards/prefix-cache/</guid><description>&lt;p>Prefix Cache 的核心概念是「當多個請求共用相同的前綴 prompt（如同一 system prompt、同一 few-shot 範例）、把該前綴的 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache&lt;/a> 算一次、後續請求共用、省下重複 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill&lt;/a> 算力」。是 production LLM 服務的常見優化、能大幅降低 latency 跟成本；但在多租戶場景下、跨租戶共用 prefix cache 是直接的隱私洩漏面。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Prefix Cache 在推論流程中的角色：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">傳統推論：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> Request A：system prompt + user A → 完整 prefill → 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> Request B：system prompt + user B → 完整 prefill → 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↑ 重複算 system prompt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">開啟 Prefix Cache：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> Request A：system prompt + user A → prefill 整段、cache 共用 prefix
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> Request B：system prompt + user B → 重用 cache 的 system prefix + 只 prefill user B → 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> ↑ 省下 system prompt 的 prefill 算力&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>效益對應的場景：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>效益&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>同 system prompt、不同 user message&lt;/td>
 &lt;td>prefill 算力大幅省&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>同 few-shot 例子、不同 query&lt;/td>
 &lt;td>prefill 算力大幅省&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>長 RAG context 共用、不同問題&lt;/td>
 &lt;td>prefill 算力大幅省&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>完全獨立的請求（無共用前綴）&lt;/td>
 &lt;td>無效益&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>主流推論引擎的支援度（依版本變化）：vLLM、SGLang、llama.cpp 等都有 prefix cache 機制、命名各異。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>事實查核註&lt;/strong>：prefix cache 的命名、設定方式、tenant 隔離預設行為依推論引擎跟版本差異大、引用前以對應引擎的官方文件為準（如 &lt;a href="https://docs.vllm.ai/">vLLM Automatic Prefix Caching&lt;/a>、SGLang RadixAttention 等）。&lt;/p>&lt;/blockquote>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>理解 prefix cache 後可以解釋兩個現象：為什麼 production LLM 服務的 latency 在啟用 prefix cache 後大幅下降（system prompt 不再每次重算）、為什麼 prefix cache 在多租戶場景是隱私風險（A 租戶的 prefix 可能被 B 看到、見 &lt;a href="https://tarrragon.github.io/blog/backend/07-security-data-protection/llm-multi-tenant-isolation/" data-link-title="LLM 多租戶推論隔離" data-link-desc="production LLM 服務的多租戶隔離：KV cache 不共享、log / model artifact 隔離、跨用戶 prompt 洩漏面">llm-multi-tenant-isolation&lt;/a>）。&lt;/p>
&lt;p>production 設計時、prefix cache 應該按 tenant 分桶、同 tenant 內可共用、跨 tenant 必須隔離。隔離邊界對齊 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant-boundary&lt;/a> 卡片的設計。&lt;/p></description><content:encoded><![CDATA[<p>Prefix Cache 的核心概念是「當多個請求共用相同的前綴 prompt（如同一 system prompt、同一 few-shot 範例）、把該前綴的 <a href="/blog/llm/knowledge-cards/kv-cache/" data-link-title="KV Cache" data-link-desc="已處理 token 的 attention 中間結果暫存：避免重算、加速後續生成">KV cache</a> 算一次、後續請求共用、省下重複 <a href="/blog/llm/knowledge-cards/prefill/" data-link-title="Prefill" data-link-desc="Prompt 首次處理時的計算階段：把整段輸入跑過模型、產生 KV cache">prefill</a> 算力」。是 production LLM 服務的常見優化、能大幅降低 latency 跟成本；但在多租戶場景下、跨租戶共用 prefix cache 是直接的隱私洩漏面。</p>
<h2 id="概念位置">概念位置</h2>
<p>Prefix Cache 在推論流程中的角色：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">傳統推論：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  Request A：system prompt + user A → 完整 prefill → 生成
</span></span><span class="line"><span class="ln">3</span><span class="cl">  Request B：system prompt + user B → 完整 prefill → 生成
</span></span><span class="line"><span class="ln">4</span><span class="cl">                                       ↑ 重複算 system prompt
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">開啟 Prefix Cache：
</span></span><span class="line"><span class="ln">7</span><span class="cl">  Request A：system prompt + user A → prefill 整段、cache 共用 prefix
</span></span><span class="line"><span class="ln">8</span><span class="cl">  Request B：system prompt + user B → 重用 cache 的 system prefix + 只 prefill user B → 生成
</span></span><span class="line"><span class="ln">9</span><span class="cl">                                       ↑ 省下 system prompt 的 prefill 算力</span></span></code></pre></div><p>效益對應的場景：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>效益</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同 system prompt、不同 user message</td>
          <td>prefill 算力大幅省</td>
      </tr>
      <tr>
          <td>同 few-shot 例子、不同 query</td>
          <td>prefill 算力大幅省</td>
      </tr>
      <tr>
          <td>長 RAG context 共用、不同問題</td>
          <td>prefill 算力大幅省</td>
      </tr>
      <tr>
          <td>完全獨立的請求（無共用前綴）</td>
          <td>無效益</td>
      </tr>
  </tbody>
</table>
<p>主流推論引擎的支援度（依版本變化）：vLLM、SGLang、llama.cpp 等都有 prefix cache 機制、命名各異。</p>
<blockquote>
<p><strong>事實查核註</strong>：prefix cache 的命名、設定方式、tenant 隔離預設行為依推論引擎跟版本差異大、引用前以對應引擎的官方文件為準（如 <a href="https://docs.vllm.ai/">vLLM Automatic Prefix Caching</a>、SGLang RadixAttention 等）。</p></blockquote>
<h2 id="設計責任">設計責任</h2>
<p>理解 prefix cache 後可以解釋兩個現象：為什麼 production LLM 服務的 latency 在啟用 prefix cache 後大幅下降（system prompt 不再每次重算）、為什麼 prefix cache 在多租戶場景是隱私風險（A 租戶的 prefix 可能被 B 看到、見 <a href="/blog/backend/07-security-data-protection/llm-multi-tenant-isolation/" data-link-title="LLM 多租戶推論隔離" data-link-desc="production LLM 服務的多租戶隔離：KV cache 不共享、log / model artifact 隔離、跨用戶 prompt 洩漏面">llm-multi-tenant-isolation</a>）。</p>
<p>production 設計時、prefix cache 應該按 tenant 分桶、同 tenant 內可共用、跨 tenant 必須隔離。隔離邊界對齊 <a href="/blog/backend/knowledge-cards/tenant-boundary/" data-link-title="Tenant Boundary" data-link-desc="說明多租戶系統如何隔離不同客戶或組織的資料與資源">tenant-boundary</a> 卡片的設計。</p>
]]></content:encoded></item><item><title>8.1 並行處理實戰</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/</guid><description>&lt;p>本章將入門系列學到的 &lt;code>concurrent.futures&lt;/code> 知識，應用於 &lt;code>.claude/lib&lt;/code> 中的真實程式碼，展示如何識別並行化機會、實作並行版本，以及測量效能差異。&lt;/p>
&lt;h2 id="學習目標">學習目標&lt;/h2>
&lt;p>完成本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>識別並行化機會&lt;/strong>：判斷一段程式碼是否適合並行化&lt;/li>
&lt;li>&lt;strong>應用 ThreadPoolExecutor&lt;/strong>：將循序處理改寫為並行版本&lt;/li>
&lt;li>&lt;strong>實作進度報告&lt;/strong>：使用 &lt;code>as_completed()&lt;/code> 追蹤任務完成狀態&lt;/li>
&lt;li>&lt;strong>處理並行錯誤&lt;/strong>：避免單一任務失敗影響整體執行&lt;/li>
&lt;li>&lt;strong>測量效能差異&lt;/strong>：用數據證明優化效果&lt;/li>
&lt;/ol>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>本章假設你已經讀過入門系列的並行處理章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a> - &lt;code>concurrent.futures&lt;/code> 基本用法&lt;/li>
&lt;/ul>
&lt;p>如果你對 &lt;code>ThreadPoolExecutor&lt;/code>、&lt;code>executor.map()&lt;/code>、&lt;code>as_completed()&lt;/code> 還不熟悉，建議先閱讀該章節。&lt;/p>
&lt;hr>
&lt;h2 id="識別並行化機會">識別並行化機會&lt;/h2>
&lt;h3 id="io-密集-vs-cpu-密集快速判斷法">I/O 密集 vs CPU 密集：快速判斷法&lt;/h3>
&lt;p>在入門系列中，我們學到 I/O 密集任務適合 &lt;code>ThreadPoolExecutor&lt;/code>。但實際程式碼中，如何快速判斷？&lt;/p>
&lt;h4 id="問自己這個問題程式在等什麼">問自己這個問題：程式在等什麼？&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 模式 1：等待外部資源（I/O 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等網路&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 等磁碟&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cursor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">execute&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 等資料庫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 模式 2：純計算（CPU 密集）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10_000_000&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 沒有等待，純運算&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="獨立任務的識別">獨立任務的識別&lt;/h3>
&lt;p>並行化的前提是&lt;strong>任務之間互相獨立&lt;/strong>。識別方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">獨立性檢查清單：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">[ ] 任務之間沒有共享可變狀態？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">[ ] 任務執行順序不影響結果？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">[ ] 任務 B 不依賴任務 A 的輸出？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">如果三個都勾選，就適合並行化。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="真實案例markdown_link_checkerpy">真實案例：markdown_link_checker.py&lt;/h3>
&lt;p>讓我們看一個真實的例子。以下是 &lt;code>markdown_link_checker.py&lt;/code> 中的 &lt;code>check_directory()&lt;/code> 方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 原始版本：循序處理&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查目錄下所有 Markdown 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 循序檢查每個檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)))&lt;/span> &lt;span class="c1"># 一個接一個&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式碼適合並行化嗎？讓我們用檢查清單分析：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>問題&lt;/th>
 &lt;th>分析&lt;/th>
 &lt;th>結論&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>任務之間有共享狀態？&lt;/td>
 &lt;td>&lt;code>results&lt;/code> 只在主執行緒操作&lt;/td>
 &lt;td>沒有&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>執行順序影響結果？&lt;/td>
 &lt;td>檢查檔案 A 不影響檔案 B&lt;/td>
 &lt;td>不影響&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>任務互相依賴？&lt;/td>
 &lt;td>每個檔案獨立檢查&lt;/td>
 &lt;td>不依賴&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>是 I/O 密集？&lt;/td>
 &lt;td>&lt;code>check_file()&lt;/code> 讀取檔案內容&lt;/td>
 &lt;td>是&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>結論：&lt;strong>非常適合並行化&lt;/strong>。&lt;/p>
&lt;hr>
&lt;h2 id="threadpoolexecutor-實戰">ThreadPoolExecutor 實戰&lt;/h2>
&lt;h3 id="步驟-1確認可並行化的函式">步驟 1：確認可並行化的函式&lt;/h3>
&lt;p>首先，確認被並行化的函式是「純函式」或至少沒有副作用：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查單個 Markdown 檔案的連結
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 特性：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸入：檔案路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸出：檢查結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 沒有修改外部狀態
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 沒有依賴執行順序
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 實作省略&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>check_file()&lt;/code> 滿足條件：&lt;/p>
&lt;ul>
&lt;li>只讀取檔案，不修改&lt;/li>
&lt;li>返回獨立的結果物件&lt;/li>
&lt;li>不依賴其他檔案的結果&lt;/li>
&lt;/ul>
&lt;h3 id="步驟-2改寫為並行版本">步驟 2：改寫為並行版本&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">concurrent.futures&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">as_completed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory_parallel&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">max_workers&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;並行檢查目錄下所有 Markdown 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 並行檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">ThreadPoolExecutor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_workers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">max_workers&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">executor&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 提交所有任務&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">futures&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">executor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">submit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)):&lt;/span> &lt;span class="n">md_file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">future&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">as_completed&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">futures&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">future&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers&lt;/h3>
&lt;p>&lt;code>max_workers&lt;/code> 的選擇影響效能：&lt;/p></description><content:encoded><![CDATA[<p>本章將入門系列學到的 <code>concurrent.futures</code> 知識，應用於 <code>.claude/lib</code> 中的真實程式碼，展示如何識別並行化機會、實作並行版本，以及測量效能差異。</p>
<h2 id="學習目標">學習目標</h2>
<p>完成本章後，你將能夠：</p>
<ol>
<li><strong>識別並行化機會</strong>：判斷一段程式碼是否適合並行化</li>
<li><strong>應用 ThreadPoolExecutor</strong>：將循序處理改寫為並行版本</li>
<li><strong>實作進度報告</strong>：使用 <code>as_completed()</code> 追蹤任務完成狀態</li>
<li><strong>處理並行錯誤</strong>：避免單一任務失敗影響整體執行</li>
<li><strong>測量效能差異</strong>：用數據證明優化效果</li>
</ol>
<h2 id="先備知識">先備知識</h2>
<p>本章假設你已經讀過入門系列的並行處理章節：</p>
<ul>
<li><a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a> - <code>concurrent.futures</code> 基本用法</li>
</ul>
<p>如果你對 <code>ThreadPoolExecutor</code>、<code>executor.map()</code>、<code>as_completed()</code> 還不熟悉，建議先閱讀該章節。</p>
<hr>
<h2 id="識別並行化機會">識別並行化機會</h2>
<h3 id="io-密集-vs-cpu-密集快速判斷法">I/O 密集 vs CPU 密集：快速判斷法</h3>
<p>在入門系列中，我們學到 I/O 密集任務適合 <code>ThreadPoolExecutor</code>。但實際程式碼中，如何快速判斷？</p>
<h4 id="問自己這個問題程式在等什麼">問自己這個問題：程式在等什麼？</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 模式 1：等待外部資源（I/O 密集）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>      <span class="c1"># 等網路</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">content</span> <span class="o">=</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>             <span class="c1"># 等磁碟</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>    <span class="c1"># 等資料庫</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 模式 2：純計算（CPU 密集）</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10_000_000</span><span class="p">))</span>  <span class="c1"># 沒有等待，純運算</span></span></span></code></pre></div><h3 id="獨立任務的識別">獨立任務的識別</h3>
<p>並行化的前提是<strong>任務之間互相獨立</strong>。識別方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">獨立性檢查清單：
</span></span><span class="line"><span class="ln">2</span><span class="cl">[ ] 任務之間沒有共享可變狀態？
</span></span><span class="line"><span class="ln">3</span><span class="cl">[ ] 任務執行順序不影響結果？
</span></span><span class="line"><span class="ln">4</span><span class="cl">[ ] 任務 B 不依賴任務 A 的輸出？
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">如果三個都勾選，就適合並行化。</span></span></code></pre></div><h3 id="真實案例markdown_link_checkerpy">真實案例：markdown_link_checker.py</h3>
<p>讓我們看一個真實的例子。以下是 <code>markdown_link_checker.py</code> 中的 <code>check_directory()</code> 方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 原始版本：循序處理</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查目錄下所有 Markdown 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 循序檢查每個檔案</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>  <span class="c1"># 一個接一個</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p>這段程式碼適合並行化嗎？讓我們用檢查清單分析：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>分析</th>
          <th>結論</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>任務之間有共享狀態？</td>
          <td><code>results</code> 只在主執行緒操作</td>
          <td>沒有</td>
      </tr>
      <tr>
          <td>執行順序影響結果？</td>
          <td>檢查檔案 A 不影響檔案 B</td>
          <td>不影響</td>
      </tr>
      <tr>
          <td>任務互相依賴？</td>
          <td>每個檔案獨立檢查</td>
          <td>不依賴</td>
      </tr>
      <tr>
          <td>是 I/O 密集？</td>
          <td><code>check_file()</code> 讀取檔案內容</td>
          <td>是</td>
      </tr>
  </tbody>
</table>
<p>結論：<strong>非常適合並行化</strong>。</p>
<hr>
<h2 id="threadpoolexecutor-實戰">ThreadPoolExecutor 實戰</h2>
<h3 id="步驟-1確認可並行化的函式">步驟 1：確認可並行化的函式</h3>
<p>首先，確認被並行化的函式是「純函式」或至少沒有副作用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    檢查單個 Markdown 檔案的連結
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    特性：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    - 輸入：檔案路徑
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    - 輸出：檢查結果
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 沒有修改外部狀態
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 沒有依賴執行順序
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># ... 實作省略</span></span></span></code></pre></div><p><code>check_file()</code> 滿足條件：</p>
<ul>
<li>只讀取檔案，不修改</li>
<li>返回獨立的結果物件</li>
<li>不依賴其他檔案的結果</li>
</ul>
<h3 id="步驟-2改寫為並行版本">步驟 2：改寫為並行版本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;並行檢查目錄下所有 Markdown 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 並行檢查</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)):</span> <span class="n">md_file</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers</h3>
<p><code>max_workers</code> 的選擇影響效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># I/O 密集任務：可以設定較高</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 經驗法則：CPU 核心數的 2-4 倍</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">max_workers</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 或根據檔案數量動態調整</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">get_optimal_workers</span><span class="p">(</span><span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據檔案數量決定 worker 數量&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 檔案少時不需要太多 worker</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="n">file_count</span><span class="p">,</span> <span class="n">cpu_count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 檔案多時，I/O 密集可以多開一些</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span></span></span></code></pre></div><h4 id="為什麼-io-密集可以超過-cpu-核心數">為什麼 I/O 密集可以超過 CPU 核心數？</h4>
<p>因為執行緒在等待 I/O 時會釋放 GIL，其他執行緒可以繼續執行。如果有 8 個核心，但每個任務有 80% 時間在等待 I/O，那開 16-32 個 worker 可以更充分利用 CPU。</p>
<hr>
<h2 id="進度報告與錯誤處理">進度報告與錯誤處理</h2>
<h3 id="使用-as_completed-報告進度">使用 as_completed() 報告進度</h3>
<p><code>as_completed()</code> 返回一個迭代器，任務完成時立即 yield，適合顯示進度：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    並行檢查目錄，支援進度回報
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        progress_callback: 回呼函式 (completed, total, current_file)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">md_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="c1"># 建立 future -&gt; 檔案 的映射</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">completed</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">completed</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">current_file</span> <span class="o">=</span> <span class="n">futures</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="c1"># 回報進度</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">current_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">current_file</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">percent</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">percent</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">% - </span><span class="si">{</span><span class="n">current_file</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="s2">&#34;docs/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="nb">print</span><span class="p">()</span>  <span class="c1"># 換行</span></span></span></code></pre></div><h3 id="錯誤處理不讓單一失敗拖垮全部">錯誤處理：不讓單一失敗拖垮全部</h3>
<p>並行處理時，單一任務的異常不應該中斷其他任務：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_robust</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">],</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行檢查，分開返回成功結果和錯誤
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        (成功的結果列表, 錯誤資訊列表)
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">file_path</span> <span class="o">=</span> <span class="n">futures</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="c1"># 記錄錯誤，繼續處理其他檔案</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                    <span class="s2">&#34;file&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                    <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="nb">type</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">results</span><span class="p">,</span> <span class="n">errors</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="n">results</span><span class="p">,</span> <span class="n">errors</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_robust</span><span class="p">(</span><span class="s2">&#34;docs/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;成功檢查 </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個檔案&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="k">if</span> <span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;有 </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個錯誤：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">err</span><span class="p">[</span><span class="s1">&#39;file&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="p">[</span><span class="s1">&#39;error&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="效能測量">效能測量</h2>
<h3 id="使用-timeit-比較效能">使用 timeit 比較效能</h3>
<p>理論上並行會更快，但「更快多少」需要測量：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 準備測試資料</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;docs/&#34;</span><span class="p">)</span>  <span class="c1"># 假設有 50+ 個 .md 檔案</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 測量循序版本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">test_sequential</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">test_dir</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">sequential_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">test_sequential</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 測量並行版本</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">test_parallel</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_parallel</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">test_dir</span><span class="p">),</span> <span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">parallel_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">test_parallel</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># 計算加速比</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">speedup</span> <span class="o">=</span> <span class="n">sequential_time</span> <span class="o">/</span> <span class="n">parallel_time</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本：</span><span class="si">{</span><span class="n">sequential_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本：</span><span class="si">{</span><span class="n">parallel_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比：</span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="真實測試結果參考">真實測試結果參考</h3>
<p>以下是在不同檔案數量下的測試結果（供參考）：</p>
<table>
  <thead>
      <tr>
          <th>檔案數量</th>
          <th>循序時間</th>
          <th>並行時間 (8 workers)</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10</td>
          <td>0.15s</td>
          <td>0.08s</td>
          <td>1.9x</td>
      </tr>
      <tr>
          <td>50</td>
          <td>0.72s</td>
          <td>0.18s</td>
          <td>4.0x</td>
      </tr>
      <tr>
          <td>100</td>
          <td>1.45s</td>
          <td>0.32s</td>
          <td>4.5x</td>
      </tr>
      <tr>
          <td>200</td>
          <td>2.91s</td>
          <td>0.58s</td>
          <td>5.0x</td>
      </tr>
  </tbody>
</table>
<p><strong>觀察</strong>：</p>
<ol>
<li>檔案越多，並行效益越明顯</li>
<li>加速比不會無限增長（受限於 I/O 頻寬和 GIL）</li>
<li>檔案少於 10 個時，並行的額外開銷可能抵消效益</li>
</ol>
<h3 id="完整的效能測試腳本">完整的效能測試腳本</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;效能比較測試腳本&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 假設這是 markdown_link_checker 的簡化版</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">class</span> <span class="nc">MarkdownChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;&#34;&#34;模擬檔案檢查（包含 I/O 延遲）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 模擬一些處理時間</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.01</span><span class="p">)</span>  <span class="c1"># 10ms I/O 延遲</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="s2">&#34;file&#34;</span><span class="p">:</span> <span class="n">file_path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="s2">&#34;lines&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()),</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;chars&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">check_sequential</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;循序檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">check_parallel</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">,</span> <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">8</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;並行檢查&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">futures</span> <span class="o">=</span> <span class="p">{</span><span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量函式執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">elapsed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># 收集測試檔案</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">)</span>  <span class="c1"># 替換為你的目錄</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">test_dir</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))[:</span><span class="mi">100</span><span class="p">]</span>  <span class="c1"># 取前 100 個</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;需要至少 10 個 .md 檔案來測試&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試檔案數量：</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">MarkdownChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="c1"># 測試不同 worker 數量</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">效能比較：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">checker</span><span class="o">.</span><span class="n">check_sequential</span><span class="p">,</span> <span class="n">files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本：</span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">for</span> <span class="n">workers</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="n">par_time</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">checker</span><span class="o">.</span><span class="n">check_parallel</span><span class="p">,</span> <span class="n">files</span><span class="p">,</span> <span class="n">workers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">seq_time</span> <span class="o">/</span> <span class="n">par_time</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行 (</span><span class="si">{</span><span class="n">workers</span><span class="si">:</span><span class="s2">2d</span><span class="si">}</span><span class="s2"> workers)：</span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒 (</span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><hr>
<h2 id="什麼時候不該用並行">什麼時候不該用並行？</h2>
<h3 id="反模式-1檔案數量太少">反模式 1：檔案數量太少</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：只有 3 個檔案還用並行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;a.md&#34;</span><span class="p">,</span> <span class="s2">&#34;b.md&#34;</span><span class="p">,</span> <span class="s2">&#34;c.md&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">check_file</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 問題：建立執行緒池的開銷可能比省下的時間還多</span></span></span></code></pre></div><p><strong>建議</strong>：檔案少於 5-10 個時，直接循序處理。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">check_files_smart</span><span class="p">(</span><span class="n">files</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據檔案數量選擇處理方式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">files</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># 少量檔案：循序處理</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="n">check_file</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="c1"># 大量檔案：並行處理</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">            <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">check_file</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span></span></span></code></pre></div><h3 id="反模式-2任務之間有依賴">反模式 2：任務之間有依賴</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不好：任務 B 依賴任務 A 的結果</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">process_a</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">return</span> <span class="n">fetch_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">def</span> <span class="nf">process_b</span><span class="p">(</span><span class="n">config</span><span class="p">):</span>  <span class="c1"># 需要 A 的結果</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">return</span> <span class="n">validate</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 強行並行化會出錯或需要額外同步</span></span></span></code></pre></div><h3 id="反模式-3共享可變狀態">反模式 3：共享可變狀態</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：多個執行緒修改同一個列表</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">shared_results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">bad_worker</span><span class="p">(</span><span class="n">file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">check_file</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">shared_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>  <span class="c1"># 競爭條件！</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 正確做法：讓 worker 返回結果，由主執行緒收集</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">good_worker</span><span class="p">(</span><span class="n">file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">check_file</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">()</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">good_worker</span><span class="p">,</span> <span class="n">files</span><span class="p">))</span></span></span></code></pre></div><h3 id="反模式-4cpu-密集任務用-threadpoolexecutor">反模式 4：CPU 密集任務用 ThreadPoolExecutor</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好：CPU 密集任務受 GIL 限制</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">compute_heavy</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># ThreadPoolExecutor 對 CPU 密集任務沒有加速效果</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="p">[</span><span class="mi">10_000_000</span><span class="p">]</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 正確：CPU 密集應該用 ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">with</span> <span class="n">ProcessPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">compute_heavy</span><span class="p">,</span> <span class="p">[</span><span class="mi">10_000_000</span><span class="p">]</span> <span class="o">*</span> <span class="mi">8</span><span class="p">))</span></span></span></code></pre></div><h3 id="快速決策表">快速決策表</h3>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>推薦做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&lt; 10 個檔案</td>
          <td>循序處理</td>
      </tr>
      <tr>
          <td>10-100 個檔案，I/O 密集</td>
          <td>ThreadPoolExecutor</td>
      </tr>
      <tr>
          <td>&gt; 100 個檔案，I/O 密集</td>
          <td>ThreadPoolExecutor + 進度報告</td>
      </tr>
      <tr>
          <td>CPU 密集計算</td>
          <td>ProcessPoolExecutor</td>
      </tr>
      <tr>
          <td>任務有依賴關係</td>
          <td>重新設計，或用 asyncio</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>
<p><strong>為什麼 <code>check_directory_parallel()</code> 中使用 <code>as_completed()</code> 而不是 <code>executor.map()</code>？</strong>
提示：思考兩者在「錯誤處理」和「進度報告」上的差異。</p>
</li>
<li>
<p><strong>如果 <code>check_file()</code> 除了讀檔還會寫入日誌檔，還適合並行化嗎？需要什麼額外措施？</strong>
提示：考慮日誌寫入的執行緒安全性。</p>
</li>
<li>
<p><strong>在 8 核心的機器上，為什麼 I/O 密集任務開 16 個 worker 可能比 8 個更快？</strong>
提示：思考 GIL 和 I/O 等待時間的關係。</p>
</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>
<p><strong>修改 hook_validator.py</strong>：將 <code>validate_all_hooks()</code> 改寫為並行版本，測量效能差異。</p>
</li>
<li>
<p><strong>實作超時機制</strong>：如果單一檔案檢查超過 5 秒，自動跳過並記錄警告。
提示：查看 <code>future.result(timeout=5)</code>。</p>
</li>
<li>
<p><strong>實作批次處理</strong>：當檔案超過 1000 個時，分批處理（每批 100 個），避免記憶體壓力。</p>
</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例研究：並行檔案檢查</a> - 完整的實作與測試</li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">案例研究：並行 Hook 驗證</a> - 結合 as_completed 與進度報告</li>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a> - 複習基礎概念</li>
<li>進階系列 <a href="/blog/python-advanced/04-cpython-internals/gil-threading/" data-link-title="3.4 GIL 與執行緒模型" data-link-desc="深入理解 GIL 的設計與實現">4.3 GIL 與執行緒模型</a> - 深入理解 GIL</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組索引</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">效能調優實戰</a></em></p>
]]></content:encoded></item><item><title>案例：並行檔案檢查</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/markdown_link_checker.py&lt;/code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>markdown_link_checker.py&lt;/code> 的 &lt;code>check_directory()&lt;/code> 方法檢查目錄下所有 Markdown 檔案的內部連結：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_directory&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查目錄下所有 Markdown 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> dir_path: 目錄路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="s2"> recursive: 是否遞迴檢查子目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[LinkCheckResult]: 所有檔案的檢查結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">dir_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">total_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">BrokenLink&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">file&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">link_text&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">link_target&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集所有 .md 檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">recursive&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rglob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">md_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dir_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.md&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 循序檢查每個檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">md_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">md_files&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">md_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，程式碼容易理解&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：檔案按排序順序處理，結果也按順序返回&lt;/li>
&lt;li>&lt;strong>除錯容易&lt;/strong>：問題發生時，可以精確定位到哪個檔案&lt;/li>
&lt;/ol>
&lt;h3 id="效能瓶頸分析">效能瓶頸分析&lt;/h3>
&lt;p>讓我們分析 &lt;code>check_file()&lt;/code> 方法的執行時間組成：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查單個 Markdown 檔案的連結&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">file_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 1. 檢查檔案是否存在（I/O）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 3. 解析連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 4. 過濾內部連結（CPU - 很快）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">internal_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_filter_internal_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">links&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">link&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">internal_links&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">suggestion&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_check_link&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">link&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;target&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">is_valid&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">broken_links&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">LinkCheckResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>時間分布估計&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">操作 | 類型 | 每檔案耗時
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">read_text() | I/O | 1-5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">parse_links() | CPU | 0.1 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">filter_links() | CPU | 0.01 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">check_link() x N | I/O | N * 0.5 ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">-----------------|-------|----------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">總計（10 連結） | | ~7 ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>對於 100 個檔案的專案：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/markdown_link_checker.py</code>，展示如何用 ThreadPoolExecutor 加速 I/O 密集的檔案檢查任務。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>markdown_link_checker.py</code> 的 <code>check_directory()</code> 方法檢查目錄下所有 Markdown 檔案的內部連結：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1"># 循序檢查每個檔案</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：循序執行，程式碼容易理解</li>
<li><strong>結果有序</strong>：檔案按排序順序處理，結果也按順序返回</li>
<li><strong>除錯容易</strong>：問題發生時，可以精確定位到哪個檔案</li>
</ol>
<h3 id="效能瓶頸分析">效能瓶頸分析</h3>
<p>讓我們分析 <code>check_file()</code> 方法的執行時間組成：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查單個 Markdown 檔案的連結&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 1. 檢查檔案是否存在（I/O）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 2. 讀取檔案內容（I/O - 主要瓶頸）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># 3. 解析連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_markdown_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 4. 過濾內部連結（CPU - 很快）</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># 5. 檢查每個連結（I/O - 檔案系統檢查）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span></span></span></code></pre></div><p><strong>時間分布估計</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">操作              | 類型  | 每檔案耗時
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">3</span><span class="cl">read_text()      | I/O   | 1-5 ms
</span></span><span class="line"><span class="ln">4</span><span class="cl">parse_links()    | CPU   | 0.1 ms
</span></span><span class="line"><span class="ln">5</span><span class="cl">filter_links()   | CPU   | 0.01 ms
</span></span><span class="line"><span class="ln">6</span><span class="cl">check_link() x N | I/O   | N * 0.5 ms
</span></span><span class="line"><span class="ln">7</span><span class="cl">-----------------|-------|----------
</span></span><span class="line"><span class="ln">8</span><span class="cl">總計（10 連結）  |       | ~7 ms</span></span></code></pre></div><p>對於 100 個檔案的專案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 循序執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">total_time</span> <span class="o">=</span> <span class="mi">100</span> <span class="o">*</span> <span class="mi">7</span><span class="n">ms</span> <span class="o">=</span> <span class="mi">700</span><span class="n">ms</span> <span class="o">=</span> <span class="mf">0.7</span> <span class="n">秒</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 這看起來不長，但如果：</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># - 檔案更多（500+ 個）</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># - 每個檔案連結更多</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># - 網路檔案系統（NFS）</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 時間會快速增長</span></span></span></code></pre></div><h4 id="為什麼適合並行化">為什麼適合並行化？</h4>
<ol>
<li><strong>I/O 密集</strong>：大部分時間花在檔案讀取和存在性檢查</li>
<li><strong>任務獨立</strong>：每個檔案的檢查互不依賴</li>
<li><strong>無共享狀態</strong>：不需要同步機制</li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>提升效能</strong>：利用並行化加速 I/O 操作</li>
<li><strong>保持 API 相容</strong>：不改變方法簽名和返回值</li>
<li><strong>可配置</strong>：允許調整並行度</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別獨立任務">步驟 1：識別獨立任務</h4>
<p>每個檔案的檢查是完全獨立的：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這些操作可以同時執行</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">result_1</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc1.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">result_2</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc2.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">result_3</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="s2">&#34;doc3.md&#34;</span><span class="p">)</span>  <span class="c1"># 獨立</span></span></span></code></pre></div><h4 id="步驟-2使用-threadpoolexecutor">步驟 2：使用 ThreadPoolExecutor</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        max_workers: 最大工作執行緒數，預設為 CPU 核心數
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        list[LinkCheckResult]: 所有檔案的檢查結果
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_create_error_result</span><span class="p">(</span><span class="n">dir_path</span><span class="p">,</span> <span class="s2">&#34;目錄不存在&#34;</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="s2">&#34;**/*.md&#34;</span> <span class="k">if</span> <span class="n">recursive</span> <span class="k">else</span> <span class="s2">&#34;*.md&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">recursive</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                      <span class="k">else</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="c1"># 使用 ThreadPoolExecutor 並行處理</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)):</span> <span class="n">md_file</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="c1"># 收集結果</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="c1"># 按檔案路徑排序（保持一致的輸出順序）</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h4 id="步驟-3選擇-max_workers">步驟 3：選擇 max_workers</h4>
<p><code>max_workers</code> 的選擇影響效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 預設值：min(32, os.cpu_count() + 4)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 這是 Python 3.8+ 的預設行為</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 對於 I/O 密集任務，可以設定更高</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">get_optimal_workers</span><span class="p">(</span><span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    根據檔案數量計算最佳工作執行緒數
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    經驗法則：
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 檔案數 &lt; 10: 使用檔案數
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 檔案數 &gt;= 10: 使用 CPU 核心數 * 2，但不超過 32
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">基於 markdown_link_checker.py，展示如何用 ThreadPoolExecutor 加速檔案檢查。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">class</span> <span class="nc">BrokenLink</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;&#34;&#34;失效連結描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">link_text</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">link_target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="k">class</span> <span class="nc">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個檔案的連結檢查結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="n">total_links</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">broken_links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">BrokenLink</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="n">is_valid</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="k">class</span> <span class="nc">ParallelMarkdownLinkChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    並行 Markdown 連結檢查器
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    相較於原版的改進：
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    - check_directory() 使用 ThreadPoolExecutor 並行處理
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">    - 支援自訂 max_workers
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    - 保持 API 相容性
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">INLINE_LINK_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;(?&lt;!!)\[([^\]]+)\]\(([^)]+)\)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">EXTERNAL_PATTERNS</span> <span class="o">=</span> <span class="p">[</span><span class="sa">r</span><span class="s1">&#39;^https?://&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^mailto:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^tel:&#39;</span><span class="p">,</span> <span class="sa">r</span><span class="s1">&#39;^ftp://&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="c1"># ===== 核心方法 =====</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        檢查單個 Markdown 檔案的連結
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">        這個方法是執行緒安全的，可以並行呼叫。
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">file_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">internal_links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_filter_internal_links</span><span class="p">(</span><span class="n">links</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">broken_links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">internal_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">is_valid</span><span class="p">,</span> <span class="n">suggestion</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span> <span class="n">file_path</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">broken_links</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                    <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                        <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">line</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;line&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">link_text</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                        <span class="n">link_target</span><span class="o">=</span><span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                        <span class="n">suggestion</span><span class="o">=</span><span class="n">suggestion</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">file_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">total_links</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">internal_links</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">broken_links</span><span class="o">=</span><span class="n">broken_links</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="n">max_workers</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">123</span><span class="cl"><span class="s2">        並行檢查目錄下所有 Markdown 檔案
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">            dir_path: 目錄路徑
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">            recursive: 是否遞迴檢查子目錄
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">            max_workers: 最大工作執行緒數，None 表示使用預設值
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">            list[LinkCheckResult]: 所有檔案的檢查結果（按路徑排序）
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="c1"># 收集所有 .md 檔案</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="c1"># 計算最佳工作執行緒數</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">if</span> <span class="n">max_workers</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">max_workers</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_optimal_workers</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">md_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="c1"># 並行處理</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">        <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">future_to_file</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">                <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">                <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="c1"># 收集結果（as_completed 提供最快的回應）</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">            <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_file</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">                    <span class="c1"># 處理意外錯誤</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">                    <span class="n">md_file</span> <span class="o">=</span> <span class="n">future_to_file</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">                    <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">                        <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">                            <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                            <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">                            <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">                                <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">                                    <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                                    <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檢查失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">                                <span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">                            <span class="p">]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 排序以保持一致的輸出順序</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">r</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="c1"># ===== 循序版本（用於比較）=====</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">    <span class="k">def</span> <span class="nf">check_directory_sequential</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">recursive</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="s2">&#34;&#34;&#34;循序版本，用於效能比較&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">        <span class="n">dir_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">LinkCheckResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">file_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">total_links</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                    <span class="n">broken_links</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">BrokenLink</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                            <span class="n">file</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span> <span class="n">line</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                            <span class="n">link_text</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">link_target</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">                            <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;目錄不存在: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">rglob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">            <span class="n">md_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">dir_path</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.md&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">
</span></span><span class="line"><span class="ln">230</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">for</span> <span class="n">md_file</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_file</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">md_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">
</span></span><span class="line"><span class="ln">234</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="c1"># ===== 私有方法 =====</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">    <span class="k">def</span> <span class="nf">_resolve_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="k">return</span> <span class="n">p</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">()</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">    <span class="k">def</span> <span class="nf">_parse_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="n">links</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="n">in_code_block</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">
</span></span><span class="line"><span class="ln">246</span><span class="cl">        <span class="k">for</span> <span class="n">line_num</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">),</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;```&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="n">in_code_block</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">in_code_block</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">
</span></span><span class="line"><span class="ln">251</span><span class="cl">            <span class="k">if</span> <span class="n">in_code_block</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">INLINE_LINK_PATTERN</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                <span class="n">links</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                    <span class="s2">&#34;target&#34;</span><span class="p">:</span> <span class="k">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">                    <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line_num</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">
</span></span><span class="line"><span class="ln">261</span><span class="cl">        <span class="k">return</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="k">def</span> <span class="nf">_filter_internal_links</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">links</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">Dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">internal</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">            <span class="n">target</span> <span class="o">=</span> <span class="n">link</span><span class="p">[</span><span class="s2">&#34;target&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">EXTERNAL_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="n">internal</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">return</span> <span class="n">internal</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_link</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="n">target</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="n">base_dir</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">target_path</span> <span class="o">=</span> <span class="n">target</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;#&#34;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">target_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">resolved</span> <span class="o">=</span> <span class="p">(</span><span class="n">base_dir</span> <span class="o">/</span> <span class="n">target_path</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="k">if</span> <span class="n">resolved</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">target_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">
</span></span><span class="line"><span class="ln">289</span><span class="cl">    <span class="k">def</span> <span class="nf">_get_optimal_workers</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_count</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;&#34;&#34;計算最佳工作執行緒數&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="n">cpu_count</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">cpu_count</span><span class="p">()</span> <span class="ow">or</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="k">if</span> <span class="n">file_count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="k">return</span> <span class="n">file_count</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="n">cpu_count</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">file_count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="c1"># ===== 效能測量工具 =====</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">
</span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_checker</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">303</span><span class="cl"><span class="s2">    比較循序與並行版本的效能
</span></span></span><span class="line"><span class="ln">304</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">305</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">306</span><span class="cl"><span class="s2">        dir_path: 要檢查的目錄
</span></span></span><span class="line"><span class="ln">307</span><span class="cl"><span class="s2">        iterations: 執行次數（取平均）
</span></span></span><span class="line"><span class="ln">308</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">309</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">310</span><span class="cl"><span class="s2">        dict: {&#39;sequential&#39;: 秒數, &#39;parallel&#39;: 秒數, &#39;speedup&#39;: 加速比}
</span></span></span><span class="line"><span class="ln">311</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="c1"># 預熱（讓檔案系統快取生效）</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl">    <span class="c1"># 測量循序版本</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">seq_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">seq_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="c1"># 測量並行版本</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">par_times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">par_times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">
</span></span><span class="line"><span class="ln">333</span><span class="cl">    <span class="n">seq_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">seq_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="n">par_avg</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">par_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">
</span></span><span class="line"><span class="ln">336</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="s2">&#34;sequential&#34;</span><span class="p">:</span> <span class="n">seq_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="s2">&#34;parallel&#34;</span><span class="p">:</span> <span class="n">par_avg</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">seq_avg</span> <span class="o">/</span> <span class="n">par_avg</span> <span class="k">if</span> <span class="n">par_avg</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">
</span></span><span class="line"><span class="ln">342</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="c1"># 預設檢查當前目錄</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">target_dir</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;=== 並行 Markdown 連結檢查示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目標目錄: </span><span class="si">{</span><span class="n">target_dir</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 執行檢查</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">
</span></span><span class="line"><span class="ln">358</span><span class="cl">    <span class="c1"># 統計</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">    <span class="n">total_files</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="n">total_links</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">total_links</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">    <span class="n">broken_count</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">broken_links</span><span class="p">)</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">invalid_files</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">r</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">
</span></span><span class="line"><span class="ln">364</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;檔案數: </span><span class="si">{</span><span class="n">total_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;連結數: </span><span class="si">{</span><span class="n">total_links</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;失效連結: </span><span class="si">{</span><span class="n">broken_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;有問題的檔案: </span><span class="si">{</span><span class="n">invalid_files</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">
</span></span><span class="line"><span class="ln">369</span><span class="cl">    <span class="c1"># 顯示失效連結</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">    <span class="k">if</span> <span class="n">broken_count</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">失效連結詳情:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">        <span class="k">for</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">373</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">374</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">  </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">file_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">broken_links</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">                    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    Line </span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">line</span><span class="si">}</span><span class="s2">: [</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_text</span><span class="si">}</span><span class="s2">](/python-advanced/08-practical-optimization/case-studies/parallel-file-check/</span><span class="si">{</span><span class="n">link</span><span class="o">.</span><span class="n">link_target</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">377</span><span class="cl">
</span></span><span class="line"><span class="ln">378</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">    <span class="k">if</span> <span class="n">total_files</span> <span class="o">&gt;=</span> <span class="mi">5</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">=== 效能比較 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="n">benchmark</span> <span class="o">=</span> <span class="n">benchmark_checker</span><span class="p">(</span><span class="n">target_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;sequential&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;parallel&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">benchmark</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="效能測量">效能測量</h3>
<p>使用 <code>timeit</code> 比較前後效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">parallel_link_checker</span> <span class="kn">import</span> <span class="n">ParallelMarkdownLinkChecker</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">measure_performance</span><span class="p">(</span><span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">num_runs</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量並比較循序與並行版本的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">checker</span> <span class="o">=</span> <span class="n">ParallelMarkdownLinkChecker</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 循序版本</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory_sequential</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 並行版本</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">par_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">checker</span><span class="o">.</span><span class="n">check_directory</span><span class="p">(</span><span class="n">dir_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">)</span> <span class="o">/</span> <span class="n">num_runs</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目錄: </span><span class="si">{</span><span class="n">dir_path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;循序版本: </span><span class="si">{</span><span class="n">seq_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;並行版本: </span><span class="si">{</span><span class="n">par_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">seq_time</span> <span class="o">/</span> <span class="n">par_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># 實際測試結果（範例）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># 目錄: ./docs （50 個 .md 檔案）</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 循序版本: 0.3521 秒</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 並行版本: 0.0892 秒</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 加速比: 3.95x</span></span></span></code></pre></div><p><strong>不同規模的預期加速比</strong>：</p>
<table>
  <thead>
      <tr>
          <th>檔案數</th>
          <th>循序時間</th>
          <th>並行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>10</td>
          <td>70 ms</td>
          <td>25 ms</td>
          <td>2.8x</td>
      </tr>
      <tr>
          <td>50</td>
          <td>350 ms</td>
          <td>90 ms</td>
          <td>3.9x</td>
      </tr>
      <tr>
          <td>100</td>
          <td>700 ms</td>
          <td>160 ms</td>
          <td>4.4x</td>
      </tr>
      <tr>
          <td>500</td>
          <td>3.5 s</td>
          <td>750 ms</td>
          <td>4.7x</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p>注意：實際加速比取決於檔案大小、連結數量、磁碟速度等因素。</p></blockquote>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>循序版本</th>
          <th>並行版本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>效能</td>
          <td>較慢，線性增長</td>
          <td>快 3-5 倍</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>簡單</td>
          <td>需要理解執行緒池</td>
      </tr>
      <tr>
          <td>除錯</td>
          <td>容易</td>
          <td>需要注意執行緒安全</td>
      </tr>
      <tr>
          <td>記憶體</td>
          <td>較低</td>
          <td>較高（執行緒開銷）</td>
      </tr>
      <tr>
          <td>結果順序</td>
          <td>保證有序</td>
          <td>需要額外排序</td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td>直接</td>
          <td>需要處理 Future 例外</td>
      </tr>
  </tbody>
</table>
<h3 id="執行緒安全考量">執行緒安全考量</h3>
<p><code>check_file()</code> 方法是執行緒安全的，因為：</p>
<ol>
<li><strong>無共享狀態</strong>：每次呼叫都獨立處理一個檔案</li>
<li><strong>唯讀操作</strong>：只讀取檔案，不修改</li>
<li><strong>獨立返回值</strong>：每個呼叫返回獨立的 <code>LinkCheckResult</code></li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 這是安全的</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">check_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># 所有變數都是區域變數</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>  <span class="c1"># 新物件</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>            <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">links</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_parse_links</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>         <span class="c1"># 區域變數</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">return</span> <span class="n">LinkCheckResult</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>                <span class="c1"># 新物件</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>多檔案處理</strong>：需要處理大量獨立檔案</li>
<li><strong>I/O 密集</strong>：主要時間花在檔案讀寫</li>
<li><strong>任務獨立</strong>：每個任務不依賴其他任務的結果</li>
<li><strong>可接受亂序</strong>：或願意在最後排序</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>檔案很少</strong>：少於 5 個檔案，並行開銷可能大於收益</li>
<li><strong>CPU 密集</strong>：如果主要時間花在計算，應考慮 ProcessPoolExecutor</li>
<li><strong>有依賴關係</strong>：後續檔案依賴前面檔案的結果</li>
<li><strong>記憶體受限</strong>：並行版本會同時載入多個檔案</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="練習-1加入進度回報">練習 1：加入進度回報</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">callback</span><span class="p">:</span> <span class="n">callable</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    並行檢查，並在每個檔案完成時呼叫 callback
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    callback 簽名: callback(completed: int, total: int, result: LinkCheckResult)
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：使用 as_completed() 在每個任務完成時觸發回報
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-2支援取消">練習 2：支援取消</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_cancellable</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cancel_event</span><span class="p">:</span> <span class="n">threading</span><span class="o">.</span><span class="n">Event</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    可取消的並行檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當 cancel_event.is_set() 時，停止提交新任務並返回已完成的結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：在迴圈中檢查 cancel_event
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="練習-3批次處理大型目錄">練習 3：批次處理大型目錄</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_directory_batched</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">dir_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">batch_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">LinkCheckResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    分批處理大型目錄
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    避免一次提交太多任務導致記憶體問題
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：將檔案列表分成多個批次，依序處理每批
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h4 id="練習-4加入重試機制">練習 4：加入重試機制</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_file_with_retry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">max_retries</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">LinkCheckResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    帶重試的檔案檢查
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    當檔案被鎖定或暫時不可用時自動重試
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    提示：捕捉特定例外，使用指數退避
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="練習-5實作並行度自動調整">練習 5：實作並行度自動調整</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveParallelChecker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    自動調整並行度的檢查器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    根據系統負載和檢查速度動態調整 max_workers
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    功能：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 初始使用保守的 max_workers
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 如果任務完成很快，增加 max_workers
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 如果系統負載高，減少 max_workers
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 記錄最佳 max_workers 供下次使用
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor">ThreadPoolExecutor 使用指南</a></li>
<li><a href="https://superfastpython.com/threadpoolexecutor-map-vs-submit/">as_completed vs map 的選擇</a></li>
<li>入門系列：<a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></p>
]]></content:encoded></item><item><title>2.2 微積分與最佳化</title><link>https://tarrragon.github.io/blog/llm/02-math-foundations/calculus-and-optimization/</link><pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/llm/02-math-foundations/calculus-and-optimization/</guid><description>&lt;p>LLM 訓練的本質是「最佳化問題」：給定 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/loss-function/" data-link-title="Loss Function" data-link-desc="把「模型預測」跟「正確答案」的差距量化成一個純量、訓練的最佳化目標">loss function&lt;/a>（預訓練用 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/cross-entropy/" data-link-title="Cross-Entropy" data-link-desc="衡量「預測機率分佈」跟「真實分佈」距離的指標、LLM 預訓練的主要 loss">cross-entropy&lt;/a>、推導見 &lt;a href="https://tarrragon.github.io/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">2.1 機率與資訊論&lt;/a>）、找一組權重讓 loss 最小。微積分提供工具回答「往哪個方向調權重能讓 loss 變小」、最佳化演算法回答「具體怎麼一步一步調」。&lt;/p>
&lt;p>寫 code 場景的使用者通常無需親自訓練、但理解這條鏈能解釋「為什麼 fine-tuning 要這麼多 GPU」「為什麼 learning rate 是關鍵 hyperparameter」「為什麼 gradient explosion 是常見問題」。本章整理核心概念、不展開完整推導。&lt;/p>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>讀完本章後、你應該能：&lt;/p>
&lt;ol>
&lt;li>解釋 gradient 在訓練中扮演的角色。&lt;/li>
&lt;li>看到「learning rate = 1e-4」設定時、知道它控制什麼。&lt;/li>
&lt;li>區分 SGD、Adam、AdamW 在訓練 LLM 時的取捨。&lt;/li>
&lt;li>看到 gradient explosion / vanishing 報告時、知道發生在哪一層。&lt;/li>
&lt;/ol>
&lt;h2 id="偏導數與-gradient往哪個方向走-loss-變小">偏導數與 &lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/gradient/" data-link-title="Gradient" data-link-desc="loss function 對權重的偏微分向量、指出「該往哪個方向調權重才能讓 loss 下降最快」">gradient&lt;/a>：往哪個方向走 loss 變小&lt;/h2>
&lt;p>偏導數（partial derivative）的核心定義是「對多變數函式中的一個變數微分、其他變數視為常數」。記號 &lt;code>∂f / ∂xᵢ&lt;/code>。&lt;/p>
&lt;p>Gradient（梯度）的核心定義是「所有偏導數打包成的向量」：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">∇f = (∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>幾何意義：gradient 指向「函式增加最快的方向」、長度等於該方向的變化率。要讓函式變小、就往 gradient 的反方向走。&lt;/p>
&lt;p>LLM 訓練的核心步驟：&lt;/p>
&lt;ol>
&lt;li>把訓練資料丟進模型、跑 forward pass、得到預測。&lt;/li>
&lt;li>算 loss（預測跟真實答案的差距）。&lt;/li>
&lt;li>對所有權重算 gradient：&lt;code>∇_W loss&lt;/code>。&lt;/li>
&lt;li>更新權重：&lt;code>W ← W - α · ∇_W loss&lt;/code>（α 是 learning rate）。&lt;/li>
&lt;li>回到第 1 步、重複數百萬次。&lt;/li>
&lt;/ol>
&lt;p>第 4 步的更新公式就是 gradient descent。整個流程的關鍵在 gradient 怎麼算出來。&lt;/p>
&lt;h2 id="chain-rule把-gradient-從輸出傳到所有權重">Chain rule：把 gradient 從輸出傳到所有權重&lt;/h2>
&lt;p>Chain rule（連鎖律）的核心定義是「複合函式的導數等於各層導數的乘積」。一變數情況：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">若 z = f(g(x))、則 dz/dx = (df/dg) × (dg/dx)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>多變數情況推廣到 chain rule 的矩陣形式（Jacobian）。&lt;/p>
&lt;p>LLM 有數十億參數、每個參數都要算 gradient。Chain rule 讓「從 loss 倒推每個權重的 gradient」變成可計算的問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">loss
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↑ ∂loss/∂output
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">output (last layer)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↑ ∂output/∂layer_N_input × chain rule
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">layer N
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↑ ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">layer 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↑ ∂layer_1_input/∂W₁
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">weights W₁&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每層算「local gradient」（output 對 input 的導數）、chain rule 把它們乘起來、最終得到 loss 對每個權重的 gradient。這個流程叫 &lt;strong>&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/backpropagation/" data-link-title="Backpropagation" data-link-desc="從 output loss 反向遞推、用 chain rule 算出每個權重的 gradient 的演算法">backpropagation&lt;/a>&lt;/strong>（反向傳播）。&lt;/p>
&lt;p>詳細展開見 &lt;a href="https://tarrragon.github.io/blog/llm/03-theoretical-foundations/neural-network-basics/" data-link-title="3.0 神經網路基礎" data-link-desc="從單一 neuron 到 multi-layer：weights、activation function、forward / backward pass 的角色">3.0 神經網路基礎&lt;/a>。&lt;/p>
&lt;h2 id="learning-rate每步走多遠">&lt;a href="https://tarrragon.github.io/blog/llm/knowledge-cards/learning-rate/" data-link-title="Learning Rate" data-link-desc="gradient descent 每步更新權重的幅度、訓練中最敏感的 hyperparameter">Learning Rate&lt;/a>：每步走多遠&lt;/h2>
&lt;p>Learning rate（學習率）的核心定義是「gradient descent 每步更新的幅度」、記號 α 或 η。權重更新：&lt;/p></description><content:encoded><![CDATA[<p>LLM 訓練的本質是「最佳化問題」：給定 <a href="/blog/llm/knowledge-cards/loss-function/" data-link-title="Loss Function" data-link-desc="把「模型預測」跟「正確答案」的差距量化成一個純量、訓練的最佳化目標">loss function</a>（預訓練用 <a href="/blog/llm/knowledge-cards/cross-entropy/" data-link-title="Cross-Entropy" data-link-desc="衡量「預測機率分佈」跟「真實分佈」距離的指標、LLM 預訓練的主要 loss">cross-entropy</a>、推導見 <a href="/blog/llm/02-math-foundations/probability-and-information/" data-link-title="2.1 機率與資訊論" data-link-desc="LLM 輸出的本質是機率分佈：softmax、cross-entropy、KL divergence、perplexity 在訓練與推論中的角色">2.1 機率與資訊論</a>）、找一組權重讓 loss 最小。微積分提供工具回答「往哪個方向調權重能讓 loss 變小」、最佳化演算法回答「具體怎麼一步一步調」。</p>
<p>寫 code 場景的使用者通常無需親自訓練、但理解這條鏈能解釋「為什麼 fine-tuning 要這麼多 GPU」「為什麼 learning rate 是關鍵 hyperparameter」「為什麼 gradient explosion 是常見問題」。本章整理核心概念、不展開完整推導。</p>
<h2 id="本章目標">本章目標</h2>
<p>讀完本章後、你應該能：</p>
<ol>
<li>解釋 gradient 在訓練中扮演的角色。</li>
<li>看到「learning rate = 1e-4」設定時、知道它控制什麼。</li>
<li>區分 SGD、Adam、AdamW 在訓練 LLM 時的取捨。</li>
<li>看到 gradient explosion / vanishing 報告時、知道發生在哪一層。</li>
</ol>
<h2 id="偏導數與-gradient往哪個方向走-loss-變小">偏導數與 <a href="/blog/llm/knowledge-cards/gradient/" data-link-title="Gradient" data-link-desc="loss function 對權重的偏微分向量、指出「該往哪個方向調權重才能讓 loss 下降最快」">gradient</a>：往哪個方向走 loss 變小</h2>
<p>偏導數（partial derivative）的核心定義是「對多變數函式中的一個變數微分、其他變數視為常數」。記號 <code>∂f / ∂xᵢ</code>。</p>
<p>Gradient（梯度）的核心定義是「所有偏導數打包成的向量」：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">∇f = (∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ)</span></span></code></pre></div><p>幾何意義：gradient 指向「函式增加最快的方向」、長度等於該方向的變化率。要讓函式變小、就往 gradient 的反方向走。</p>
<p>LLM 訓練的核心步驟：</p>
<ol>
<li>把訓練資料丟進模型、跑 forward pass、得到預測。</li>
<li>算 loss（預測跟真實答案的差距）。</li>
<li>對所有權重算 gradient：<code>∇_W loss</code>。</li>
<li>更新權重：<code>W ← W - α · ∇_W loss</code>（α 是 learning rate）。</li>
<li>回到第 1 步、重複數百萬次。</li>
</ol>
<p>第 4 步的更新公式就是 gradient descent。整個流程的關鍵在 gradient 怎麼算出來。</p>
<h2 id="chain-rule把-gradient-從輸出傳到所有權重">Chain rule：把 gradient 從輸出傳到所有權重</h2>
<p>Chain rule（連鎖律）的核心定義是「複合函式的導數等於各層導數的乘積」。一變數情況：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">若 z = f(g(x))、則 dz/dx = (df/dg) × (dg/dx)</span></span></code></pre></div><p>多變數情況推廣到 chain rule 的矩陣形式（Jacobian）。</p>
<p>LLM 有數十億參數、每個參數都要算 gradient。Chain rule 讓「從 loss 倒推每個權重的 gradient」變成可計算的問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">loss
</span></span><span class="line"><span class="ln">2</span><span class="cl"> ↑ ∂loss/∂output
</span></span><span class="line"><span class="ln">3</span><span class="cl">output (last layer)
</span></span><span class="line"><span class="ln">4</span><span class="cl"> ↑ ∂output/∂layer_N_input × chain rule
</span></span><span class="line"><span class="ln">5</span><span class="cl">layer N
</span></span><span class="line"><span class="ln">6</span><span class="cl"> ↑ ...
</span></span><span class="line"><span class="ln">7</span><span class="cl">layer 1
</span></span><span class="line"><span class="ln">8</span><span class="cl"> ↑ ∂layer_1_input/∂W₁
</span></span><span class="line"><span class="ln">9</span><span class="cl">weights W₁</span></span></code></pre></div><p>每層算「local gradient」（output 對 input 的導數）、chain rule 把它們乘起來、最終得到 loss 對每個權重的 gradient。這個流程叫 <strong><a href="/blog/llm/knowledge-cards/backpropagation/" data-link-title="Backpropagation" data-link-desc="從 output loss 反向遞推、用 chain rule 算出每個權重的 gradient 的演算法">backpropagation</a></strong>（反向傳播）。</p>
<p>詳細展開見 <a href="/blog/llm/03-theoretical-foundations/neural-network-basics/" data-link-title="3.0 神經網路基礎" data-link-desc="從單一 neuron 到 multi-layer：weights、activation function、forward / backward pass 的角色">3.0 神經網路基礎</a>。</p>
<h2 id="learning-rate每步走多遠"><a href="/blog/llm/knowledge-cards/learning-rate/" data-link-title="Learning Rate" data-link-desc="gradient descent 每步更新權重的幅度、訓練中最敏感的 hyperparameter">Learning Rate</a>：每步走多遠</h2>
<p>Learning rate（學習率）的核心定義是「gradient descent 每步更新的幅度」、記號 α 或 η。權重更新：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">W_new = W_old - learning_rate × gradient</span></span></code></pre></div><p>Learning rate 的影響：</p>
<table>
  <thead>
      <tr>
          <th>Learning rate</th>
          <th>效果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>太大</td>
          <td>跨過最佳解、loss 震盪不收斂、甚至發散</td>
      </tr>
      <tr>
          <td>適中</td>
          <td>穩定下降、合理時間內收斂</td>
      </tr>
      <tr>
          <td>太小</td>
          <td>收斂太慢、訓練時間爆增、可能卡在 local minimum</td>
      </tr>
  </tbody>
</table>
<p>LLM 訓練常用 learning rate：</p>
<ul>
<li>預訓練（pre-training）：1e-4 ~ 3e-4、有 warmup 後線性衰減</li>
<li>Fine-tuning：1e-5 ~ 5e-5、較小避免破壞 pre-trained 權重</li>
<li>LoRA：1e-4 ~ 1e-3、只更新少量參數可較大</li>
</ul>
<p>Learning rate 是訓練 LLM 最關鍵的 hyperparameter、設錯時整個訓練容易失敗、實務上極難救回。實務上常用 learning rate scheduler 動態調整：warmup + cosine decay 是最主流的組合。</p>
<h2 id="sgd最基本的最佳化演算法"><a href="/blog/llm/knowledge-cards/sgd/" data-link-title="SGD" data-link-desc="Stochastic Gradient Descent：每次用 mini-batch 算 gradient 更新權重的基礎 optimizer">SGD</a>：最基本的最佳化演算法</h2>
<p>SGD（Stochastic Gradient Descent、隨機梯度下降）的核心定義是「每次只用一小批資料（mini-batch）算 gradient、更新權重」。對應 vanilla gradient descent（用全部資料算一次）的計算成本問題：</p>
<ul>
<li><strong>Batch GD</strong>：每步用全部訓練資料、gradient 準但每步成本高、適合小資料集</li>
<li><strong>SGD（mini-batch）</strong>：每步用 32 ~ 256 筆、gradient 有 noise 但平均下來方向對、適合大資料集</li>
</ul>
<p>LLM 預訓練資料動輒 TB 級、每步只能用 mini-batch；每個 token 算一次 forward + backward、跑數兆 token、總更新數十萬到數百萬步。</p>
<p>Vanilla SGD 在 LLM 場景的缺點：</p>
<ol>
<li>對 learning rate 敏感、不同 layer / 不同參數可能需要不同 learning rate。</li>
<li>在「狹長 loss surface」上震盪、收斂慢。</li>
<li>不利用過去 gradient 資訊。</li>
</ol>
<p>SGD-with-momentum 在 vanilla SGD 上補了「過去 gradient 累積成 velocity」、處理震盪問題、在 vision（ResNet、ImageNet 訓練）跟小規模 fine-tune 仍是合理選擇；Adam / AdamW 在 LLM 預訓練成主流的原因是「自適應 learning rate + per-parameter scale」更能對付 Transformer 的高維、稀疏 gradient 結構、大規模 transformer 預訓練幾乎全部用 AdamW。</p>
<h2 id="adam-與-adamw適應性最佳化"><a href="/blog/llm/knowledge-cards/adam-adamw/" data-link-title="Adam / AdamW" data-link-desc="對每個參數自適應 learning rate 的 optimizer、LLM 訓練主流選擇">Adam 與 AdamW</a>：適應性最佳化</h2>
<p>Adam（Adaptive Moment Estimation）的核心定義是「每個參數有自己的有效 learning rate、根據過去 gradient 的一階矩跟二階矩自動調整」。簡化版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">m_t = β₁ × m_{t-1} + (1 - β₁) × gradient   ← 一階矩（gradient 的指數移動平均）
</span></span><span class="line"><span class="ln">2</span><span class="cl">v_t = β₂ × v_{t-1} + (1 - β₂) × gradient²  ← 二階矩（gradient 平方的指數移動平均）
</span></span><span class="line"><span class="ln">3</span><span class="cl">update = learning_rate × m_t / (sqrt(v_t) + ε)</span></span></code></pre></div><p>直覺：</p>
<ul>
<li>一階矩 m：類似動量、讓更新方向有慣性、減少震盪。</li>
<li>二階矩 v：估計 gradient 大小、把更新除以 sqrt(v)、自動調整每個參數的有效步幅。</li>
<li>結果：高 gradient 的參數步小、低 gradient 的參數步大、整體穩定收斂。</li>
</ul>
<p>AdamW 是 Adam 的改進版、把 weight decay（L2 正則化）跟 gradient update 解耦。大規模 transformer 預訓練幾乎都用 AdamW、vanilla Adam 已退出 LLM 主流（SGD-with-momentum 在 vision 跟小規模 fine-tune 仍適用）。</p>
<p>代價：Adam / AdamW 需要為每個參數額外存 m（一階矩、gradient 的指數移動平均）跟 v（二階矩、gradient 平方的指數移動平均）、記憶體成本是 SGD 的 3 倍。31B 模型用 AdamW 訓練的 optimizer state 約佔 200GB+ 記憶體、拆解如下（mixed-precision training、batch=1024 / 不含 activation checkpoint 的典型配置）：</p>
<ul>
<li>fp32 master weights：31B × 4 bytes ≈ 124 GB</li>
<li>m（一階矩）：31B × 4 bytes ≈ 124 GB</li>
<li>v（二階矩）：31B × 4 bytes ≈ 124 GB</li>
<li>總計約 372 GB optimizer state、加上 activation 與 gradient buffer 後實際需求更高</li>
</ul>
<p>對比推論時 Gemma 4 31B Q4 量化版約 18GB（含 KV cache、見 <a href="/blog/llm/00-foundations/hardware-memory-budget/" data-link-title="0.5 Apple Silicon 記憶體預算" data-link-desc="記憶體決定能跑什麼，Q4 量化下的可運作模型對照與系統保留">0.5 Apple Silicon 記憶體預算</a>）、訓練需求是推論的 20 倍以上。這就是為什麼訓練 LLM 需要大量 GPU、推論可以在個人 Mac 上跑。</p>
<h2 id="gradient-explosion-與-vanishing"><a href="/blog/llm/knowledge-cards/gradient-explosion-vanishing/" data-link-title="Gradient Explosion / Vanishing" data-link-desc="深層網路訓練中 gradient 透過 chain rule 累乘、容易爆炸或衰減到 0 的兩種失敗模式">Gradient Explosion 與 Vanishing</a></h2>
<p>Gradient explosion（梯度爆炸）的核心問題是「gradient 經過多層 chain rule 累積、變成天文數字、權重更新後完全爆掉」。常見於深度網路、特別是 RNN。</p>
<p>Gradient vanishing（梯度消失）的反面問題是「gradient 經過多層後變得幾乎為 0、深層 layer 學不到東西」。常見於用 sigmoid / tanh activation 的深度網路。</p>
<p>Transformer 為什麼能訓練深層網路：</p>
<ol>
<li><strong>Residual connection</strong>：跨層加上 <code>x + f(x)</code>、給 gradient 一條短路、避免 vanishing。</li>
<li><strong>Layer normalization</strong>：每層 activation 重新正規化、避免數值爆炸。</li>
<li><strong>適當的權重初始化</strong>：Xavier / Kaiming 初始化讓初始 forward pass 不爆。</li>
<li><strong>Gradient clipping</strong>：訓練時把 gradient 的 norm 截斷在閾值內、避免 explosion。</li>
</ol>
<p>詳細展開見 <a href="/blog/llm/03-theoretical-foundations/transformer-architecture/" data-link-title="3.3 Transformer 架構細節" data-link-desc="Decoder-only 結構、Transformer block、positional encoding、layer norm、residual stream">3.3 Transformer 架構</a>。</p>
<h2 id="backpropagationchain-rule-在多層網路上的演算法名">Backpropagation：chain rule 在多層網路上的演算法名</h2>
<p>Backpropagation（反向傳播）就是前面 <a href="#chain-rule%e6%8a%8a-gradient-%e5%be%9e%e8%bc%b8%e5%87%ba%e5%82%b3%e5%88%b0%e6%89%80%e6%9c%89%e6%ac%8a%e9%87%8d">chain rule</a> 段講的「∂loss/∂W 倒推流程」在實作上的演算法名稱、不是另一個獨立概念。整體流程：forward pass 算 output 與 loss、backward pass 用 chain rule 從 loss 逐層倒推每個權重的 gradient、framework（PyTorch / MLX）的 autograd 自動完成 backward、開發者只需寫 forward。Autograd 跟 chain rule / backprop 是同個概念在不同抽象層級的展開。</p>
<h2 id="為什麼推論不需要-backprop">為什麼推論不需要 backprop</h2>
<p>寫 code 場景用 LLM 是「推論」而非「訓練」。推論只跑 forward pass、不算 gradient、不更新權重。所以：</p>
<ol>
<li><strong>記憶體需求低得多</strong>：推論不用存中間 activation（forward pass 結束就可丟）、不用存 optimizer state。Gemma 4 31B 推論約 18GB、訓練同個模型可能要 200GB+。</li>
<li><strong>算力需求低得多</strong>：推論一個 token 要 1 次 forward pass、訓練一個 token 要 forward + backward = 約 3 次 forward 的成本。</li>
<li><strong>沒有 learning rate / optimizer 等 hyperparameter</strong>：推論只有 temperature、top-p 等 sampling 參數。</li>
</ol>
<p>這就是為什麼 32GB Mac 可以推論 31B 模型、訓練同個模型要動用整個 H100 cluster。</p>
<h2 id="下一章">下一章</h2>
<p>想看完整最佳化理論（凸最佳化、二階方法、Hessian、Newton&rsquo;s method 等）、見 <a href="/blog/llm/02-math-foundations/going-deeper-math/" data-link-title="2.4 想學更深：推薦公開課程" data-link-desc="MIT、Stanford、Harvard 等公開課程：數學基礎跟 LLM 預備知識的完整學習路線">2.4 公開課推薦</a> 的 Stanford EE364 / CS229 等課程。</p>
<p>下一章：<a href="/blog/llm/02-math-foundations/numerical-precision/" data-link-title="2.3 數值精度與量化的數學依據" data-link-desc="fp32 / bf16 / fp16 / int8 / int4 的差別、量化能省哪些 bits、品質衰減從哪裡來">2.3 數值精度與量化的數學依據</a>。</p>
]]></content:encoded></item><item><title>8.2 效能調優實戰</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/performance-tuning/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/performance-tuning/</guid><description>&lt;p>在入門系列中，我們學習了效能優化的原則和工具。本章將這些知識應用於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何從「發現問題」到「驗證效果」的完整流程。&lt;/p>
&lt;h2 id="學習目標">學習目標&lt;/h2>
&lt;p>完成本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>使用 cProfile 分析真實程式碼的效能瓶頸&lt;/li>
&lt;li>判斷哪些程式碼值得優化&lt;/li>
&lt;li>應用正則表達式預編譯提升效能&lt;/li>
&lt;li>使用 &lt;code>functools.lru_cache&lt;/code> 實現有效的快取策略&lt;/li>
&lt;li>根據查詢模式選擇適當的資料結構&lt;/li>
&lt;/ol>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>本章假設你已經閱讀：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能迷思與優化策略&lt;/a> - 效能測量工具與優化原則&lt;/li>
&lt;/ul>
&lt;p>如果你還不熟悉 &lt;code>cProfile&lt;/code>、&lt;code>timeit&lt;/code> 或「過早優化是萬惡之源」這句話的含義，請先閱讀入門系列。&lt;/p>
&lt;h2 id="效能分析流程">效能分析流程&lt;/h2>
&lt;p>優化的正確流程是：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">1. 測量基準效能
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">2. 找出瓶頸（cProfile）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3. 針對瓶頸優化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">4. 驗證優化效果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">5. 評估維護成本&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最重要的原則：&lt;strong>先測量，後優化&lt;/strong>。沒有測量數據的優化是盲目的。&lt;/p>
&lt;h3 id="真實案例hook-驗證器">真實案例：Hook 驗證器&lt;/h3>
&lt;p>我們以 &lt;code>.claude/lib/hook_validator.py&lt;/code> 為例。這個工具用來驗證 Hook 腳本是否符合專案規範，核心功能是透過正則表達式檢查程式碼內容。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 共用模組導入模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_LOGGING_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 更多模式 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_has_import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查是否有符合任一模式的導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這段程式碼有什麼效能問題？讓我們用 cProfile 來找出答案。&lt;/p>
&lt;h2 id="步驟-1測量基準效能">步驟 1：測量基準效能&lt;/h2>
&lt;p>首先，建立測試環境並測量原始版本的效能：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">cProfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">pstats&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pstats&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SortKey&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_test_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_lines&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">500&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;生成測試用的 Hook 腳本內容&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;#!/usr/bin/env python3&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;&amp;#34;&amp;#34;&amp;#34;Test hook script&amp;#34;&amp;#34;&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;import os&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;import sys&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;from hook_io import read_hook_input, write_hook_output&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;from hook_logging import setup_hook_logging&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 加入更多程式碼行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_lines&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">10&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39;def function_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">():&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39; &amp;#34;&amp;#34;&amp;#34;Function &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#34;&amp;#34;&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s1">&amp;#39; x_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1"> = &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark_original&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測量原始版本的效能&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 原始實作：每次呼叫都重新編譯正則表達式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="n">patterns&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">has_import_original&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 執行效能分析&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cProfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Profile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">enable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="n">has_import_original&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="n">profiler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disable&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pstats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">profiler&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sort_stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">SortKey&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CUMULATIVE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="n">stats&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">print_stats&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">stats&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">&lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_test_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;=== 原始版本效能 ===&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="n">benchmark_original&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>執行後的輸出類似：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">=== 原始版本效能 ===
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> 501 function calls in 0.0234 seconds
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> Ordered by: cumulative time
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ncalls tottime percall cumtime percall filename:lineno(function)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl"> 100 0.001 0.000 0.023 0.000 test.py:30(has_import_original)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> 200 0.022 0.000 0.022 0.000 {method &amp;#39;search&amp;#39; of &amp;#39;re.Pattern&amp;#39;}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> 100 0.000 0.000 0.000 0.000 {built-in method builtins.any}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>觀察結果：&lt;code>re.search&lt;/code> 佔用了大部分時間。&lt;/p></description><content:encoded><![CDATA[<p>在入門系列中，我們學習了效能優化的原則和工具。本章將這些知識應用於 <code>.claude/lib</code> 的實際程式碼，展示如何從「發現問題」到「驗證效果」的完整流程。</p>
<h2 id="學習目標">學習目標</h2>
<p>完成本章後，你將能夠：</p>
<ol>
<li>使用 cProfile 分析真實程式碼的效能瓶頸</li>
<li>判斷哪些程式碼值得優化</li>
<li>應用正則表達式預編譯提升效能</li>
<li>使用 <code>functools.lru_cache</code> 實現有效的快取策略</li>
<li>根據查詢模式選擇適當的資料結構</li>
</ol>
<h2 id="先備知識">先備知識</h2>
<p>本章假設你已經閱讀：</p>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能迷思與優化策略</a> - 效能測量工具與優化原則</li>
</ul>
<p>如果你還不熟悉 <code>cProfile</code>、<code>timeit</code> 或「過早優化是萬惡之源」這句話的含義，請先閱讀入門系列。</p>
<h2 id="效能分析流程">效能分析流程</h2>
<p>優化的正確流程是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">1. 測量基準效能
</span></span><span class="line"><span class="ln">2</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">2. 找出瓶頸（cProfile）
</span></span><span class="line"><span class="ln">4</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. 針對瓶頸優化
</span></span><span class="line"><span class="ln">6</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">7</span><span class="cl">4. 驗證優化效果
</span></span><span class="line"><span class="ln">8</span><span class="cl">       ↓
</span></span><span class="line"><span class="ln">9</span><span class="cl">5. 評估維護成本</span></span></code></pre></div><p>最重要的原則：<strong>先測量，後優化</strong>。沒有測量數據的優化是盲目的。</p>
<h3 id="真實案例hook-驗證器">真實案例：Hook 驗證器</h3>
<p>我們以 <code>.claude/lib/hook_validator.py</code> 為例。這個工具用來驗證 Hook 腳本是否符合專案規範，核心功能是透過正則表達式檢查程式碼內容。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 共用模組導入模式</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># ... 更多模式 ...</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><p>這段程式碼有什麼效能問題？讓我們用 cProfile 來找出答案。</p>
<h2 id="步驟-1測量基準效能">步驟 1：測量基準效能</h2>
<p>首先，建立測試環境並測量原始版本的效能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">pstats</span> <span class="kn">import</span> <span class="n">SortKey</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">generate_test_content</span><span class="p">(</span><span class="n">num_lines</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">500</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;生成測試用的 Hook 腳本內容&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">lines</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s1">&#39;#!/usr/bin/env python3&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s1">&#39;&#34;&#34;&#34;Test hook script&#34;&#34;&#34;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s1">&#39;import os&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s1">&#39;import sys&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s1">&#39;from hook_io import read_hook_input, write_hook_output&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s1">&#39;from hook_logging import setup_hook_logging&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s1">&#39;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 加入更多程式碼行</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_lines</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;def function_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">():&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;    &#34;&#34;&#34;Function </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">&#34;&#34;&#34;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;    x_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1"> = </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_original</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量原始版本的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="c1"># 原始實作：每次呼叫都重新編譯正則表達式</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">patterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">def</span> <span class="nf">has_import_original</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="c1"># 執行效能分析</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">has_import_original</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CUMULATIVE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="n">stats</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="k">return</span> <span class="n">stats</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="c1"># 執行測試</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 原始版本效能 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="n">benchmark_original</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><p>執行後的輸出類似：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">=== 原始版本效能 ===
</span></span><span class="line"><span class="ln">2</span><span class="cl">         501 function calls in 0.0234 seconds
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">   Ordered by: cumulative time
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl">   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
</span></span><span class="line"><span class="ln">7</span><span class="cl">      100    0.001    0.000    0.023    0.000 test.py:30(has_import_original)
</span></span><span class="line"><span class="ln">8</span><span class="cl">      200    0.022    0.000    0.022    0.000 {method &#39;search&#39; of &#39;re.Pattern&#39;}
</span></span><span class="line"><span class="ln">9</span><span class="cl">      100    0.000    0.000    0.000    0.000 {built-in method builtins.any}</span></span></code></pre></div><p>觀察結果：<code>re.search</code> 佔用了大部分時間。</p>
<h2 id="步驟-2找出瓶頸">步驟 2：找出瓶頸</h2>
<p>從 cProfile 結果可以看到，<code>re.search</code> 被呼叫了 200 次（100 次迭代 x 2 個 pattern）。</p>
<p>問題在於：<strong><code>re.search(pattern, content)</code> 每次呼叫時都會重新編譯正則表達式</strong>。</p>
<p>雖然 Python 的 <code>re</code> 模組有內部快取（最近使用的 pattern 會被快取），但：</p>
<ol>
<li>快取有大小限制（預設 512 個）</li>
<li>查詢快取本身也有開銷</li>
<li>在 Hook 驗證器中有多達 20+ 個不同的 pattern</li>
</ol>
<h2 id="正則表達式預編譯">正則表達式預編譯</h2>
<h3 id="問題重複編譯的開銷">問題：重複編譯的開銷</h3>
<p>來看 <code>hook_validator.py</code> 中的實際程式碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 模式定義為字串列表</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 輸出函式使用模式</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 不推薦的輸出模式</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>  <span class="c1"># 每次都要編譯！</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><p>每次呼叫 <code>_has_import</code> 時，所有 pattern 都會被重新處理。</p>
<h3 id="解決方案預編譯">解決方案：預編譯</h3>
<p>將字串 pattern 改為預編譯的 <code>re.Pattern</code> 物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Pattern</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidatorOptimized</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;優化版 Hook 驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 預編譯的正則表達式模式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>  <span class="c1"># 直接使用預編譯的 pattern</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><h3 id="效能測量">效能測量</h3>
<p>比較預編譯和非預編譯的效能差異：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">compare_regex_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較預編譯 vs 非預編譯的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="n">generate_test_content</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 字串 pattern</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">str_patterns</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 預編譯 pattern</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">compiled_patterns</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">str_patterns</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 測試 1：使用字串 pattern</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="nb">any</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">str_patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">str_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 測試 2：使用預編譯 pattern</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">compiled_patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串 pattern:    </span><span class="si">{</span><span class="n">str_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯 pattern:  </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:          </span><span class="si">{</span><span class="n">str_time</span> <span class="o">/</span> <span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="n">compare_regex_performance</span><span class="p">()</span></span></span></code></pre></div><p>典型輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">字串 pattern:    0.1234s
</span></span><span class="line"><span class="ln">2</span><span class="cl">預編譯 pattern:  0.0987s
</span></span><span class="line"><span class="ln">3</span><span class="cl">加速比:          1.25x</span></span></code></pre></div><p>在這個例子中，預編譯帶來約 20-30% 的效能提升。雖然不是「快 10 倍」的驚人結果，但：</p>
<ol>
<li><strong>改動成本極低</strong>：只需要加上 <code>re.compile()</code></li>
<li><strong>無風險</strong>：行為完全相同</li>
<li><strong>累積效果</strong>：當有更多 pattern 時，效果更明顯</li>
</ol>
<h2 id="快取策略lru_cache">快取策略：lru_cache</h2>
<h3 id="適用場景">適用場景</h3>
<p><code>functools.lru_cache</code> 適合用於：</p>
<ol>
<li><strong>純函式</strong>：相同輸入總是產生相同輸出</li>
<li><strong>計算昂貴</strong>：函式執行需要較長時間</li>
<li><strong>重複呼叫</strong>：同樣的參數會被多次呼叫</li>
</ol>
<h3 id="實作範例分支保護檢查">實作範例：分支保護檢查</h3>
<p>來看 <code>.claude/lib/git_utils.py</code> 中的 <code>is_protected_branch()</code> 函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 保護分支列表（支援 glob 模式）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    檢查是否為保護分支
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><p>這個函式：</p>
<ul>
<li><strong>是純函式</strong>：相同的 <code>branch</code> 總是返回相同結果</li>
<li><strong>會被重複呼叫</strong>：在 Hook 執行期間可能檢查同一個分支多次</li>
</ul>
<h3 id="加入-lru_cache">加入 lru_cache</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    檢查是否為保護分支（帶快取）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><h3 id="快取命中率分析">快取命中率分析</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">analyze_cache_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="s2">&#34;&#34;&#34;分析快取命中率&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># 模擬真實的呼叫模式</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">branches</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span> <span class="s2">&#34;main&#34;</span><span class="p">,</span> <span class="s2">&#34;main&#34;</span><span class="p">,</span>  <span class="c1"># 重複檢查 main</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;feat/new-feature&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="s2">&#34;fix/bug-123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;release/1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;feat/new-feature&#34;</span><span class="p">,</span>  <span class="c1"># 重複</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 清除快取統計</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="n">branches</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;檢查 </span><span class="si">{</span><span class="n">branch</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">result</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 查看快取統計</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">info</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">快取統計:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  未命中: </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">maxsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="n">analyze_cache_performance</span><span class="p">()</span></span></span></code></pre></div><p>輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">檢查 main: True
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">檢查 main: True
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">檢查 main: True
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">檢查 feat/new-feature: False
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">檢查 main: True
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">檢查 fix/bug-123: False
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">檢查 main: True
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">檢查 release/1.0: True
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">檢查 feat/new-feature: False
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">快取統計:
</span></span><span class="line"><span class="ln">12</span><span class="cl">  命中: 5
</span></span><span class="line"><span class="ln">13</span><span class="cl">  未命中: 4
</span></span><span class="line"><span class="ln">14</span><span class="cl">  命中率: 55.6%
</span></span><span class="line"><span class="ln">15</span><span class="cl">  快取大小: 4/128</span></span></code></pre></div><h3 id="lru_cache-的注意事項">lru_cache 的注意事項</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 參數必須是可雜湊的（hashable）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@lru_cache</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">list</span><span class="p">):</span>  <span class="c1"># 錯誤！list 不可雜湊</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nd">@lru_cache</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">):</span>  <span class="c1"># 正確，tuple 可雜湊</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 2. 注意快取大小</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>  <span class="c1"># 無限制，可能耗盡記憶體</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">def</span> <span class="nf">expensive_function</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>  <span class="c1"># 建議設定合理上限</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">def</span> <span class="nf">expensive_function</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 3. 可以手動清除快取</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">expensive_function</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 4. 查看快取統計</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">info</span> <span class="o">=</span> <span class="n">expensive_function</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"># CacheInfo(hits=10, misses=5, maxsize=128, currsize=5)</span></span></span></code></pre></div><h2 id="資料結構選擇">資料結構選擇</h2>
<h3 id="on-vs-o1-的差異">O(n) vs O(1) 的差異</h3>
<p>在 <code>hook_validator.py</code> 中，檢查測試檔案是否存在：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查對應的測試檔案是否存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">hook_name</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 測試檔案可能在這些位置</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">possible_test_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">test_exists</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_test_paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># ...</span></span></span></code></pre></div><p>如果需要檢查多個檔案是否在某個集合中：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不好的做法：用 list，O(n) 查詢</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">existing_tests</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;test_branch_verify.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;test_hook_io.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;test_config_loader.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># ... 可能有幾十個</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">has_test_slow</span><span class="p">(</span><span class="n">test_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">existing_tests</span>  <span class="c1"># O(n)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 好的做法：用 set，O(1) 查詢</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">existing_tests_set</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;test_branch_verify.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;test_hook_io.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;test_config_loader.py&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">has_test_fast</span><span class="p">(</span><span class="n">test_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">existing_tests_set</span>  <span class="c1"># O(1)</span></span></span></code></pre></div><h3 id="真實案例測試檔案存在性檢查">真實案例：測試檔案存在性檢查</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">compare_data_structures</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較 list vs set 的查詢效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 模擬測試檔案列表</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">test_files_list</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;test_hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.py&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">test_files_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">test_files_list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># 要查詢的檔案</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">queries</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;test_hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.py&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">150</span><span class="p">)]</span>  <span class="c1"># 50 個存在，50 個不存在</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">10000</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 測試 list</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">for</span> <span class="n">q</span> <span class="ow">in</span> <span class="n">queries</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">_</span> <span class="o">=</span> <span class="n">q</span> <span class="ow">in</span> <span class="n">test_files_list</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">list_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># 測試 set</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">for</span> <span class="n">q</span> <span class="ow">in</span> <span class="n">queries</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">_</span> <span class="o">=</span> <span class="n">q</span> <span class="ow">in</span> <span class="n">test_files_set</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">set_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List 查詢: </span><span class="si">{</span><span class="n">list_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Set 查詢:  </span><span class="si">{</span><span class="n">set_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:    </span><span class="si">{</span><span class="n">list_time</span> <span class="o">/</span> <span class="n">set_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="n">compare_data_structures</span><span class="p">()</span></span></span></code></pre></div><p>輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">List 查詢: 0.8234s
</span></span><span class="line"><span class="ln">2</span><span class="cl">Set 查詢:  0.0123s
</span></span><span class="line"><span class="ln">3</span><span class="cl">加速比:    66.9x</span></span></code></pre></div><p>當資料量為 100 個元素時，set 比 list 快約 60-70 倍。隨著資料量增加，差距會更大。</p>
<h3 id="何時使用哪種資料結構">何時使用哪種資料結構</h3>
<table>
  <thead>
      <tr>
          <th>操作</th>
          <th>list</th>
          <th>set</th>
          <th>dict</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>查詢元素是否存在</td>
          <td>O(n)</td>
          <td>O(1)</td>
          <td>O(1)</td>
      </tr>
      <tr>
          <td>依索引存取</td>
          <td>O(1)</td>
          <td>N/A</td>
          <td>N/A</td>
      </tr>
      <tr>
          <td>依鍵存取</td>
          <td>N/A</td>
          <td>N/A</td>
          <td>O(1)</td>
      </tr>
      <tr>
          <td>保持順序</td>
          <td>Yes</td>
          <td>No*</td>
          <td>Yes**</td>
      </tr>
      <tr>
          <td>允許重複</td>
          <td>Yes</td>
          <td>No</td>
          <td>Keys: No</td>
      </tr>
  </tbody>
</table>
<p>* Python 3.7+ 的 set 實際上保持插入順序，但這是實作細節，不是語言保證。
** Python 3.7+ 的 dict 保證保持插入順序。</p>
<p><strong>選擇指南</strong>：</p>
<ul>
<li>需要頻繁查詢「是否存在」→ 用 <code>set</code></li>
<li>需要依索引存取 → 用 <code>list</code></li>
<li>需要鍵值對應 → 用 <code>dict</code></li>
<li>需要去重 → 用 <code>set</code></li>
</ul>
<h2 id="優化的代價">優化的代價</h2>
<p>每個優化都有代價，需要評估是否值得。</p>
<h3 id="維護成本">維護成本</h3>
<table>
  <thead>
      <tr>
          <th>優化技術</th>
          <th>效能提升</th>
          <th>程式碼複雜度</th>
          <th>維護成本</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>正則表達式預編譯</td>
          <td>20-30%</td>
          <td>低</td>
          <td>低</td>
      </tr>
      <tr>
          <td>lru_cache</td>
          <td>視命中率而定</td>
          <td>低</td>
          <td>中（需注意快取失效）</td>
      </tr>
      <tr>
          <td>list → set</td>
          <td>數十倍</td>
          <td>低</td>
          <td>低</td>
      </tr>
      <tr>
          <td>自訂資料結構</td>
          <td>視情況</td>
          <td>高</td>
          <td>高</td>
      </tr>
  </tbody>
</table>
<h3 id="何時不該優化">何時不該優化</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不值得優化的情況</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 1. 執行次數很少</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">setup_once</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;只在程式啟動時執行一次&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_all_configs</span><span class="p">()</span>  <span class="c1"># 即使慢也只執行一次</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 2. 已經夠快了</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">check_single_file</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查單一檔案是否存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span>  <span class="c1"># 0.0001s，不需要優化</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 3. I/O 才是真正的瓶頸</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">process_files</span><span class="p">(</span><span class="n">paths</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">paths</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">read_text</span><span class="p">()</span>  <span class="c1"># 瓶頸在這裡</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>  <span class="c1"># 這裡快 10 倍也沒用</span></span></span></code></pre></div><h3 id="優化前檢查清單">優化前檢查清單</h3>
<p>在優化之前，問自己：</p>
<ol>
<li><strong>這段程式碼是瓶頸嗎？</strong> - 用 cProfile 確認</li>
<li><strong>執行頻率高嗎？</strong> - 每秒執行一次 vs 每天執行一次</li>
<li><strong>優化後維護成本增加多少？</strong> - 複雜度 vs 效能</li>
<li><strong>有更簡單的解決方案嗎？</strong> - 演算法改進 vs 微優化</li>
</ol>
<h2 id="思考題">思考題</h2>
<ol>
<li>
<p>在什麼情況下，預編譯正則表達式反而可能降低效能？</p>
</li>
<li>
<p><code>lru_cache</code> 不適合用在什麼樣的函式上？</p>
</li>
<li>
<p>如果一個函式既需要快取，又需要在某些條件下強制重新計算，你會如何設計？</p>
</li>
<li>
<p>為什麼說「先測量，後優化」很重要？能舉一個反例嗎？</p>
</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<h3 id="練習-1分析現有程式碼">練習 1：分析現有程式碼</h3>
<p>用 cProfile 分析你自己專案中的一段程式碼，找出效能瓶頸。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">cProfile</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">pstats</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pstats</span> <span class="kn">import</span> <span class="n">SortKey</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 替換為你要分析的函式</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">your_function</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">profiler</span> <span class="o">=</span> <span class="n">cProfile</span><span class="o">.</span><span class="n">Profile</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">profiler</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 執行多次以獲得可靠數據</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">your_function</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">profiler</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">stats</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="n">profiler</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">stats</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="n">SortKey</span><span class="o">.</span><span class="n">CUMULATIVE</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">stats</span><span class="o">.</span><span class="n">print_stats</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span></span></span></code></pre></div><h3 id="練習-2實作帶統計的快取">練習 2：實作帶統計的快取</h3>
<p>擴展 <code>lru_cache</code>，加入更詳細的統計資訊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">defaultdict</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">cached_with_stats</span><span class="p">(</span><span class="n">maxsize</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">128</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    帶統計資訊的快取裝飾器
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    統計項目：
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 命中/未命中次數
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    - 平均執行時間
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    - 各參數的呼叫次數
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 你的實作</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</span></span></span></code></pre></div><h3 id="練習-3效能比較">練習 3：效能比較</h3>
<p>比較以下三種檢查字串是否包含多個關鍵字的方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">keywords</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;warning&#34;</span><span class="p">,</span> <span class="s2">&#34;critical&#34;</span><span class="p">,</span> <span class="s2">&#34;fatal&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 方法 1：多個 in 檢查</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">method1</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">text</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="n">keywords</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 方法 2：正則表達式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="s2">&#34;|&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">keywords</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">method2</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="nb">bool</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 方法 3：set 交集</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">keywords_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">keywords</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">method3</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">text</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="nb">bool</span><span class="p">(</span><span class="n">words</span> <span class="o">&amp;</span> <span class="n">keywords_set</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 測試並比較效能</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># ...</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<h3 id="入門系列">入門系列</h3>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">3.8 效能迷思與優化策略</a> - 效能優化的基礎知識</li>
</ul>
<h3 id="進階系列">進階系列</h3>
<ul>
<li><a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制</a> - 理解 Python 執行原理</li>
<li><a href="/blog/python-advanced/04-cpython-internals/case-studies/profiling/" data-link-title="案例：效能分析實戰" data-link-desc="用 cProfile 和 line_profiler 分析 Markdown 連結檢查器的效能瓶頸">案例：效能分析實戰</a> - cProfile 深入應用</li>
</ul>
<h3 id="官方文件">官方文件</h3>
<ul>
<li><a href="https://docs.python.org/3/library/profile.html">cProfile 文件</a></li>
<li><a href="https://docs.python.org/3/library/functools.html#functools.lru_cache">functools.lru_cache</a></li>
<li><a href="https://docs.python.org/3/library/re.html#re.compile">re.compile</a></li>
</ul>
<h3 id="外部資源">外部資源</h3>
<ul>
<li><a href="https://www.oreilly.com/library/view/high-performance-python/9781492055013/">High Performance Python</a> - O&rsquo;Reilly 書籍</li>
<li><a href="https://github.com/pyutils/line_profiler">line_profiler</a> - 行級效能分析工具</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">並行處理實戰</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/" data-link-title="案例研究：效能優化實戰" data-link-desc="基於 .claude/lib 的效能優化實戰案例">案例研究</a></em></p>
]]></content:encoded></item><item><title>案例：並行 Hook 驗證</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法，展示如何使用 &lt;code>ThreadPoolExecutor&lt;/code> 配合 &lt;code>submit()&lt;/code> + &lt;code>as_completed()&lt;/code> 實現並行驗證，並加入即時進度報告功能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查&lt;/a>（使用 &lt;code>map()&lt;/code> 的基本並行模式）&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>validate_all_hooks()&lt;/code> 方法需要驗證多個 Hook 檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationIssue&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證問題描述&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># &amp;#34;error&amp;#34; | &amp;#34;warning&amp;#34; | &amp;#34;info&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 10&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl"> &lt;span class="n">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;單個 Hook 的驗證結果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl"> &lt;span class="n">is_compliant&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">__post_init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_compliant&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="n">issue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">issue&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">issues&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證單個 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 32&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證項目：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 34&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 命名規範檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 共用模組導入檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 輸出格式檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 37&lt;/span>&lt;span class="cl">&lt;span class="s2"> - 測試存在性檢查
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 40&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 檔案不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 讀取檔案並執行各項檢查&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 57&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無法讀取 Hook 檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_naming_convention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_lib_imports&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_output_format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_path&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">issues&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">&lt;span class="s2"> 同步版本：依序驗證所有 Hook 檔案
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 84&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="n">hooks_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_resolve_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 89&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hook 目錄不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 找出所有 .py 檔案並依序驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hook_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：循序執行，易於理解和除錯&lt;/li>
&lt;li>&lt;strong>結果有序&lt;/strong>：按檔案名稱排序，輸出一致&lt;/li>
&lt;li>&lt;strong>錯誤處理明確&lt;/strong>：每個驗證結果立即可用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當 Hook 數量增加時：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法，展示如何使用 <code>ThreadPoolExecutor</code> 配合 <code>submit()</code> + <code>as_completed()</code> 實現並行驗證，並加入即時進度報告功能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></li>
<li><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">案例：並行檔案檢查</a>（使用 <code>map()</code> 的基本並行模式）</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的 <code>validate_all_hooks()</code> 方法需要驗證多個 Hook 檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">        驗證單個 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        驗證項目：
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        - 命名規範檢查
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        - 共用模組導入檢查
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        - 輸出格式檢查
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        - 測試存在性檢查
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="n">hook_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="c1"># 讀取檔案並執行各項檢查</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">hook_path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_lib_imports</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_output_format</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_test_exists</span><span class="p">(</span><span class="n">hook_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">        同步版本：依序驗證所有 Hook 檔案
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">if</span> <span class="n">hooks_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">hooks_dir</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">hooks_dir</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resolve_path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">                            <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 目錄不存在: </span><span class="si">{</span><span class="n">hooks_dir</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="c1"># 找出所有 .py 檔案並依序驗證</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單直覺</strong>：循序執行，易於理解和除錯</li>
<li><strong>結果有序</strong>：按檔案名稱排序，輸出一致</li>
<li><strong>錯誤處理明確</strong>：每個驗證結果立即可用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當 Hook 數量增加時：</p>
<ul>
<li><strong>執行時間線性增長</strong>：20 個 Hook，每個 0.1 秒 = 2 秒</li>
<li><strong>無法利用 I/O 等待時間</strong>：讀取檔案時 CPU 閒置</li>
<li><strong>使用者體驗差</strong>：大量 Hook 時沒有進度回饋</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量同步版本的執行時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 20 個 Hook，每個 0.1 秒</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 總計：20 * 0.1 = 2.0 秒</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="map-vs-submit--as_completed">map() vs submit() + as_completed()</h3>
<p>在「並行檔案檢查」案例中，我們使用 <code>executor.map()</code> 實現並行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - 結果按輸入順序返回
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 必須等所有任務完成才能取得結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 無法即時報告進度
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><p><strong><code>map()</code> 的限制</strong>：</p>
<ol>
<li><strong>無法即時取得結果</strong>：必須等待所有任務完成</li>
<li><strong>無法追蹤進度</strong>：不知道哪些任務已完成</li>
<li><strong>異常處理受限</strong>：遇到第一個異常就停止迭代</li>
</ol>
<p><strong><code>submit()</code> + <code>as_completed()</code> 的優勢</strong>：</p>
<ol>
<li><strong>即時取得完成的結果</strong>：任務完成就能處理</li>
<li><strong>支援進度報告</strong>：可以計算已完成數量</li>
<li><strong>更靈活的異常處理</strong>：可以逐一處理每個任務的異常</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    特點：
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 結果按完成順序返回
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 可以即時報告進度
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 更完善的錯誤處理
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="c1"># 個別任務失敗不影響其他任務</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="實作進度報告">實作進度報告</h3>
<p><code>as_completed()</code> 的核心優勢是支援即時進度報告：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">ThreadPoolExecutor</span><span class="p">,</span> <span class="n">as_completed</span><span class="p">,</span> <span class="n">Future</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    帶進度報告的並行驗證
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        progress_callback: 進度回調函式
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            - 參數: (已完成數, 總數, 當前檔案名)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        list[ValidationResult]: 驗證結果列表
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># 提交所有任務，記錄 Future 到路徑的映射</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># 依完成順序處理結果</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">for</span> <span class="n">completed_count</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="c1"># 呼叫進度回調</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed_count</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡單的進度顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="c1"># \r 回到行首覆蓋顯示</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>  <span class="c1"># 完成後換行</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">def</span> <span class="nf">demo_progress</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="n">hooks_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;.claude/hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;開始驗證 Hook 檔案...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_with_progress</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="c1"># 統計結果</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>進度報告的變體</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">completed</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">total</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">current_file</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">elapsed_seconds</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">estimated_remaining</span><span class="p">:</span> <span class="nb">float</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">ProgressTracker</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度追蹤器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">=</span> <span class="n">total</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ProgressInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;更新進度並返回資訊&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">start_time</span><span class="p">)</span><span class="o">.</span><span class="n">total_seconds</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 估算剩餘時間</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">avg_time</span> <span class="o">=</span> <span class="n">elapsed</span> <span class="o">/</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="n">avg_time</span> <span class="o">*</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">remaining</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">return</span> <span class="n">ProgressInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">completed</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">total</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">total</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="n">current_file</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="n">elapsed_seconds</span><span class="o">=</span><span class="n">elapsed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">estimated_remaining</span><span class="o">=</span><span class="n">remaining</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_rich_progress</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    帶詳細進度資訊的驗證
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">    顯示：完成數、百分比、已用時間、預估剩餘時間
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">tracker</span> <span class="o">=</span> <span class="n">ProgressTracker</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">future_to_path</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">            <span class="c1"># 更新並顯示進度</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">            <span class="n">info</span> <span class="o">=</span> <span class="n">tracker</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">            <span class="n">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">
</span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="k">def</span> <span class="nf">print_rich_progress</span><span class="p">(</span><span class="n">info</span><span class="p">:</span> <span class="n">ProgressInfo</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">    <span class="s2">&#34;&#34;&#34;顯示詳細進度&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">20</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">/</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">
</span></span><span class="line"><span class="ln">86</span><span class="cl">    <span class="n">elapsed_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">elapsed_seconds</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="n">remaining_str</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">estimated_remaining</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">s&#34;</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">
</span></span><span class="line"><span class="ln">89</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="si">}</span><span class="s2"> &#34;</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;(</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) | &#34;</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;已用: </span><span class="si">{</span><span class="n">elapsed_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">93</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;剩餘: </span><span class="si">{</span><span class="n">remaining_str</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">current_file</span><span class="p">[:</span><span class="mi">20</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;20</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">95</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">96</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">97</span><span class="cl">
</span></span><span class="line"><span class="ln">98</span><span class="cl">    <span class="k">if</span> <span class="n">info</span><span class="o">.</span><span class="n">completed</span> <span class="o">==</span> <span class="n">info</span><span class="o">.</span><span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">99</span><span class="cl">        <span class="nb">print</span><span class="p">()</span></span></span></code></pre></div><h3 id="錯誤處理策略">錯誤處理策略</h3>
<p><code>submit()</code> + <code>as_completed()</code> 提供更細緻的錯誤處理：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">CANCELLED</span> <span class="o">=</span> <span class="s2">&#34;cancelled&#34;</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="s2">    處理的錯誤類型：
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="s2">    - 驗證邏輯錯誤
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">    - 單一任務超時
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">    - 任務被取消
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        timeout_per_file: 單一檔案的超時秒數
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">        list[DetailedResult]: 包含狀態的詳細結果
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">                <span class="c1"># 設定單一結果的超時</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="k">def</span> <span class="nf">summarize_results</span><span class="p">(</span><span class="n">results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="s2">&#34;&#34;&#34;彙總驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">summary</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="s2">&#34;success&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="s2">&#34;failed&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="s2">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="s2">&#34;non_compliant&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;success&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span> <span class="ow">and</span> <span class="n">r</span><span class="o">.</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;non_compliant&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">elif</span> <span class="n">r</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;timeout&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;failed&#34;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">summary</span><span class="p">[</span><span class="s2">&#34;errors&#34;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">r</span><span class="o">.</span><span class="n">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="k">return</span> <span class="n">summary</span></span></span></code></pre></div><p><strong>錯誤處理模式比較</strong>：</p>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th><code>map()</code></th>
          <th><code>as_completed()</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>異常傳播</td>
          <td>第一個異常就停止</td>
          <td>可逐一處理</td>
      </tr>
      <tr>
          <td>超時控制</td>
          <td>只能設定全域超時</td>
          <td>可設定單一任務超時</td>
      </tr>
      <tr>
          <td>取消處理</td>
          <td>較難實現</td>
          <td>可以取消個別任務</td>
      </tr>
      <tr>
          <td>部分結果</td>
          <td>異常後無法取得</td>
          <td>已完成的結果仍可取得</td>
      </tr>
  </tbody>
</table>
<h2 id="完整程式碼">完整程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">並行 Hook 驗證工具 - 完整範例
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 ThreadPoolExecutor + as_completed 實現：
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">- 並行驗證多個 Hook 檔案
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">- 即時進度報告
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">- 完善的錯誤處理
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">ThreadPoolExecutor</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">as_completed</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Future</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="ne">TimeoutError</span> <span class="k">as</span> <span class="n">FuturesTimeoutError</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="c1"># ===== 資料結構 =====</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationStatus</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">SUCCESS</span> <span class="o">=</span> <span class="s2">&#34;success&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">TIMEOUT</span> <span class="o">=</span> <span class="s2">&#34;timeout&#34;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="k">class</span> <span class="nc">DetailedResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="s2">&#34;&#34;&#34;包含狀態的詳細結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">ValidationStatus</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="c1"># ===== 驗證器 =====</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器（簡化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取 Hook 檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_naming</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_check_imports</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_naming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERNS</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">def</span> <span class="nf">_check_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查導入規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;未導入 hook_io 模組&#34;</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl"><span class="c1"># ===== 並行驗證 =====</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_sync</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="s2">    同步版本（基準對照）
</span></span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">
</span></span><span class="line"><span class="ln">155</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="s2">    使用 map() 的並行版本
</span></span></span><span class="line"><span class="ln">163</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">executor</span><span class="o">.</span><span class="n">map</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">
</span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">progress_callback</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">],</span> <span class="kc">None</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="s2">    使用 submit() + as_completed() 的並行版本
</span></span></span><span class="line"><span class="ln">181</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="s2">        hook_files: Hook 檔案列表
</span></span></span><span class="line"><span class="ln">184</span><span class="cl"><span class="s2">        max_workers: 最大執行緒數
</span></span></span><span class="line"><span class="ln">185</span><span class="cl"><span class="s2">        progress_callback: 進度回調 (completed, total, filename)
</span></span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">187</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">188</span><span class="cl"><span class="s2">        驗證結果列表
</span></span></span><span class="line"><span class="ln">189</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">    <span class="n">results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="c1"># 提交所有任務</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="c1"># 依完成順序處理</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">for</span> <span class="n">completed</span><span class="p">,</span> <span class="n">future</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">            <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">            <span class="n">start</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">                    <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                    <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                        <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證失敗: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">                    <span class="p">)]</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">
</span></span><span class="line"><span class="ln">220</span><span class="cl">            <span class="k">if</span> <span class="n">progress_callback</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">                <span class="n">progress_callback</span><span class="p">(</span><span class="n">completed</span><span class="p">,</span> <span class="n">total</span><span class="p">,</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">
</span></span><span class="line"><span class="ln">225</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_error_handling</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="n">max_workers</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">    <span class="n">timeout_per_file</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">5.0</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s2">    帶完善錯誤處理的並行驗證
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">    <span class="n">detailed_results</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">DetailedResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="n">max_workers</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">future_to_path</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Future</span><span class="p">,</span> <span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">            <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">validator</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">f</span><span class="p">)):</span> <span class="n">f</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">future_to_path</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">future_to_path</span><span class="p">[</span><span class="n">future</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout_per_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">SUCCESS</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">                    <span class="n">result</span><span class="o">=</span><span class="n">result</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">            <span class="k">except</span> <span class="n">FuturesTimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;驗證超時 (</span><span class="si">{</span><span class="n">timeout_per_file</span><span class="si">}</span><span class="s2">s)&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="n">detailed_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">DetailedResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">                    <span class="n">path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">                    <span class="n">status</span><span class="o">=</span><span class="n">ValidationStatus</span><span class="o">.</span><span class="n">FAILED</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">                    <span class="n">error</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="k">return</span> <span class="n">detailed_results</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl"><span class="c1"># ===== 進度顯示 =====</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">
</span></span><span class="line"><span class="ln">269</span><span class="cl"><span class="k">def</span> <span class="nf">print_progress</span><span class="p">(</span><span class="n">completed</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">filename</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="s2">&#34;&#34;&#34;進度條顯示&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="n">percentage</span> <span class="o">=</span> <span class="p">(</span><span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="n">bar_length</span> <span class="o">=</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="n">filled</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">bar_length</span> <span class="o">*</span> <span class="n">completed</span> <span class="o">/</span> <span class="n">total</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="n">bar</span> <span class="o">=</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="n">filled</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="n">bar_length</span> <span class="o">-</span> <span class="n">filled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">
</span></span><span class="line"><span class="ln">276</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">        <span class="sa">f</span><span class="s2">&#34;</span><span class="se">\r</span><span class="s2">[</span><span class="si">{</span><span class="n">bar</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">completed</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">total</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">percentage</span><span class="si">:</span><span class="s2">.0f</span><span class="si">}</span><span class="s2">%) - </span><span class="si">{</span><span class="n">filename</span><span class="si">:</span><span class="s2">&lt;30</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">
</span></span><span class="line"><span class="ln">281</span><span class="cl">    <span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="n">total</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="c1"># ===== 效能測試 =====</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">
</span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">3</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="s2">    比較不同策略的執行時間
</span></span></span><span class="line"><span class="ln">289</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">
</span></span><span class="line"><span class="ln">292</span><span class="cl">    <span class="c1"># 同步版本</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">        <span class="n">validate_all_hooks_sync</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="c1"># map() 版本</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">        <span class="n">validate_all_hooks_map</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;map&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="c1"># as_completed() 版本</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">        <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">        <span class="n">validate_all_hooks_async</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="n">results</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">
</span></span><span class="line"><span class="ln">318</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">
</span></span><span class="line"><span class="ln">320</span><span class="cl"><span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範並行 Hook 驗證&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 並行 Hook 驗證示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">
</span></span><span class="line"><span class="ln">324</span><span class="cl">    <span class="c1"># 建立測試用的 Hook 檔案</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="n">test_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/tmp/test_hooks&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">hook_files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">20</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">test_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;hook-</span><span class="si">{</span><span class="n">i</span><span class="si">:</span><span class="s2">02d</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl">        <span class="n">hook_file</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;&#39;&#39;#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">332</span><span class="cl"><span class="s1">&#34;&#34;&#34;Test hook </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s1">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">333</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">334</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">335</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln">336</span><span class="cl"><span class="s1">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">337</span><span class="cl"><span class="s1">    write_hook_output(</span><span class="se">{{</span><span class="s1">&#34;status&#34;: &#34;ok&#34;</span><span class="se">}}</span><span class="s1">)
</span></span></span><span class="line"><span class="ln">338</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln">339</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">340</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln">341</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">        <span class="n">hook_files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">
</span></span><span class="line"><span class="ln">344</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試檔案數: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="n">times</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">hook_files</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</span><span class="cl">    <span class="k">for</span> <span class="n">strategy</span><span class="p">,</span> <span class="n">elapsed</span> <span class="ow">in</span> <span class="n">times</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   </span><span class="si">{</span><span class="n">strategy</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">
</span></span><span class="line"><span class="ln">352</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;sync&#34;</span><span class="p">]</span> <span class="o">/</span> <span class="n">times</span><span class="p">[</span><span class="s2">&#34;as_completed&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   加速比: </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">354</span><span class="cl">
</span></span><span class="line"><span class="ln">355</span><span class="cl">    <span class="c1"># 帶進度的驗證</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;2. 帶進度報告的驗證:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">validate_all_hooks_async</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">hook_files</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="n">progress_callback</span><span class="o">=</span><span class="n">print_progress</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">
</span></span><span class="line"><span class="ln">362</span><span class="cl">    <span class="n">compliant</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="mi">1</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">results</span> <span class="k">if</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">   合規: </span><span class="si">{</span><span class="n">compliant</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">results</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="c1"># 清理測試檔案</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">    <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">367</span><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="n">test_dir</span><span class="o">.</span><span class="n">rmdir</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">
</span></span><span class="line"><span class="ln">370</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">371</span><span class="cl">    <span class="n">demo</span><span class="p">()</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<h3 id="測試環境">測試環境</h3>
<ul>
<li>Python 3.11</li>
<li>20 個 Hook 檔案</li>
<li>每個驗證包含：檔案讀取、正則匹配、路徑檢查</li>
</ul>
<h3 id="測試結果">測試結果</h3>
<table>
  <thead>
      <tr>
          <th>策略</th>
          <th>執行時間</th>
          <th>加速比</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>同步 (基準)</td>
          <td>0.85s</td>
          <td>1.0x</td>
      </tr>
      <tr>
          <td>map()</td>
          <td>0.25s</td>
          <td>3.4x</td>
      </tr>
      <tr>
          <td>as_completed()</td>
          <td>0.26s</td>
          <td>3.3x</td>
      </tr>
      <tr>
          <td>as_completed() + 進度</td>
          <td>0.27s</td>
          <td>3.1x</td>
      </tr>
  </tbody>
</table>
<p><strong>觀察</strong>：</p>
<ol>
<li><code>map()</code> 和 <code>as_completed()</code> 效能相近</li>
<li>進度報告的額外開銷約 3-5%</li>
<li>實際加速比接近 <code>min(hook_count, max_workers)</code></li>
</ol>
<h3 id="不同檔案數量的效能">不同檔案數量的效能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Hook 數量    同步      並行(4)    加速比
</span></span><span class="line"><span class="ln">2</span><span class="cl">-----------------------------------------
</span></span><span class="line"><span class="ln">3</span><span class="cl">5           0.21s     0.08s      2.6x
</span></span><span class="line"><span class="ln">4</span><span class="cl">10          0.42s     0.14s      3.0x
</span></span><span class="line"><span class="ln">5</span><span class="cl">20          0.85s     0.26s      3.3x
</span></span><span class="line"><span class="ln">6</span><span class="cl">50          2.10s     0.58s      3.6x
</span></span><span class="line"><span class="ln">7</span><span class="cl">100         4.25s     1.12s      3.8x</span></span></code></pre></div><p>加速比隨檔案數量增加而提升，趨近於 <code>max_workers</code> 數量。</p>
<h2 id="設計權衡">設計權衡</h2>
<h3 id="map-vs-as_completed-選擇指南">map() vs as_completed() 選擇指南</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">需要並行處理多個獨立任務？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 是 → 需要即時進度報告？
</span></span><span class="line"><span class="ln">3</span><span class="cl">│        ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">4</span><span class="cl">│        └── 否 → 需要細緻的錯誤處理？
</span></span><span class="line"><span class="ln">5</span><span class="cl">│                 ├── 是 → 使用 submit() + as_completed()
</span></span><span class="line"><span class="ln">6</span><span class="cl">│                 └── 否 → 使用 map()（更簡潔）
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── 否 → 直接循序執行</span></span></code></pre></div><h3 id="比較表">比較表</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>map()</th>
          <th>submit() + as_completed()</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式碼複雜度</td>
          <td>低</td>
          <td>中</td>
      </tr>
      <tr>
          <td>結果順序</td>
          <td>保持輸入順序</td>
          <td>按完成順序</td>
      </tr>
      <tr>
          <td>進度報告</td>
          <td>不支援</td>
          <td>支援</td>
      </tr>
      <tr>
          <td>異常處理</td>
          <td>第一個異常就停止</td>
          <td>可逐一處理</td>
      </tr>
      <tr>
          <td>單一任務超時</td>
          <td>不支援</td>
          <td>支援</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>批次處理，不需即時回饋</td>
          <td>需要進度報告或細緻錯誤處理</td>
      </tr>
  </tbody>
</table>
<h3 id="進度報告的開銷">進度報告的開銷</h3>
<table>
  <thead>
      <tr>
          <th>進度報告方式</th>
          <th>額外開銷</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>無</td>
          <td>0%</td>
      </tr>
      <tr>
          <td>簡單計數器</td>
          <td>~1%</td>
      </tr>
      <tr>
          <td>進度條（無 flush）</td>
          <td>~2%</td>
      </tr>
      <tr>
          <td>進度條（每次 flush）</td>
          <td>~5%</td>
      </tr>
      <tr>
          <td>詳細進度（含時間估算）</td>
          <td>~8%</td>
      </tr>
  </tbody>
</table>
<p>對於大量任務（&gt;100），建議每 N 個任務更新一次進度，而非每個任務都更新。</p>
<h2 id="練習">練習</h2>
<h3 id="練習-1加入跳過已驗證功能">練習 1：加入「跳過已驗證」功能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cache</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">cache</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationResult</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    只驗證快取中沒有的檔案
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - 檢查 cache 中是否已有結果
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - 只對新檔案提交任務
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 合併快取結果和新結果
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-2實作取消機制">練習 2：實作取消機制</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_cancel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">should_cancel</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">bool</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    支援取消的並行驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    當 should_cancel() 返回 True 時，取消所有未完成的任務。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 使用 future.cancel() 取消未開始的任務
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 已開始的任務無法取消，需等待完成
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 返回已完成的結果
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="練習-3實作優先順序">練習 3：實作優先順序</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_with_priority</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">priority_fn</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">Path</span><span class="p">],</span> <span class="nb">int</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    按優先順序驗證
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    高優先順序的檔案先被驗證。
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - 按優先順序排序後提交
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    - 但 as_completed 仍按完成順序返回
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    - 考慮使用 PriorityQueue 控制提交順序
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題實作可暫停恢復的驗證">挑戰題：實作可暫停/恢復的驗證</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">PausableValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    可暫停和恢復的驗證器
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        validator = PausableValidator(hook_files)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        validator.start()
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        validator.pause()  # 暫停，已提交的任務會完成
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        # ...
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        validator.resume()  # 恢復
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        results = validator.get_results()
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    提示：
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    - 使用 threading.Event 控制暫停
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    - 追蹤已完成和未開始的任務
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    - 恢復時只提交剩餘任務
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_files</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Path</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hook_files</span> <span class="o">=</span> <span class="n">hook_files</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_paused</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">pause</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">resume</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">get_results</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.as_completed">as_completed 與 wait 的差異</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html#future-objects">Future 物件的方法</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></em></p>
]]></content:encoded></item><item><title>案例：記憶體優化</title><link>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/case-studies/memory-optimization/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/config_loader.py&lt;/code> 的實際程式碼，展示如何用 &lt;code>__slots__&lt;/code> 和 &lt;code>weakref&lt;/code> 優化記憶體使用。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/04-cpython-internals/memory-gc/" data-link-title="3.2 記憶體管理與垃圾回收" data-link-desc="理解 Python 的記憶體管理機制">4.2 記憶體管理與垃圾回收&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>config_loader.py&lt;/code> 使用全域字典作為快取，這是一個常見的設計模式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># Global cache variables&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">_quality_rules_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2"> 載入代理人配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用模組層級變數作為快取，避免重複讀取檔案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">clear_config_cache&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;清除配置快取（用於測試或配置熱更新）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_agents_config_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_quality_rules_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="n">_agents_config_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這種設計簡單直觀，但當系統需要快取更複雜的物件時，會遇到記憶體問題。&lt;/p>
&lt;h3 id="記憶體問題">記憶體問題&lt;/h3>
&lt;p>當快取大量物件時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Python 字典有額外開銷&lt;/strong>：每個字典需要維護 hash table、keys、values&lt;/li>
&lt;li>&lt;strong>物件的 &lt;code>__dict__&lt;/code> 佔用記憶體&lt;/strong>：每個實例都有自己的屬性字典&lt;/li>
&lt;li>&lt;strong>快取可能導致記憶體洩漏&lt;/strong>：強引用阻止物件被回收&lt;/li>
&lt;/ul>
&lt;p>讓我們用一個更複雜的快取場景來說明問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItem&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;配置項目，模擬複雜的快取物件&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># Create a config item and measure memory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItem&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;database.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># Object size&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ConfigItem size: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># ConfigItem size: 48 bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># But the real cost is in __dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;__dict__ size: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="c1"># __dict__ size: 184 bytes&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>當快取數萬個這樣的物件時，記憶體開銷會非常可觀。&lt;/p>
&lt;hr>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="優化目標">優化目標&lt;/h3>
&lt;ol>
&lt;li>減少每個物件的記憶體佔用&lt;/li>
&lt;li>避免快取導致的記憶體洩漏&lt;/li>
&lt;li>保持 API 不變&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1使用-__slots__-減少物件大小">步驟 1：使用 &lt;code>__slots__&lt;/code> 減少物件大小&lt;/h4>
&lt;p>&lt;code>__slots__&lt;/code> 告訴 Python 這個類別只會有哪些屬性，讓直譯器可以用更緊湊的方式儲存資料：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItemWithoutSlots&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;標準類別，使用 __dict__&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigItemWithSlots&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;使用 __slots__ 優化記憶體&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="vm">__slots__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;key&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;metadata&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;access_count&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;last_accessed&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">metadata&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">access_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_accessed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="c1"># Compare memory usage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="n">item_without&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItemWithoutSlots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;db.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;str&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="n">item_with&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigItemWithSlots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;db.host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;str&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Without __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_without&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;With __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_with&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="c1"># The real difference is __dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;__dict__ overhead: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getsizeof&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item_without&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> bytes&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="c1"># item_with has no __dict__!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">item_with&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">AttributeError&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;No __dict__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="記憶體結構比較">記憶體結構比較&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">沒有 __slots__:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">┌──────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">│ PyObject header (16 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">│ __dict__ 指標 (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">│ __weakref__ 指標 (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">│ __dict__ (separate object): │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ - hash table (64 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">│ - keys array (40 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">│ - values array (40 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ - key strings (~80 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">│ Total: ~256 bytes per object │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">└──────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">有 __slots__:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">┌──────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">│ PyObject header (16 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">│ key slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">│ value slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">│ metadata slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">│ access_count slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">│ last_accessed slot (8 B) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">│ Total: ~56 bytes per object │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">└──────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="大量物件的記憶體節省">大量物件的記憶體節省&lt;/h5>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">tracemalloc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Measure memory for creating multiple objects&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">start&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">objects&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">cls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;key_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;value_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;index&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="n">current&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_traced_memory&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">tracemalloc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stop&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">current&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objects&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># Measure both classes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="n">mem_without&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak_without&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ConfigItemWithoutSlots&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="n">mem_with&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">peak_with&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">measure_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ConfigItemWithSlots&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Without __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;With __slots__: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_with&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Savings: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">mem_with&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.2f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> MB&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Ratio: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">mem_without&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">mem_with&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.1f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">x&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="c1"># Typical output:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="c1"># Without __slots__: 3.82 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="c1"># With __slots__: 1.15 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="c1"># Savings: 2.67 MB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="c1"># Ratio: 3.3x&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2使用-weakref-避免強引用">步驟 2：使用 weakref 避免強引用&lt;/h4>
&lt;p>&lt;code>weakref&lt;/code> 讓我們可以引用物件，但不阻止它被垃圾回收：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/config_loader.py</code> 的實際程式碼，展示如何用 <code>__slots__</code> 和 <code>weakref</code> 優化記憶體使用。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制</a></li>
<li><a href="/blog/python-advanced/04-cpython-internals/memory-gc/" data-link-title="3.2 記憶體管理與垃圾回收" data-link-desc="理解 Python 的記憶體管理機制">4.2 記憶體管理與垃圾回收</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>config_loader.py</code> 使用全域字典作為快取，這是一個常見的設計模式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Global cache variables</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_agents_config_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_quality_rules_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">load_agents_config</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    載入代理人配置
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    使用模組層級變數作為快取，避免重複讀取檔案。
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">_agents_config_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="n">_get_default_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="n">_agents_config_cache</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">def</span> <span class="nf">clear_config_cache</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;&#34;&#34;清除配置快取（用於測試或配置熱更新）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">global</span> <span class="n">_agents_config_cache</span><span class="p">,</span> <span class="n">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">_agents_config_cache</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><p>這種設計簡單直觀，但當系統需要快取更複雜的物件時，會遇到記憶體問題。</p>
<h3 id="記憶體問題">記憶體問題</h3>
<p>當快取大量物件時：</p>
<ul>
<li><strong>Python 字典有額外開銷</strong>：每個字典需要維護 hash table、keys、values</li>
<li><strong>物件的 <code>__dict__</code> 佔用記憶體</strong>：每個實例都有自己的屬性字典</li>
<li><strong>快取可能導致記憶體洩漏</strong>：強引用阻止物件被回收</li>
</ul>
<p>讓我們用一個更複雜的快取場景來說明問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItem</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置項目，模擬複雜的快取物件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Create a config item and measure memory</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">item</span> <span class="o">=</span> <span class="n">ConfigItem</span><span class="p">(</span><span class="s2">&#34;database.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;string&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Object size</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;ConfigItem size: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># ConfigItem size: 48 bytes</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># But the real cost is in __dict__</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;__dict__ size: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># __dict__ size: 184 bytes</span></span></span></code></pre></div><p>當快取數萬個這樣的物件時，記憶體開銷會非常可觀。</p>
<hr>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="優化目標">優化目標</h3>
<ol>
<li>減少每個物件的記憶體佔用</li>
<li>避免快取導致的記憶體洩漏</li>
<li>保持 API 不變</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1使用-__slots__-減少物件大小">步驟 1：使用 <code>__slots__</code> 減少物件大小</h4>
<p><code>__slots__</code> 告訴 Python 這個類別只會有哪些屬性，讓直譯器可以用更緊湊的方式儲存資料：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItemWithoutSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;標準類別，使用 __dict__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigItemWithSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 __slots__ 優化記憶體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;metadata&#39;</span><span class="p">,</span> <span class="s1">&#39;access_count&#39;</span><span class="p">,</span> <span class="s1">&#39;last_accessed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_accessed</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># Compare memory usage</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">item_without</span> <span class="o">=</span> <span class="n">ConfigItemWithoutSlots</span><span class="p">(</span><span class="s2">&#34;db.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;str&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">item_with</span> <span class="o">=</span> <span class="n">ConfigItemWithSlots</span><span class="p">(</span><span class="s2">&#34;db.host&#34;</span><span class="p">,</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;str&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Without __slots__: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_without</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;With __slots__:    </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_with</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># The real difference is __dict__</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;__dict__ overhead: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">item_without</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># item_with has no __dict__!</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">item_with</span><span class="o">.</span><span class="vm">__dict__</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">except</span> <span class="ne">AttributeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No __dict__: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h5 id="記憶體結構比較">記憶體結構比較</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">沒有 __slots__:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">┌──────────────────────────────────┐
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">│ PyObject header         (16 B)   │
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">│ __dict__ 指標            (8 B)   │
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│ __weakref__ 指標         (8 B)   │
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│                                  │
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│ __dict__ (separate object):      │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   - hash table          (64 B)   │
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">│   - keys array          (40 B)   │
</span></span><span class="line"><span class="ln">10</span><span class="cl">│   - values array        (40 B)   │
</span></span><span class="line"><span class="ln">11</span><span class="cl">│   - key strings        (~80 B)   │
</span></span><span class="line"><span class="ln">12</span><span class="cl">│                                  │
</span></span><span class="line"><span class="ln">13</span><span class="cl">│ Total: ~256 bytes per object     │
</span></span><span class="line"><span class="ln">14</span><span class="cl">└──────────────────────────────────┘
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">有 __slots__:
</span></span><span class="line"><span class="ln">17</span><span class="cl">┌──────────────────────────────────┐
</span></span><span class="line"><span class="ln">18</span><span class="cl">│ PyObject header         (16 B)   │
</span></span><span class="line"><span class="ln">19</span><span class="cl">│ key slot                 (8 B)   │
</span></span><span class="line"><span class="ln">20</span><span class="cl">│ value slot               (8 B)   │
</span></span><span class="line"><span class="ln">21</span><span class="cl">│ metadata slot            (8 B)   │
</span></span><span class="line"><span class="ln">22</span><span class="cl">│ access_count slot        (8 B)   │
</span></span><span class="line"><span class="ln">23</span><span class="cl">│ last_accessed slot       (8 B)   │
</span></span><span class="line"><span class="ln">24</span><span class="cl">│                                  │
</span></span><span class="line"><span class="ln">25</span><span class="cl">│ Total: ~56 bytes per object      │
</span></span><span class="line"><span class="ln">26</span><span class="cl">└──────────────────────────────────┘</span></span></code></pre></div><h5 id="大量物件的記憶體節省">大量物件的記憶體節省</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">measure_memory</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure memory for creating multiple objects&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">objects</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">cls</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;key_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">current</span><span class="p">,</span> <span class="n">peak</span><span class="p">,</span> <span class="n">objects</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Measure both classes</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">mem_without</span><span class="p">,</span> <span class="n">peak_without</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">measure_memory</span><span class="p">(</span><span class="n">ConfigItemWithoutSlots</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">mem_with</span><span class="p">,</span> <span class="n">peak_with</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">measure_memory</span><span class="p">(</span><span class="n">ConfigItemWithSlots</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Without __slots__: </span><span class="si">{</span><span class="n">mem_without</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;With __slots__:    </span><span class="si">{</span><span class="n">mem_with</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Savings:           </span><span class="si">{</span><span class="p">(</span><span class="n">mem_without</span> <span class="o">-</span> <span class="n">mem_with</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> MB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Ratio:             </span><span class="si">{</span><span class="n">mem_without</span> <span class="o">/</span> <span class="n">mem_with</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># Typical output:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Without __slots__: 3.82 MB</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># With __slots__:    1.15 MB</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># Savings:           2.67 MB</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># Ratio:             3.3x</span></span></span></code></pre></div><h4 id="步驟-2使用-weakref-避免強引用">步驟 2：使用 weakref 避免強引用</h4>
<p><code>weakref</code> 讓我們可以引用物件，但不阻止它被垃圾回收：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">CacheableConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;可以被弱引用的配置物件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;_data&#39;</span><span class="p">,</span> <span class="s1">&#39;__weakref__&#39;</span><span class="p">]</span>  <span class="c1"># Note: __weakref__ slot</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_data</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;CacheableConfig(</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">key</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="si">!r}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Create object and weak reference</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">weak_ref</span> <span class="o">=</span> <span class="n">weakref</span><span class="o">.</span><span class="n">ref</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Object exists: </span><span class="si">{</span><span class="n">weak_ref</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># Object exists: CacheableConfig(&#39;app.name&#39;, &#39;MyApp&#39;)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># Delete the strong reference</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">del</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;After del: </span><span class="si">{</span><span class="n">weak_ref</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># After del: None</span></span></span></code></pre></div><h5 id="使用-callback-追蹤物件回收">使用 callback 追蹤物件回收</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">on_finalize</span><span class="p">(</span><span class="n">ref</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Callback when object is garbage collected&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Object was garbage collected!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;db.port&#34;</span><span class="p">,</span> <span class="s2">&#34;5432&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">weak_ref</span> <span class="o">=</span> <span class="n">weakref</span><span class="o">.</span><span class="n">ref</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="n">on_finalize</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deleting object...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">del</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Output: Object was garbage collected!</span></span></span></code></pre></div><h4 id="步驟-3使用-weakvaluedictionary">步驟 3：使用 WeakValueDictionary</h4>
<p><code>WeakValueDictionary</code> 是實作自動清理快取的利器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">WeakCache</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Auto-cleaning cache using weak references.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Objects are automatically removed from cache when no strong
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    references exist outside the cache.
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">T</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">factory</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        Get item from cache, creating it if necessary.
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            key: Cache key
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">            factory: Function to create value if not cached
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">            Cached or newly created value
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="n">factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">def</span> <span class="fm">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Return cache statistics&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">/</span> <span class="n">total</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1%</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">
</span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="c1"># Demo: automatic cleanup</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="n">cache</span> <span class="o">=</span> <span class="n">WeakCache</span><span class="p">[</span><span class="n">CacheableConfig</span><span class="p">]()</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="c1"># Create and cache object</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="n">config1</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="n">config2</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">CacheableConfig</span><span class="p">(</span><span class="s2">&#34;app.name&#34;</span><span class="p">,</span> <span class="s2">&#34;MyApp&#34;</span><span class="p">))</span>  <span class="c1"># Cache hit</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cache size: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># 1</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Same object: </span><span class="si">{</span><span class="n">config1</span> <span class="ow">is</span> <span class="n">config2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Stats: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># hits=1, misses=1</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="c1"># Delete strong reference</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="k">del</span> <span class="n">config1</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="k">del</span> <span class="n">config2</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="c1"># Object is garbage collected, cache is auto-cleaned</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">
</span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cache size after cleanup: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># 0</span></span></span></code></pre></div><h4 id="步驟-4測量記憶體使用">步驟 4：測量記憶體使用</h4>
<p>使用 <code>sys.getsizeof</code> 和 <code>tracemalloc</code> 進行精確測量：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">pympler</span> <span class="kn">import</span> <span class="n">asizeof</span>  <span class="c1"># pip install pympler</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">measure_object_size</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;Object&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure object size using different methods&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># Basic size (doesn&#39;t include referenced objects)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">basic</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># Deep size (includes all referenced objects)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># Using pympler for accurate measurement</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">deep</span> <span class="o">=</span> <span class="n">asizeof</span><span class="o">.</span><span class="n">asizeof</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">label</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  sys.getsizeof: </span><span class="si">{</span><span class="n">basic</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  pympler deep:  </span><span class="si">{</span><span class="n">deep</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">basic</span><span class="p">,</span> <span class="n">deep</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1"># Compare different object types</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">item_without</span> <span class="o">=</span> <span class="n">ConfigItemWithoutSlots</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">,</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">item_with</span> <span class="o">=</span> <span class="n">ConfigItemWithSlots</span><span class="p">(</span><span class="s2">&#34;key&#34;</span><span class="p">,</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">measure_object_size</span><span class="p">(</span><span class="n">item_without</span><span class="p">,</span> <span class="s2">&#34;Without __slots__&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="n">measure_object_size</span><span class="p">(</span><span class="n">item_with</span><span class="p">,</span> <span class="s2">&#34;With __slots__&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Using tracemalloc for allocation tracking</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="k">def</span> <span class="nf">track_allocations</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Track memory allocations during execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># Simulate creating many cached objects</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">items</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">items</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ConfigItemWithSlots</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">,</span> <span class="s2">&#34;active&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="c1"># Get snapshot</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">snapshot</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">top_stats</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="n">statistics</span><span class="p">(</span><span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Top 5 memory allocations:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">top_stats</span><span class="p">[:</span><span class="mi">5</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">stat</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># Get traced memory</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Current memory: </span><span class="si">{</span><span class="n">current</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Peak memory:    </span><span class="si">{</span><span class="n">peak</span> <span class="o">/</span> <span class="mi">1024</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> KB&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">return</span> <span class="n">items</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="n">track_allocations</span><span class="p">()</span></span></span></code></pre></div><h5 id="比較記憶體差異的完整腳本">比較記憶體差異的完整腳本</h5>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">StandardConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Standard class with __dict__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">metadata</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">SlottedConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Optimized with __slots__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;metadata&#39;</span><span class="p">,</span> <span class="s1">&#39;hits&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">metadata</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">metadata</span> <span class="o">=</span> <span class="n">metadata</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">class</span> <span class="nc">DataclassConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using dataclass&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">key</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">hits</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="nd">@dataclass</span><span class="p">(</span><span class="n">slots</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>  <span class="c1"># Python 3.10+</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">class</span> <span class="nc">SlottedDataclass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Dataclass with __slots__&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">key</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">metadata</span><span class="p">:</span> <span class="nb">dict</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">hits</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_memory</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10000</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Benchmark memory usage for a class&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">objects</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="bp">cls</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;key_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;index&#34;</span><span class="p">:</span> <span class="n">i</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">current</span><span class="p">,</span> <span class="n">peak</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">get_traced_memory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="n">per_object</span> <span class="o">=</span> <span class="n">current</span> <span class="o">/</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">label</span> <span class="ow">or</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">:</span><span class="s2">25</span><span class="si">}</span><span class="s2"> | &#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">          <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">current</span><span class="o">/</span><span class="mi">1024</span><span class="si">:</span><span class="s2">8.1f</span><span class="si">}</span><span class="s2"> KB | &#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">          <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">per_object</span><span class="si">:</span><span class="s2">6.1f</span><span class="si">}</span><span class="s2"> B/obj&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="k">return</span> <span class="n">objects</span>  <span class="c1"># Keep reference to prevent GC</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="s1">&#39;Class&#39;</span><span class="si">:</span><span class="s2">25</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="s1">&#39;Total&#39;</span><span class="si">:</span><span class="s2">&gt;10</span><span class="si">}</span><span class="s2"> | </span><span class="si">{</span><span class="s1">&#39;Per Object&#39;</span><span class="si">:</span><span class="s2">&gt;10</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">55</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">
</span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">StandardConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">SlottedConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">DataclassConfig</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="n">benchmark_memory</span><span class="p">(</span><span class="n">SlottedDataclass</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">
</span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="c1"># Typical output:</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="c1"># Class                     |      Total |  Per Object</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="c1"># -------------------------------------------------------</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="c1"># StandardConfig            |   2578.5 KB |  263.6 B/obj</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># SlottedConfig             |    859.4 KB |   87.9 B/obj</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="c1"># DataclassConfig           |   2656.3 KB |  271.6 B/obj</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="c1"># SlottedDataclass          |    898.4 KB |   91.9 B/obj</span></span></span></code></pre></div><hr>
<h3 id="完整程式碼">完整程式碼</h3>
<p>以下是整合所有優化技術的完整實作：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Memory-optimized configuration cache system.
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">This module demonstrates:
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">- Using __slots__ to reduce object memory footprint
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">- Using weakref for automatic cache cleanup
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">- Using tracemalloc for memory profiling
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">Based on patterns from .claude/lib/config_loader.py
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">weakref</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s1">&#39;T&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigEntry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    Memory-optimized configuration entry.
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">    Uses __slots__ to reduce memory footprint by ~3x compared
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">    to regular classes.
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="s1">&#39;key&#39;</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="s1">&#39;source&#39;</span><span class="p">,</span> <span class="s1">&#39;loaded_at&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="s1">&#39;access_count&#39;</span><span class="p">,</span> <span class="s1">&#39;__weakref__&#39;</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">        <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="n">source</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="n">source</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">loaded_at</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;ConfigEntry(key=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">key</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;value=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;accesses=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">access_count</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">def</span> <span class="nf">touch</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Record an access to this entry&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">access_count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="k">class</span> <span class="nc">SmartConfigCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s2">    Smart configuration cache with automatic memory management.
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">    Features:
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">    - Weak references for automatic cleanup
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    - Memory usage tracking
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">    - Hit/miss statistics
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">    - Optional size limits
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s2">        cache = SmartConfigCache(max_size=1000)
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">        # Get or create config
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s2">        config = cache.get_or_create(
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s2">            &#34;database.host&#34;,
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="s2">            lambda: ConfigEntry(&#34;database.host&#34;, &#34;localhost&#34;, &#34;env&#34;)
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="s2">        )
</span></span></span><span class="line"><span class="ln"> 75</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">        # Check stats
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">        print(cache.stats())
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_size</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">        Initialize the cache.
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="s2">            max_size: Maximum number of entries. None for unlimited.
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">:</span> <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ConfigEntry</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="n">weakref</span><span class="o">.</span><span class="n">WeakValueDictionary</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ConfigEntry</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>  <span class="c1"># Keep important items</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span> <span class="o">=</span> <span class="n">max_size</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ConfigEntry</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        Get entry from cache.
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">            ConfigEntry if found, None otherwise
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="c1"># Check strong refs first</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">entry</span><span class="o">.</span><span class="n">touch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="c1"># Then check weak refs</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">entry</span><span class="o">.</span><span class="n">touch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="k">def</span> <span class="nf">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">factory</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">ConfigEntry</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="n">keep_strong</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ConfigEntry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">        Get existing entry or create new one.
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">            factory: Function to create entry if not found
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">            keep_strong: If True, keep a strong reference (won&#39;t auto-cleanup)
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="s2">            Existing or newly created ConfigEntry
</span></span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">            <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="c1"># Create new entry</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="n">factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="k">if</span> <span class="n">keep_strong</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_enforce_size_limit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="k">return</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="k">def</span> <span class="nf">_enforce_size_limit</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Evict old entries if cache is full&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">        <span class="k">while</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_max_size</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">            <span class="c1"># Evict least accessed entry</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                <span class="k">break</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="n">min_key</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">keys</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">                <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">k</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">.</span><span class="n">access_count</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">min_key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="k">def</span> <span class="nf">pin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">        Pin an entry to prevent automatic cleanup.
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="s2">            True if entry was pinned, False if not found
</span></span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">        <span class="k">if</span> <span class="n">entry</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_enforce_size_limit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">entry</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="k">def</span> <span class="nf">unpin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="s2">        Unpin an entry to allow automatic cleanup.
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">194</span><span class="cl"><span class="s2">            key: Configuration key
</span></span></span><span class="line"><span class="ln">195</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">196</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="s2">            True if entry was unpinned, False if not found
</span></span></span><span class="line"><span class="ln">198</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="k">def</span> <span class="nf">clear</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Clear all cached entries&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="s2">        Get cache statistics.
</span></span></span><span class="line"><span class="ln">212</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">            Dict with hits, misses, hit_rate, size, pinned, evictions
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span> <span class="o">/</span> <span class="n">total</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1%</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">            <span class="s2">&#34;total_size&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">            <span class="s2">&#34;weak_refs&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">            <span class="s2">&#34;pinned&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="s2">&#34;evictions&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_evictions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">228</span><span class="cl">
</span></span><span class="line"><span class="ln">229</span><span class="cl">    <span class="k">def</span> <span class="nf">memory_usage</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">231</span><span class="cl"><span class="s2">        Estimate memory usage of cached entries.
</span></span></span><span class="line"><span class="ln">232</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">234</span><span class="cl"><span class="s2">            Dict with entry_count, estimated_bytes, per_entry_bytes
</span></span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">        <span class="n">all_entries</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="o">.</span><span class="n">values</span><span class="p">())</span> <span class="o">+</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_strong_refs</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">all_entries</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">                <span class="s2">&#34;entry_count&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">                <span class="s2">&#34;estimated_bytes&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">                <span class="s2">&#34;per_entry_bytes&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">        <span class="c1"># Estimate based on first entry</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">        <span class="n">sample</span> <span class="o">=</span> <span class="n">all_entries</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">        <span class="n">per_entry</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">sample</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">            <span class="s2">&#34;entry_count&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_entries</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">            <span class="s2">&#34;estimated_bytes&#34;</span><span class="p">:</span> <span class="n">per_entry</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_entries</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">            <span class="s2">&#34;per_entry_bytes&#34;</span><span class="p">:</span> <span class="n">per_entry</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">
</span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="k">def</span> <span class="nf">demo_memory_optimization</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Demonstrate memory optimization techniques&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">
</span></span><span class="line"><span class="ln">258</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Memory Optimization Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="c1"># Start memory tracking</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="n">snapshot1</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">
</span></span><span class="line"><span class="ln">267</span><span class="cl">    <span class="c1"># Create cache and populate</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="n">cache</span> <span class="o">=</span> <span class="n">SmartConfigCache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">
</span></span><span class="line"><span class="ln">270</span><span class="cl">    <span class="c1"># Simulate loading many configurations</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="n">entries</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">            <span class="k">lambda</span> <span class="n">i</span><span class="o">=</span><span class="n">i</span><span class="p">:</span> <span class="n">ConfigEntry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;config.item.</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;value_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">                <span class="s2">&#34;demo&#34;</span>
</span></span><span class="line"><span class="ln">279</span><span class="cl">            <span class="p">),</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">keep_strong</span><span class="o">=</span><span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">)</span>  <span class="c1"># Pin first 100</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="n">entries</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">
</span></span><span class="line"><span class="ln">284</span><span class="cl">    <span class="c1"># Take snapshot after creation</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="n">snapshot2</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl">    <span class="c1"># Print stats</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Cache Statistics:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Memory Usage:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">    <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">cache</span><span class="o">.</span><span class="n">memory_usage</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">value</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="c1"># Show memory diff</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="n">diff</span> <span class="o">=</span> <span class="n">snapshot2</span><span class="o">.</span><span class="n">compare_to</span><span class="p">(</span><span class="n">snapshot1</span><span class="p">,</span> <span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Top 5 Memory Allocations:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">diff</span><span class="p">[:</span><span class="mi">5</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">stat</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="c1"># Demo weak reference cleanup</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Weak Reference Cleanup Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">
</span></span><span class="line"><span class="ln">308</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Before cleanup - Cache size: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()[</span><span class="s1">&#39;total_size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</span><span class="cl">
</span></span><span class="line"><span class="ln">310</span><span class="cl">    <span class="c1"># Delete external references to unpinned entries</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="k">del</span> <span class="n">entries</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">
</span></span><span class="line"><span class="ln">314</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;After cleanup  - Cache size: </span><span class="si">{</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">()[</span><span class="s1">&#39;total_size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">315</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;(Only pinned entries remain)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="n">tracemalloc</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">
</span></span><span class="line"><span class="ln">319</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="n">demo_memory_optimization</span><span class="p">()</span></span></span></code></pre></div><hr>
<h3 id="使用範例">使用範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">memory_optimized_cache</span> <span class="kn">import</span> <span class="n">SmartConfigCache</span><span class="p">,</span> <span class="n">ConfigEntry</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Initialize cache with size limit</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">cache</span> <span class="o">=</span> <span class="n">SmartConfigCache</span><span class="p">(</span><span class="n">max_size</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># Load configuration</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">load_database_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Factory function to load database config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">ConfigEntry</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">key</span><span class="o">=</span><span class="s2">&#34;database&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">value</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="s2">&#34;host&#34;</span><span class="p">:</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s2">&#34;port&#34;</span><span class="p">:</span> <span class="mi">5432</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;myapp&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">source</span><span class="o">=</span><span class="s2">&#34;config.yaml&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># Get or create (with strong reference for important config)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">db_config</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;database&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">load_database_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">keep_strong</span><span class="o">=</span><span class="kc">True</span>  <span class="c1"># Keep in memory</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Database host: </span><span class="si">{</span><span class="n">db_config</span><span class="o">.</span><span class="n">value</span><span class="p">[</span><span class="s1">&#39;host&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Temporary config (will be auto-cleaned when not referenced)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">temp_config</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get_or_create</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;temp.setting&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">lambda</span><span class="p">:</span> <span class="n">ConfigEntry</span><span class="p">(</span><span class="s2">&#34;temp.setting&#34;</span><span class="p">,</span> <span class="s2">&#34;temporary&#34;</span><span class="p">,</span> <span class="s2">&#34;runtime&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># Check statistics</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">cache</span><span class="o">.</span><span class="n">stats</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"># {&#39;hits&#39;: 0, &#39;misses&#39;: 2, &#39;hit_rate&#39;: &#39;0.0%&#39;,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1">#  &#39;total_size&#39;: 2, &#39;weak_refs&#39;: 1, &#39;pinned&#39;: 1, &#39;evictions&#39;: 0}</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># Memory usage</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">cache</span><span class="o">.</span><span class="n">memory_usage</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1"># {&#39;entry_count&#39;: 2, &#39;estimated_bytes&#39;: 112, &#39;per_entry_bytes&#39;: 56}</span></span></span></code></pre></div><hr>
<h2 id="設計權衡">設計權衡</h2>
<h3 id="__slots__-vs-標準類別"><code>__slots__</code> vs 標準類別</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>標準類別</th>
          <th><code>__slots__</code></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>記憶體佔用</strong></td>
          <td>較多（有 <code>__dict__</code>）</td>
          <td>較少（節省 ~60-70%）</td>
      </tr>
      <tr>
          <td><strong>動態屬性</strong></td>
          <td>支援 <code>obj.new_attr = x</code></td>
          <td>不支援（除非加 <code>__dict__</code>）</td>
      </tr>
      <tr>
          <td><strong>繼承</strong></td>
          <td>簡單</td>
          <td>子類別需要自己的 <code>__slots__</code></td>
      </tr>
      <tr>
          <td><strong>弱引用</strong></td>
          <td>預設支援</td>
          <td>需要加入 <code>__weakref__</code> slot</td>
      </tr>
      <tr>
          <td><strong>Pickle</strong></td>
          <td>直接支援</td>
          <td>需要 <code>__getstate__</code>/<code>__setstate__</code></td>
      </tr>
      <tr>
          <td><strong>多重繼承</strong></td>
          <td>正常運作</td>
          <td>多個父類別不能都有非空 <code>__slots__</code></td>
      </tr>
  </tbody>
</table>
<h3 id="強引用-vs-弱引用快取">強引用 vs 弱引用快取</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>強引用快取</th>
          <th>弱引用快取</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>記憶體管理</strong></td>
          <td>需要手動清理</td>
          <td>自動清理</td>
      </tr>
      <tr>
          <td><strong>資料保證</strong></td>
          <td>資料一定存在</td>
          <td>資料可能被回收</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>關鍵配置</td>
          <td>暫時性資料</td>
      </tr>
      <tr>
          <td><strong>實作複雜度</strong></td>
          <td>簡單</td>
          <td>稍微複雜</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用哪種技術">何時使用哪種技術？</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">決策樹：
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">物件數量多嗎？
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">├── 是 → 考慮 __slots__
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">│   └── 需要動態屬性嗎？
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">│       ├── 是 → __slots__ = [..., &#39;__dict__&#39;]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">│       └── 否 → __slots__ = [...]
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">└── 否 → 標準類別即可
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">快取可能無限增長嗎？
</span></span><span class="line"><span class="ln">11</span><span class="cl">├── 是 → 使用 WeakValueDictionary 或 LRU
</span></span><span class="line"><span class="ln">12</span><span class="cl">└── 否 → 普通字典即可
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">資料可以被回收嗎？
</span></span><span class="line"><span class="ln">15</span><span class="cl">├── 是 → weakref
</span></span><span class="line"><span class="ln">16</span><span class="cl">└── 否 → 強引用</span></span></code></pre></div><hr>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>建立大量小物件</strong>：如資料點、事件、配置項目</li>
<li><strong>記憶體使用是瓶頸</strong>：經過 profiling 確認</li>
<li><strong>快取可能無限增長</strong>：如用戶 session、請求資料</li>
<li><strong>長時間運行的服務</strong>：如 web server、daemon</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>物件數量很少</strong>：優化效果不明顯</li>
<li><strong>需要動態新增屬性</strong>：<code>__slots__</code> 會限制彈性</li>
<li><strong>過早優化</strong>：先確認是否真的有問題</li>
<li><strong>程式碼可讀性優先</strong>：標準類別更直觀</li>
</ul>
<h3 id="優化決策流程">優化決策流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Step 1: Profile first!</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># Don&#39;t optimize until you know where the problem is</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">tracemalloc</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># ... run your code ...</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">snapshot</span> <span class="o">=</span> <span class="n">tracemalloc</span><span class="o">.</span><span class="n">take_snapshot</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">top_stats</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="n">statistics</span><span class="p">(</span><span class="s1">&#39;lineno&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">for</span> <span class="n">stat</span> <span class="ow">in</span> <span class="n">top_stats</span><span class="p">[:</span><span class="mi">10</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">stat</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># Step 2: If memory is the issue, identify the class</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># Look for classes with many instances</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kn">import</span> <span class="nn">gc</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">Counter</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">counter</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span> <span class="k">for</span> <span class="n">obj</span> <span class="ow">in</span> <span class="n">gc</span><span class="o">.</span><span class="n">get_objects</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">counter</span><span class="o">.</span><span class="n">most_common</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># Step 3: Only then apply __slots__ to hot classes</span></span></span></code></pre></div><hr>
<h2 id="練習">練習</h2>
<h3 id="基礎練習比較有無-__slots__-的記憶體差異">基礎練習：比較有無 <code>__slots__</code> 的記憶體差異</h3>
<p>撰寫一個腳本，比較以下三種類別建立 100,000 個實例的記憶體使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Complete this benchmark script</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">tracemalloc</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 1. Standard class</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">PointStandard</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 2. Class with __slots__</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">class</span> <span class="nc">PointSlots</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="vm">__slots__</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 3. Named tuple</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">PointNamed</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">&#39;PointNamed&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;x&#39;</span><span class="p">,</span> <span class="s1">&#39;y&#39;</span><span class="p">,</span> <span class="s1">&#39;z&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># TODO: Write benchmark function</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">100000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Measure memory for creating `count` instances&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># Implement this</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># TODO: Compare results</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="c1"># Expected: PointSlots uses ~3x less memory than PointStandard</span></span></span></code></pre></div><h3 id="進階練習實作-weakref-快取">進階練習：實作 weakref 快取</h3>
<p>建立一個 <code>ImageCache</code> 類別，具有以下功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Implement ImageCache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">ImageCache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Cache for image data with automatic cleanup.
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Requirements:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - Use WeakValueDictionary for auto-cleanup
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    - Track hit/miss statistics
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    - Support maximum size limit
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    - Provide memory usage estimation
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">    Example usage:
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        cache = ImageCache(max_size=100)
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        img = cache.get_or_load(&#34;photo.jpg&#34;, lambda: load_image(&#34;photo.jpg&#34;))
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        print(cache.stats())
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        # {&#39;hits&#39;: 0, &#39;misses&#39;: 1, &#39;size&#39;: 1}
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_size</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># TODO: Initialize cache</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">get_or_load</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">loader</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># TODO: Implement get or load logic</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">stats</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="c1"># TODO: Return cache statistics</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="挑戰題用-tracemalloc-追蹤記憶體洩漏">挑戰題：用 tracemalloc 追蹤記憶體洩漏</h3>
<p>給定以下有記憶體洩漏的程式碼，使用 <code>tracemalloc</code> 找出問題並修復：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Exercise: Find and fix the memory leak</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">EventHandler</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">_handlers</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># Class variable - potential leak!</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">EventHandler</span><span class="o">.</span><span class="n">_handlers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>  <span class="c1"># Leak: strong reference</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">callback</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">callback</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">fire</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">for</span> <span class="n">cb</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">callbacks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">cb</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">process_events</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;This function creates handlers but never cleans them up&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">handler</span> <span class="o">=</span> <span class="n">EventHandler</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;handler_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">handler</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">handler</span><span class="o">.</span><span class="n">fire</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;event_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># handler goes out of scope but is still in _handlers!</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># TODO:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># 1. Use tracemalloc to measure memory growth</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 2. Identify the leak</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 3. Fix EventHandler to use weak references</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="c1"># 4. Verify the fix with tracemalloc</span></span></span></code></pre></div><hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#slots"><code>__slots__</code> 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/weakref.html">weakref 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/tracemalloc.html">tracemalloc 官方文件</a></li>
<li><a href="https://pympler.readthedocs.io/">Pympler - Memory profiling</a></li>
<li><a href="https://realpython.com/python-memory-management/">Python Memory Management - Real Python</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/04-cpython-internals/case-studies/profiling/" data-link-title="案例：效能分析實戰" data-link-desc="用 cProfile 和 line_profiler 分析 Markdown 連結檢查器的效能瓶頸">效能分析實戰</a></em>
<em>返回：<a href="/blog/python-advanced/04-cpython-internals/" data-link-title="模組四：CPython 內部機制" data-link-desc="深入 CPython 直譯器，理解 Python 如何運作">模組四：CPython 內部機制</a></em></p>
]]></content:encoded></item><item><title>案例：正則表達式預編譯</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何透過正則表達式預編譯來減少重複編譯的開銷。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八基礎章節&lt;/a>&lt;/li>
&lt;li>Python &lt;code>re&lt;/code> 模組基本操作&lt;/li>
&lt;li>基本的效能測量概念&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 是一個 Hook 合規性驗證工具，用於檢查 Hook 腳本是否遵循專案規範。它定義了多組正則表達式模式來偵測各種程式碼特徵：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 共用模組導入模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_LOGGING_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_logging\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">CONFIG_LOADER_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.config_loader\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">GIT_UTILS_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.git_utils\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 輸出函式使用模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;write_hook_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_pretooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_posttooluse_output\s*\(&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 不推薦的輸出模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">BAD_OUTPUT_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;print\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 命名規範模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">VALID_NAME_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Hook 類型推測模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="n">HOOK_TYPE_HINTS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_pretooluse_output|permissionDecision&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;create_posttooluse_output|additionalContext&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;Stop|subagent&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;SessionStart|session_id&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>目前的實作將模式儲存為字串列表，並在輔助方法中使用 &lt;code>re.search()&lt;/code> 進行匹配：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_has_import&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查是否有符合任一模式的導入&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_matches_pattern&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">patterns&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;檢查是否符合任一模式&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">patterns&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="python-re-的內部快取">Python re 的內部快取&lt;/h3>
&lt;p>在討論優化之前，我們需要了解 Python &lt;code>re&lt;/code> 模組的內部機制。&lt;/p>
&lt;p>當你使用 &lt;code>re.search(pattern, string)&lt;/code> 這樣的函式時，Python 會在內部執行兩個步驟：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>編譯&lt;/strong>：將正則表達式字串轉換為內部的 pattern 物件&lt;/li>
&lt;li>&lt;strong>匹配&lt;/strong>：使用 pattern 物件對目標字串進行匹配&lt;/li>
&lt;/ol>
&lt;p>為了避免重複編譯，&lt;code>re&lt;/code> 模組內建了一個 LRU 快取：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 內部實作概念（簡化版）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n">_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n">_MAXCACHE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">512&lt;/span> &lt;span class="c1"># Python 3.12 的預設值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 快取命中&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 實際編譯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sre_compile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 儲存到快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="n">_MAXCACHE&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 快取滿了就清空&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">compiled&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">compiled&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可以驗證這個快取的存在：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">re&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查看快取大小&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;快取大小上限: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_MAXCACHE&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 第一次呼叫會編譯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\d+&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;test123&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 查看快取內容（僅供觀察，不建議在生產環境使用）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;目前快取數量: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼還需要手動預編譯">為什麼還需要手動預編譯？&lt;/h3>
&lt;p>既然 &lt;code>re&lt;/code> 有內建快取，為什麼還需要手動使用 &lt;code>re.compile()&lt;/code>？原因有幾個：&lt;/p>
&lt;h4 id="1-快取查找有開銷">1. 快取查找有開銷&lt;/h4>
&lt;p>每次使用 &lt;code>re.search()&lt;/code> 時，都需要：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 虛擬碼：每次 re.search() 的內部流程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 1. 建立快取鍵（需要計算 hash）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 2. 查找快取（dict lookup）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="n">compiled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_compile_and_cache&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flags&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 3. 執行匹配&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">compiled&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">string&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>相比之下，預編譯後直接使用：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何透過正則表達式預編譯來減少重複編譯的開銷。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八基礎章節</a></li>
<li>Python <code>re</code> 模組基本操作</li>
<li>基本的效能測量概念</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 是一個 Hook 合規性驗證工具，用於檢查 Hook 腳本是否遵循專案規範。它定義了多組正則表達式模式來偵測各種程式碼特徵：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="c1"># 共用模組導入模式</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 輸出函式使用模式</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 不推薦的輸出模式</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># 命名規範模式</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="c1"># Hook 類型推測模式</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="p">]</span></span></span></code></pre></div><p>目前的實作將模式儲存為字串列表，並在輔助方法中使用 <code>re.search()</code> 進行匹配：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><h3 id="python-re-的內部快取">Python re 的內部快取</h3>
<p>在討論優化之前，我們需要了解 Python <code>re</code> 模組的內部機制。</p>
<p>當你使用 <code>re.search(pattern, string)</code> 這樣的函式時，Python 會在內部執行兩個步驟：</p>
<ol>
<li><strong>編譯</strong>：將正則表達式字串轉換為內部的 pattern 物件</li>
<li><strong>匹配</strong>：使用 pattern 物件對目標字串進行匹配</li>
</ol>
<p>為了避免重複編譯，<code>re</code> 模組內建了一個 LRU 快取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Python 內部實作概念（簡化版）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">_cache</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">_MAXCACHE</span> <span class="o">=</span> <span class="mi">512</span>  <span class="c1"># Python 3.12 的預設值</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">_compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>  <span class="c1"># 快取命中</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 實際編譯</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">compiled</span> <span class="o">=</span> <span class="n">sre_compile</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 儲存到快取</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">_MAXCACHE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>  <span class="c1"># 快取滿了就清空</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">compiled</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">compiled</span></span></span></code></pre></div><p>你可以驗證這個快取的存在：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 查看快取大小</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取大小上限: </span><span class="si">{</span><span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 第一次呼叫會編譯</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\d+&#39;</span><span class="p">,</span> <span class="s1">&#39;test123&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 查看快取內容（僅供觀察，不建議在生產環境使用）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目前快取數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="為什麼還需要手動預編譯">為什麼還需要手動預編譯？</h3>
<p>既然 <code>re</code> 有內建快取，為什麼還需要手動使用 <code>re.compile()</code>？原因有幾個：</p>
<h4 id="1-快取查找有開銷">1. 快取查找有開銷</h4>
<p>每次使用 <code>re.search()</code> 時，都需要：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 虛擬碼：每次 re.search() 的內部流程</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">string</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 1. 建立快取鍵（需要計算 hash）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">pattern</span><span class="p">),</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 2. 查找快取（dict lookup）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">compiled</span> <span class="o">=</span> <span class="n">_cache</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">compiled</span> <span class="o">=</span> <span class="n">_compile_and_cache</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 3. 執行匹配</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="n">compiled</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">string</span><span class="p">)</span></span></span></code></pre></div><p>相比之下，預編譯後直接使用：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 預編譯</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;\d+&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 直接使用，無需快取查找</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">string</span><span class="p">)</span></span></span></code></pre></div><h4 id="2-快取可能被清空">2. 快取可能被清空</h4>
<p>當快取達到上限（預設 512 個模式）時，整個快取會被清空：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">_MAXCACHE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">_cache</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>  <span class="c1"># 全部清空！</span></span></span></code></pre></div><p>這表示在大型專案中，你的常用模式可能會被意外從快取中移除。</p>
<h4 id="3-語意更清晰">3. 語意更清晰</h4>
<p>預編譯讓程式碼意圖更明確：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 不清楚：pattern 是什麼時候編譯的？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern1&#39;</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">if</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern2&#39;</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 清楚：模式在類別載入時就編譯好了</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">PATTERN1</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern1&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">PATTERN2</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;pattern2&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">PATTERN1</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="o">...</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">PATTERN2</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="o">...</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別需要預編譯的模式">步驟 1：識別需要預編譯的模式</h4>
<p>首先，找出所有會被重複使用的正則表達式。在 <code>hook_validator.py</code> 中，以下模式會在每次驗證時使用：</p>
<ul>
<li>導入檢查模式（7 組，共 14 個模式）</li>
<li>輸出格式檢查模式（5 個模式）</li>
<li>命名規範模式（1 個模式）</li>
<li>Hook 類型推測模式（4 個模式）</li>
</ul>
<h4 id="步驟-2建立預編譯版本">步驟 2：建立預編譯版本</h4>
<p>將字串模式轉換為已編譯的 pattern 物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidatorOptimized</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用預編譯正則表達式的 Hook 驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 預編譯的導入模式</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># 預編譯的輸出模式</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="c1"># 預編譯的命名模式</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="c1"># 預編譯的 Hook 類型推測模式</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="c1"># 其他預編譯模式</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">JSON_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;json\.dumps&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_.*_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="p">]</span></span></span></code></pre></div><h4 id="步驟-3更新匹配方法">步驟 3：更新匹配方法</h4>
<p>修改輔助方法，使用預編譯的 pattern 物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入（使用預編譯模式）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>  <span class="c1"># 直接使用 Pattern.search()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式（使用預編譯模式）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">_has_json_output</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查是否有 JSON 輸出相關程式碼&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">JSON_OUTPUT_PATTERNS</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><h3 id="完整程式碼">完整程式碼</h3>
<p>以下是完整的優化版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">Hook 合規性驗證工具（優化版）
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">使用 re.compile 預編譯所有正則表達式，減少重複編譯開銷。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">Tuple</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證問題描述&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">level</span><span class="p">:</span> <span class="nb">str</span>  <span class="c1"># &#34;error&#34; | &#34;warning&#34; | &#34;info&#34;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">message</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">line</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">suggestion</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單個 Hook 的驗證結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">issues</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">is_compliant</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">is_compliant</span> <span class="o">=</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">            <span class="n">issue</span><span class="o">.</span><span class="n">level</span> <span class="o">==</span> <span class="s2">&#34;error&#34;</span> <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">issues</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidatorOptimized</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    使用預編譯正則表達式的 Hook 驗證器
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    所有正則表達式在類別定義時編譯一次，
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">    之後所有實例共享這些已編譯的 pattern 物件。
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="c1"># ===== 預編譯的正則表達式 =====</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="c1"># 導入模式</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="n">HOOK_IO_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="n">HOOK_LOGGING_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="n">CONFIG_LOADER_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="n">GIT_UTILS_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1"># 輸出模式</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="c1"># 命名模式</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">    <span class="n">VALID_NAME_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="c1"># Hook 類型推測</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">]]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_pretooluse_output|permissionDecision&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_posttooluse_output|additionalContext&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;Stop|subagent&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;SessionStart|session_id&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="c1"># JSON 輸出檢測</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="n">JSON_OUTPUT_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;json\.dumps&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;write_hook_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;create_.*_output&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="c1"># 功能推測模式</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">CONFIG_KEYWORDS_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;load_config|configuration|config|yaml|json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="n">GIT_KEYWORDS_PATTERN</span><span class="p">:</span> <span class="n">Pattern</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;git|branch|commit|worktree|is_protected_branch|get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;初始化驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_import</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有符合任一模式的導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="nf">_matches_pattern</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否符合任一模式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="k">def</span> <span class="nf">_has_json_output</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查是否有 JSON 輸出相關程式碼&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">JSON_OUTPUT_PATTERNS</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="k">def</span> <span class="nf">_needs_config_loader</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷 Hook 是否需要配置載入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">CONFIG_KEYWORDS_PATTERN</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="k">if</span> <span class="n">hook_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="n">name_lower</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">name_lower</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;config&#34;</span><span class="p">,</span> <span class="s2">&#34;agent&#34;</span><span class="p">,</span> <span class="s2">&#34;dispatch&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="k">def</span> <span class="nf">_needs_git_utils</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Path</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="s2">&#34;&#34;&#34;判斷 Hook 是否需要 Git 操作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">GIT_KEYWORDS_PATTERN</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">
</span></span><span class="line"><span class="ln">146</span><span class="cl">        <span class="k">if</span> <span class="n">hook_path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">            <span class="n">name_lower</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">            <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">kw</span> <span class="ow">in</span> <span class="n">name_lower</span> <span class="k">for</span> <span class="n">kw</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;git&#34;</span><span class="p">,</span> <span class="s2">&#34;commit&#34;</span><span class="p">,</span> <span class="s2">&#34;worktree&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">
</span></span><span class="line"><span class="ln">153</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming_convention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">154</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查命名規範&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERN</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">filename</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;建議使用 snake-case 或 kebab-case 命名&#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">
</span></span><span class="line"><span class="ln">167</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="k">def</span> <span class="nf">infer_hook_type</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據內容推測 Hook 類型&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">for</span> <span class="n">hook_type</span><span class="p">,</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_TYPE_HINTS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="k">if</span> <span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">                <span class="k">return</span> <span class="n">hook_type</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">path</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">
</span></span><span class="line"><span class="ln">182</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook 檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">                <span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">                <span class="n">issues</span><span class="o">=</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;無法讀取檔案: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">check_naming_convention</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="c1"># 使用預編譯模式進行各項檢查</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_has_import</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">HOOK_IO_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;未導入 hook_io 模組&#34;</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">
</span></span><span class="line"><span class="ln">212</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_matches_pattern</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">BAD_OUTPUT_PATTERNS</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;warning&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;使用不推薦的輸出方式&#34;</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">
</span></span><span class="line"><span class="ln">218</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">hook_path</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">issues</span><span class="p">)</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<p>使用 <code>timeit</code> 模組來精確測量預編譯帶來的效能提升：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">正則表達式預編譯效能測試
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">比較：
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">1. 每次使用 re.search(string_pattern, content)
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">2. 使用預編譯的 pattern.search(content)
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Pattern</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="c1"># 測試用的正則表達式模式（來自 hook_validator.py）</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="n">STRING_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_io\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.hook_logging\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.config_loader\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;from\s+lib\.git_utils\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;write_hook_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;create_pretooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;create_posttooluse_output\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="sa">r</span><span class="s1">&#39;print\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="sa">r</span><span class="s1">&#39;sys\.stdout\.write\s*\(\s*json\.dumps\s*\(&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/08-practical-optimization/case-studies/regex-precompile/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="c1"># 預編譯版本</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="n">COMPILED_PATTERNS</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">STRING_PATTERNS</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="c1"># 模擬的 Hook 檔案內容</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="n">SAMPLE_CONTENT</span> <span class="o">=</span> <span class="s1">&#39;&#39;&#39;
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s1">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s1">&#34;&#34;&#34;Sample hook for testing&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s1">import json
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s1">import sys
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s1">from pathlib import Path
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s1">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s1">from hook_logging import setup_hook_logging
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s1">from config_loader import load_config
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s1">logger = setup_hook_logging(__name__)
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s1">def main():
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s1">    &#34;&#34;&#34;Main entry point&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s1">    input_data = read_hook_input()
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s1">    config = load_config()
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s1">    # Process the input
</span></span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="s1">    result = process(input_data, config)
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s1">    # Write output using recommended function
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s1">    write_hook_output({
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s1">        &#34;result&#34;: &#34;continue&#34;,
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s1">        &#34;additionalContext&#34;: result
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s1">    })
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s1">def process(data, config):
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s1">    &#34;&#34;&#34;Process the hook data&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s1">    return {&#34;status&#34;: &#34;ok&#34;}
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s1">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s1">    main()
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s1">&#39;&#39;&#39;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="k">def</span> <span class="nf">search_with_strings</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用字串模式搜尋（每次都會經過 re 快取）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="nb">bool</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="k">def</span> <span class="nf">search_with_compiled</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">patterns</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Pattern</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用預編譯模式搜尋&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="nb">bool</span><span class="p">(</span><span class="n">pattern</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">patterns</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">
</span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行效能測試&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="c1"># 預熱（讓 re 模組的快取填滿）</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="c1"># 測試參數</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">10000</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="n">repeat</span> <span class="o">=</span> <span class="mi">5</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="c1"># 測試字串模式（有 re 快取）</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">    <span class="n">string_times</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">repeat</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">iterations</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="n">repeat</span><span class="o">=</span><span class="n">repeat</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="c1"># 測試預編譯模式</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">compiled_times</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">repeat</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="n">iterations</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">repeat</span><span class="o">=</span><span class="n">repeat</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="c1"># 計算結果</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">string_best</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">string_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="n">compiled_best</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">compiled_times</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">string_best</span> <span class="o">/</span> <span class="n">compiled_best</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="c1"># 輸出結果</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;正則表達式預編譯效能測試&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;測試內容大小: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">)</span><span class="si">}</span><span class="s2"> 字元&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;模式數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">STRING_PATTERNS</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;迭代次數: </span><span class="si">{</span><span class="n">iterations</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> 次 x </span><span class="si">{</span><span class="n">repeat</span><span class="si">}</span><span class="s2"> 輪&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;結果（最佳時間）:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式 (re.search):     </span><span class="si">{</span><span class="n">string_best</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式 (Pattern):     </span><span class="si">{</span><span class="n">compiled_best</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:                   </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="c1"># 單次操作時間</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">string_per_op</span> <span class="o">=</span> <span class="p">(</span><span class="n">string_best</span> <span class="o">/</span> <span class="n">iterations</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span>  <span class="c1"># 微秒</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">    <span class="n">compiled_per_op</span> <span class="o">=</span> <span class="p">(</span><span class="n">compiled_best</span> <span class="o">/</span> <span class="n">iterations</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">
</span></span><span class="line"><span class="ln">136</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;單次操作時間:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;-&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式:                 </span><span class="si">{</span><span class="n">string_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式:               </span><span class="si">{</span><span class="n">compiled_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;每次節省:                 </span><span class="si">{</span><span class="n">string_per_op</span> <span class="o">-</span> <span class="n">compiled_per_op</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> 微秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="s2">&#34;string_time&#34;</span><span class="p">:</span> <span class="n">string_best</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="s2">&#34;compiled_time&#34;</span><span class="p">:</span> <span class="n">compiled_best</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">speedup</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_cache_miss</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試快取未命中的情況&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;快取未命中測試（清空快取後）&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=&#34;</span> <span class="o">*</span> <span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">
</span></span><span class="line"><span class="ln">154</span><span class="cl">    <span class="n">iterations</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="c1"># 清空 re 模組快取</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="c1"># 測試字串模式（快取被清空）</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>  <span class="c1"># 每次都清空快取</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="n">search_with_strings</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">STRING_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">    <span class="n">string_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="c1"># 測試預編譯模式（不受快取影響）</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>  <span class="c1"># 清空快取不影響預編譯模式</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">        <span class="n">search_with_compiled</span><span class="p">(</span><span class="n">SAMPLE_CONTENT</span><span class="p">,</span> <span class="n">COMPILED_PATTERNS</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">default_timer</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">speedup</span> <span class="o">=</span> <span class="n">string_time</span> <span class="o">/</span> <span class="n">compiled_time</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式 (無快取):        </span><span class="si">{</span><span class="n">string_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式:               </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比:                   </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">
</span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="n">benchmark</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="n">benchmark_cache_miss</span><span class="p">()</span></span></span></code></pre></div><h3 id="典型測試結果">典型測試結果</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">正則表達式預編譯效能測試
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">測試內容大小: 847 字元
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">模式數量: 14 個
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">迭代次數: 10,000 次 x 5 輪
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">結果（最佳時間）:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">------------------------------------------------------------
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">字串模式 (re.search):     0.4823 秒
</span></span><span class="line"><span class="ln">10</span><span class="cl">預編譯模式 (Pattern):     0.3891 秒
</span></span><span class="line"><span class="ln">11</span><span class="cl">加速比:                   1.24x
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">單次操作時間:
</span></span><span class="line"><span class="ln">14</span><span class="cl">------------------------------------------------------------
</span></span><span class="line"><span class="ln">15</span><span class="cl">字串模式:                 48.23 微秒
</span></span><span class="line"><span class="ln">16</span><span class="cl">預編譯模式:               38.91 微秒
</span></span><span class="line"><span class="ln">17</span><span class="cl">每次節省:                 9.32 微秒
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">20</span><span class="cl">快取未命中測試（清空快取後）
</span></span><span class="line"><span class="ln">21</span><span class="cl">============================================================
</span></span><span class="line"><span class="ln">22</span><span class="cl">字串模式 (無快取):        2.3456 秒
</span></span><span class="line"><span class="ln">23</span><span class="cl">預編譯模式:               0.3912 秒
</span></span><span class="line"><span class="ln">24</span><span class="cl">加速比:                   6.00x</span></span></code></pre></div><p>從結果可以看出：</p>
<ul>
<li><strong>正常情況</strong>：預編譯帶來約 <strong>1.2-1.3 倍</strong> 的加速</li>
<li><strong>快取未命中</strong>：當 <code>re</code> 模組快取失效時，加速可達 <strong>6 倍</strong></li>
</ul>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>字串模式</th>
          <th>預編譯模式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體使用</td>
          <td>較低（依賴 re 快取）</td>
          <td>略高（每個 Pattern 物件）</td>
      </tr>
      <tr>
          <td>首次載入</td>
          <td>快（延遲編譯）</td>
          <td>慢（類別載入時編譯）</td>
      </tr>
      <tr>
          <td>執行效能</td>
          <td>依賴快取狀態</td>
          <td>穩定且可預測</td>
      </tr>
      <tr>
          <td>程式碼可讀性</td>
          <td>模式定義較簡潔</td>
          <td>意圖更明確</td>
      </tr>
      <tr>
          <td>型別提示</td>
          <td><code>List[str]</code></td>
          <td><code>List[Pattern]</code></td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>少量模式、低頻呼叫</td>
          <td>多模式、高頻呼叫</td>
      </tr>
  </tbody>
</table>
<h3 id="記憶體考量">記憶體考量</h3>
<p>預編譯的 Pattern 物件會佔用額外記憶體：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">pattern_str</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;from\s+hook_io\s+import&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">pattern_obj</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串大小: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">pattern_str</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Pattern 大小: </span><span class="si">{</span><span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">pattern_obj</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 字串大小: 74 bytes</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># Pattern 大小: 256 bytes（視模式複雜度而定）</span></span></span></code></pre></div><p>但在大多數情況下，這點記憶體是值得的。</p>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合預編譯的情況">適合預編譯的情況</h3>
<ul>
<li>同一個模式會被使用多次（例如在迴圈中）</li>
<li>模式數量較多，可能超過 <code>re</code> 快取上限（512 個）</li>
<li>效能敏感的程式碼路徑</li>
<li>需要穩定、可預測的執行時間</li>
<li>類別或模組級別的模式定義</li>
</ul>
<h3 id="不需要預編譯的情況">不需要預編譯的情況</h3>
<ul>
<li>模式只使用一次</li>
<li>快速原型開發</li>
<li>簡單的腳本工具</li>
<li>模式是動態生成的</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習測量你的正則表達式效能">基礎練習：測量你的正則表達式效能</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 1：測量自己專案中正則表達式的效能
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">步驟：
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">1. 找出你專案中使用正則表達式的程式碼
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">2. 記錄有多少個不同的模式
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">3. 測量預編譯前後的效能差異
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># TODO: 將你專案中的模式填入這裡</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">YOUR_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;your_pattern_1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="sa">r</span><span class="s2">&#34;your_pattern_2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">YOUR_TEST_CONTENT</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">your test content here
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">def</span> <span class="nf">measure_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量效能差異&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 字串版本</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">string_patterns</span> <span class="o">=</span> <span class="n">YOUR_PATTERNS</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="c1"># 預編譯版本</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">compiled_patterns</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">YOUR_PATTERNS</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="c1"># 測量</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">string_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">YOUR_TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">string_patterns</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="mi">10000</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">compiled_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="k">lambda</span><span class="p">:</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">YOUR_TEST_CONTENT</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">compiled_patterns</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">number</span><span class="o">=</span><span class="mi">10000</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;字串模式: </span><span class="si">{</span><span class="n">string_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯模式: </span><span class="si">{</span><span class="n">compiled_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;加速比: </span><span class="si">{</span><span class="n">string_time</span> <span class="o">/</span> <span class="n">compiled_time</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">measure_performance</span><span class="p">()</span></span></span></code></pre></div><h3 id="進階練習監控-re-快取狀態">進階練習：監控 re 快取狀態</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 2：監控 re 模組的快取狀態
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">了解你的程式實際使用了多少快取空間。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">check_cache_status</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;檢查 re 模組快取狀態&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取上限: </span><span class="si">{</span><span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;目前快取數量: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;使用率: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">/</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span> <span class="o">*</span> <span class="mf">0.8</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;警告：快取即將滿載！&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">simulate_cache_overflow</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬快取溢出&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;模擬快取溢出...&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 記錄初始狀態</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">initial_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="c1"># 建立大量不同的模式</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">600</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;pattern_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;test content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">final_count</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">_cache</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;初始快取: </span><span class="si">{</span><span class="n">initial_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;最終快取: </span><span class="si">{</span><span class="n">final_count</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;快取被清空了 </span><span class="si">{</span><span class="p">(</span><span class="mi">600</span> <span class="o">-</span> <span class="n">final_count</span><span class="p">)</span> <span class="o">//</span> <span class="n">re</span><span class="o">.</span><span class="n">_MAXCACHE</span><span class="si">}</span><span class="s2"> 次&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">check_cache_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">simulate_cache_overflow</span><span class="p">()</span></span></span></code></pre></div><h3 id="挑戰題建立自動預編譯裝飾器">挑戰題：建立自動預編譯裝飾器</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">練習 3：建立自動預編譯裝飾器
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">設計一個裝飾器，自動將類別中的字串模式轉換為預編譯模式。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">,</span> <span class="n">Type</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">auto_compile_patterns</span><span class="p">(</span><span class="bp">cls</span><span class="p">:</span> <span class="n">Type</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    類別裝飾器：自動預編譯所有 _PATTERNS 結尾的類別屬性
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    使用方式：
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        @auto_compile_patterns
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        class MyValidator:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            IMPORT_PATTERNS = [
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">                r&#34;from\s+module\s+import&#34;,
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">                r&#34;import\s+module&#34;,
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            ]
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">attr_name</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="n">attr_name</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s2">&#34;_PATTERNS&#34;</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">attr_name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">list</span><span class="p">)</span> <span class="ow">and</span> <span class="n">value</span> <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                <span class="c1"># 將字串列表轉換為預編譯模式列表</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">compiled</span> <span class="o">=</span> <span class="p">[</span><span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="nb">setattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">compiled</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;預編譯 </span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="n">attr_name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">compiled</span><span class="p">)</span><span class="si">}</span><span class="s2"> 個模式&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="c1"># 測試</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="nd">@auto_compile_patterns</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">class</span> <span class="nc">TestValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">IMPORT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;from\s+module\s+import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;import\s+module&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;print\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;write\s*\(&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="n">NOT_A_PATTERN</span> <span class="o">=</span> <span class="s2">&#34;這不是模式列表&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># 驗證轉換結果</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">IMPORT_PATTERNS 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">IMPORT_PATTERNS</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;OUTPUT_PATTERNS 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">OUTPUT_PATTERNS</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;NOT_A_PATTERN 類型: </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">TestValidator</span><span class="o">.</span><span class="n">NOT_A_PATTERN</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/re.html">Python re 模組文件</a></li>
<li><a href="https://docs.python.org/3/howto/regex.html">Regular Expression HOWTO</a></li>
<li><a href="https://docs.python.org/3/howto/regex.html#common-problems">正則表達式效能最佳實踐</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></em></p>
]]></content:encoded></item><item><title>案例：LRU 快取</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/git_utils.py&lt;/code> 的 &lt;code>is_protected_branch()&lt;/code> 和 &lt;code>is_allowed_branch()&lt;/code> 函式，展示如何用 &lt;code>functools.lru_cache&lt;/code> 快取重複的分支檢查結果。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化&lt;/a>&lt;/li>
&lt;li>基本的 Git 分支概念&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>git_utils.py&lt;/code> 提供兩個分支檢查函式：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">fnmatch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 保護分支列表（支援 glob 模式）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">PROTECTED_BRANCHES&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;master&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;develop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;release/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;production&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 允許編輯的分支模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">ALLOWED_BRANCHES&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;feat/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;feature/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;fix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;hotfix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;bugfix/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;chore/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;docs/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;refactor/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;test/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查是否為保護分支
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> branch: 分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> bool: 如果是保護分支返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">PROTECTED_BRANCHES&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">fnmatch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fnmatch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查是否為允許編輯的分支
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="s2"> branch: 分支名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> bool: 如果是允許編輯的分支返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">pattern&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">ALLOWED_BRANCHES&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">fnmatch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fnmatch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>簡單直覺&lt;/strong>：迴圈遍歷模式清單，易於理解&lt;/li>
&lt;li>&lt;strong>彈性高&lt;/strong>：支援 glob 模式，可輕易新增規則&lt;/li>
&lt;li>&lt;strong>無狀態&lt;/strong>：純函數，沒有副作用&lt;/li>
&lt;/ol>
&lt;h3 id="重複計算的開銷">重複計算的開銷&lt;/h3>
&lt;p>在 Hook 驗證流程中，同一個分支可能被檢查多次：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_config&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證 Hook 配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第一次檢查：是否允許執行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Cannot run on protected branch&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 其他驗證邏輯 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次檢查：是否需要額外確認&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">hook_config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;dangerous&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Dangerous operation on protected branch&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">errors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">process_multiple_hooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;處理多個 Hook&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">branch&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">hooks&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 每個 Hook 都會檢查分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 再次檢查保護狀態&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 特殊處理 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>問題分析&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>同一個分支名稱被重複檢查多次&lt;/li>
&lt;li>每次檢查都要遍歷整個模式清單&lt;/li>
&lt;li>&lt;code>fnmatch.fnmatch()&lt;/code> 雖然快，但重複呼叫仍是浪費&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>測量重複呼叫的影響&lt;/strong>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">benchmark_repeated_calls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">float&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;測量重複呼叫的時間&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">is_allowed_branch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">branch&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 測量結果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10000 次呼叫: ~0.05 秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 每次呼叫: ~5 微秒&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="c1"># 看起來很快，但如果在熱路徑上...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="c1"># 100 個 Hook x 10 次檢查 = 1000 次呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># 累積起來就有影響了&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="lru_cache-基礎">lru_cache 基礎&lt;/h3>
&lt;p>&lt;code>functools.lru_cache&lt;/code> 是 Python 內建的記憶化（memoization）裝飾器：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/git_utils.py</code> 的 <code>is_protected_branch()</code> 和 <code>is_allowed_branch()</code> 函式，展示如何用 <code>functools.lru_cache</code> 快取重複的分支檢查結果。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化</a></li>
<li>基本的 Git 分支概念</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>git_utils.py</code> 提供兩個分支檢查函式：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 保護分支列表（支援 glob 模式）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 允許編輯的分支模式</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    檢查是否為保護分支
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：迴圈遍歷模式清單，易於理解</li>
<li><strong>彈性高</strong>：支援 glob 模式，可輕易新增規則</li>
<li><strong>無狀態</strong>：純函數，沒有副作用</li>
</ol>
<h3 id="重複計算的開銷">重複計算的開銷</h3>
<p>在 Hook 驗證流程中，同一個分支可能被檢查多次：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook</span><span class="p">(</span><span class="n">hook_config</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證 Hook 配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 第一次檢查：是否允許執行</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;Cannot run on protected branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># ... 其他驗證邏輯 ...</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 第二次檢查：是否需要額外確認</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span> <span class="ow">and</span> <span class="n">hook_config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;dangerous&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;Dangerous operation on protected branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="n">errors</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">process_multiple_hooks</span><span class="p">(</span><span class="n">hooks</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理多個 Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="n">get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">hook</span> <span class="ow">in</span> <span class="n">hooks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># 每個 Hook 都會檢查分支</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># 再次檢查保護狀態</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">if</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="c1"># ... 特殊處理 ...</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">pass</span></span></span></code></pre></div><p><strong>問題分析</strong>：</p>
<ul>
<li>同一個分支名稱被重複檢查多次</li>
<li>每次檢查都要遍歷整個模式清單</li>
<li><code>fnmatch.fnmatch()</code> 雖然快，但重複呼叫仍是浪費</li>
</ul>
<p><strong>測量重複呼叫的影響</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_repeated_calls</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測量重複呼叫的時間&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">return</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 測量結果</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 10000 次呼叫: ~0.05 秒</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 每次呼叫: ~5 微秒</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 看起來很快，但如果在熱路徑上...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 100 個 Hook x 10 次檢查 = 1000 次呼叫</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 累積起來就有影響了</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="lru_cache-基礎">lru_cache 基礎</h3>
<p><code>functools.lru_cache</code> 是 Python 內建的記憶化（memoization）裝飾器：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">expensive_function</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;結果會被快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 第一次呼叫：實際計算</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result1</span> <span class="o">=</span> <span class="n">expensive_function</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 計算並快取</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 第二次呼叫：直接返回快取結果</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">result2</span> <span class="o">=</span> <span class="n">expensive_function</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 從快取讀取</span></span></span></code></pre></div><p><strong>lru_cache 的適用條件</strong>：</p>
<ol>
<li><strong>純函數</strong>：相同輸入永遠產生相同輸出</li>
<li><strong>可雜湊參數</strong>：所有參數必須是 hashable（可作為 dict 的 key）</li>
<li><strong>沒有副作用</strong>：函數不應修改外部狀態</li>
<li><strong>計算成本 &gt; 快取成本</strong>：快取有記憶體開銷，要值得</li>
</ol>
<p><strong>檢查 <code>is_protected_branch</code> 是否適用</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 純函數？</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">#    是 - 相同分支名稱永遠返回相同結果</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1">#    （前提：PROTECTED_BRANCHES 不會在執行時改變）</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 2. 可雜湊參數？</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">#    是 - str 是 hashable</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 3. 沒有副作用？</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">#    是 - 只讀取全域常數，不修改任何狀態</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 4. 計算成本值得快取？</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">#    要測量...</span></span></span></code></pre></div><h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1加入-lru_cache-裝飾器">步驟 1：加入 lru_cache 裝飾器</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    檢查是否為保護分支（帶快取）
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">        結果會被快取，快取大小為 128 個不同的分支名稱。
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        如果 PROTECTED_BRANCHES 在執行時改變，需要呼叫
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        is_protected_branch.cache_clear() 清除快取。
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支（帶快取）
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">    Note:
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">        結果會被快取。修改 ALLOWED_BRANCHES 後需要
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        呼叫 is_allowed_branch.cache_clear()。
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><h4 id="步驟-2驗證快取行為">步驟 2：驗證快取行為</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">verify_cache_behavior</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證快取確實在運作&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 清除快取，確保乾淨狀態</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># 第一次呼叫</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">result1</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;main&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">info1</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;第一次呼叫: hits=</span><span class="si">{</span><span class="n">info1</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info1</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 輸出: hits=0, misses=1</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 第二次呼叫（相同參數）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">result2</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;main&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">info2</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;第二次呼叫: hits=</span><span class="si">{</span><span class="n">info2</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info2</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 輸出: hits=1, misses=1</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 不同參數</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">result3</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="s2">&#34;feature/new&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">info3</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;不同參數: hits=</span><span class="si">{</span><span class="n">info3</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">, misses=</span><span class="si">{</span><span class="n">info3</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 輸出: hits=1, misses=2</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">verify_cache_behavior</span><span class="p">()</span></span></span></code></pre></div><h3 id="maxsize-的選擇">maxsize 的選擇</h3>
<p><code>maxsize</code> 決定快取可以儲存多少個不同的輸入結果：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># maxsize=None：無限制快取</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 優點：所有結果都快取，命中率最高</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 缺點：記憶體可能無限增長</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">unlimited_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># maxsize=128（預設）：快取最多 128 個結果</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># LRU = Least Recently Used，超過時淘汰最久未使用的</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">limited_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># maxsize=1：只快取最後一個結果</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 適用於「連續呼叫通常是相同參數」的情況</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">single_cache</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span></span></span></code></pre></div><p><strong>選擇策略</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">choose_maxsize</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    選擇 maxsize 的考量因素
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># 因素 1：輸入值的多樣性</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># - 分支名稱通常不多（&lt; 100 種）</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># - 單一專案可能只有 10-20 個分支</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># - maxsize=128 綽綽有餘</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 因素 2：記憶體使用</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># - 每個快取項目: key + value + overhead</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># - str 的 key 大小視長度而定</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># - bool 的 value 很小</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># - 128 個項目 &lt; 10KB，可忽略</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 因素 3：呼叫模式</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># - 如果同一個分支會被檢查很多次 → 大 maxsize</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># - 如果每次都是新分支 → 快取沒意義</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="c1"># 對於分支檢查：</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="c1"># - 通常會重複檢查目前分支</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># - maxsize=32 或 64 就足夠</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="c1"># - 用 128 是保守選擇</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><p><strong>實際測量 maxsize 的影響</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_maxsize</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較不同 maxsize 的效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 模擬分支名稱</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">branches</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span> <span class="s2">&#34;master&#34;</span><span class="p">,</span> <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;feature/auth&#34;</span><span class="p">,</span> <span class="s2">&#34;feature/api&#34;</span><span class="p">,</span> <span class="s2">&#34;feature/ui&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;fix/bug1&#34;</span><span class="p">,</span> <span class="s2">&#34;fix/bug2&#34;</span><span class="p">,</span> <span class="s2">&#34;fix/bug3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;release/v1.0&#34;</span><span class="p">,</span> <span class="s2">&#34;release/v2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># 模擬呼叫模式：80% 是常見分支，20% 是其他</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">common_branches</span> <span class="o">=</span> <span class="n">branches</span><span class="p">[:</span><span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">simulate_calls</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">10000</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="k">if</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">&lt;</span> <span class="mf">0.8</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">common_branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">                <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">func</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="n">func</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># 測試不同 maxsize</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">for</span> <span class="n">maxsize</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="n">maxsize</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">def</span> <span class="nf">test_func</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                    <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">info</span> <span class="o">=</span> <span class="n">simulate_calls</span><span class="p">(</span><span class="n">test_func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">hit_rate</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;maxsize=</span><span class="si">{</span><span class="nb">str</span><span class="p">(</span><span class="n">maxsize</span><span class="p">)</span><span class="si">:</span><span class="s2">&gt;4</span><span class="si">}</span><span class="s2">: &#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;hit_rate=</span><span class="si">{</span><span class="n">hit_rate</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%, &#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">              <span class="sa">f</span><span class="s2">&#34;size=</span><span class="si">{</span><span class="n">info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># 輸出範例:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"># maxsize=   1: hit_rate=65.2%, size=1</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"># maxsize=   4: hit_rate=92.8%, size=4</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"># maxsize=  16: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"># maxsize=  64: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1"># maxsize= 128: hit_rate=99.9%, size=11</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="c1"># maxsize=None: hit_rate=99.9%, size=11</span></span></span></code></pre></div><p><strong>結論</strong>：對於分支檢查，<code>maxsize=32</code> 就足夠。用 <code>maxsize=128</code> 是安全的預設值。</p>
<h3 id="快取命中率分析">快取命中率分析</h3>
<p><code>cache_info()</code> 提供詳細的快取統計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">analyze_cache_performance</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;分析快取效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 模擬一個 Hook 驗證流程</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 模擬驗證 50 個 Hook，每個 Hook 檢查 2 次</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="s2">&#34;feature/new-feature&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">50</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># 每個 Hook 的驗證邏輯</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 某些 Hook 會再次檢查</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 分析結果</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">protected_info</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">allowed_info</span> <span class="o">=</span> <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 快取效能分析 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">is_protected_branch:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  未命中: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">protected_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">protected_info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">protected_info</span><span class="o">.</span><span class="n">maxsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">is_allowed_branch:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  未命中: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">misses</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">allowed_info</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">currsize</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">allowed_info</span><span class="o">.</span><span class="n">maxsize</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="c1"># 輸出:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="c1"># === 快取效能分析 ===</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="c1"># is_protected_branch:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="c1">#   命中: 99</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="c1">#   未命中: 1</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1">#   命中率: 99.0%</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1">#   快取大小: 1/128</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="c1"># is_allowed_branch:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1">#   命中: 99</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1">#   未命中: 1</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1">#   命中率: 99.0%</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="c1">#   快取大小: 1/128</span></span></span></code></pre></div><p><strong>命中率解讀</strong>：</p>
<table>
  <thead>
      <tr>
          <th>命中率</th>
          <th>意義</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>&gt; 90%</td>
          <td>快取非常有效</td>
          <td>保持現狀</td>
      </tr>
      <tr>
          <td>70-90%</td>
          <td>快取有幫助</td>
          <td>可考慮增加 maxsize</td>
      </tr>
      <tr>
          <td>50-70%</td>
          <td>效果有限</td>
          <td>檢查呼叫模式</td>
      </tr>
      <tr>
          <td>&lt; 50%</td>
          <td>快取可能沒意義</td>
          <td>考慮移除快取</td>
      </tr>
  </tbody>
</table>
<h2 id="完整程式碼">完整程式碼</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">  1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  3</span><span class="cl"><span class="s2">分支檢查工具 - 帶 LRU 快取
</span></span></span><span class="line"><span class="ln">  4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">展示如何用 functools.lru_cache 優化重複的分支檢查。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">fnmatch</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># ===== 分支配置常數 =====</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="n">PROTECTED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="s2">&#34;master&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="s2">&#34;release/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;production&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">
</span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="n">ALLOWED_BRANCHES</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;feat/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="s2">&#34;feature/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;fix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="s2">&#34;hotfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="s2">&#34;bugfix/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="s2">&#34;chore/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="s2">&#34;docs/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="s2">&#34;refactor/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="s2">&#34;test/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">
</span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="c1"># ===== 帶快取的檢查函式 =====</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    檢查是否為保護分支（帶快取）
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">        bool: 如果是保護分支返回 True
</span></span></span><span class="line"><span class="ln"> 46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_protected_branch(&#34;main&#34;)
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">        True
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_protected_branch(&#34;feature/new&#34;)
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">        False
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl"><span class="k">def</span> <span class="nf">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">    檢查是否為允許編輯的分支（帶快取）
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">        branch: 分支名稱
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">        bool: 如果是允許編輯的分支返回 True
</span></span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_allowed_branch(&#34;feat/new-feature&#34;)
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s2">        True
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s2">        &gt;&gt;&gt; is_allowed_branch(&#34;random-branch&#34;)
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="s2">        False
</span></span></span><span class="line"><span class="ln"> 74</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">ALLOWED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="c1"># ===== 快取管理工具 =====</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">
</span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="k">def</span> <span class="nf">clear_branch_caches</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="s2">    清除所有分支檢查快取
</span></span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">    當 PROTECTED_BRANCHES 或 ALLOWED_BRANCHES 在執行時
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">    被修改時，需要呼叫此函式。
</span></span></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">        &gt;&gt;&gt; PROTECTED_BRANCHES.append(&#34;staging&#34;)
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="s2">        &gt;&gt;&gt; clear_branch_caches()  # 清除舊的快取結果
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">
</span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="k">def</span> <span class="nf">get_cache_stats</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">    獲取快取統計資訊
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        dict: 包含兩個函式的快取統計
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">        &gt;&gt;&gt; stats = get_cache_stats()
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        &gt;&gt;&gt; print(f&#34;protected hit rate: </span><span class="si">{stats[&#39;protected&#39;][&#39;hit_rate&#39;]:.1f}</span><span class="s2">%&#34;)
</span></span></span><span class="line"><span class="ln">106</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="n">protected</span> <span class="o">=</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">allowed</span> <span class="o">=</span> <span class="n">is_allowed_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">def</span> <span class="nf">calc_hit_rate</span><span class="p">(</span><span class="n">info</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">info</span><span class="o">.</span><span class="n">misses</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="k">return</span> <span class="n">info</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="n">total</span> <span class="o">*</span> <span class="mi">100</span> <span class="k">if</span> <span class="n">total</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="k">else</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="s2">&#34;protected&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="n">calc_hit_rate</span><span class="p">(</span><span class="n">protected</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">currsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="s2">&#34;maxsize&#34;</span><span class="p">:</span> <span class="n">protected</span><span class="o">.</span><span class="n">maxsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="s2">&#34;allowed&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="s2">&#34;hits&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">hits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="s2">&#34;misses&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">misses</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="s2">&#34;hit_rate&#34;</span><span class="p">:</span> <span class="n">calc_hit_rate</span><span class="p">(</span><span class="n">allowed</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">            <span class="s2">&#34;size&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">currsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="s2">&#34;maxsize&#34;</span><span class="p">:</span> <span class="n">allowed</span><span class="o">.</span><span class="n">maxsize</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">
</span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="k">def</span> <span class="nf">print_cache_stats</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="s2">&#34;&#34;&#34;印出快取統計的格式化報告&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">    <span class="n">stats</span> <span class="o">=</span> <span class="n">get_cache_stats</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== 分支檢查快取統計 ===&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">
</span></span><span class="line"><span class="ln">137</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">stats</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;hits&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">, 未命中: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;misses&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  命中率: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;hit_rate&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取大小: </span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;size&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">info</span><span class="p">[</span><span class="s1">&#39;maxsize&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">
</span></span><span class="line"><span class="ln">143</span><span class="cl"><span class="c1"># ===== 效能比較工具 =====</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">
</span></span><span class="line"><span class="ln">145</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_with_without_cache</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">branches</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="n">iterations</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">1000</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">150</span><span class="cl"><span class="s2">    比較有無快取的效能差異
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="s2">        branches: 要測試的分支列表
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="s2">        iterations: 每個分支的呼叫次數
</span></span></span><span class="line"><span class="ln">155</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">156</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">157</span><span class="cl"><span class="s2">        dict: 效能比較結果
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="c1"># 無快取版本</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="k">def</span> <span class="nf">is_protected_no_cache</span><span class="p">(</span><span class="n">branch</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">PROTECTED_BRANCHES</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="k">if</span> <span class="n">fnmatch</span><span class="o">.</span><span class="n">fnmatch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="n">pattern</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">
</span></span><span class="line"><span class="ln">168</span><span class="cl">    <span class="c1"># 測試無快取版本</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="n">branches</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="n">is_protected_no_cache</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">    <span class="n">no_cache_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">
</span></span><span class="line"><span class="ln">175</span><span class="cl">    <span class="c1"># 清除快取，測試有快取版本</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">    <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_clear</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">    <span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="n">branches</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">iterations</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">            <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">    <span class="n">with_cache_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="s2">&#34;no_cache_time&#34;</span><span class="p">:</span> <span class="n">no_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="s2">&#34;with_cache_time&#34;</span><span class="p">:</span> <span class="n">with_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="s2">&#34;speedup&#34;</span><span class="p">:</span> <span class="n">no_cache_time</span> <span class="o">/</span> <span class="n">with_cache_time</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="s2">&#34;cache_info&#34;</span><span class="p">:</span> <span class="n">is_protected_branch</span><span class="o">.</span><span class="n">cache_info</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl"><span class="c1"># ===== 示範 =====</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">
</span></span><span class="line"><span class="ln">193</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== LRU 快取示範 ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">
</span></span><span class="line"><span class="ln">198</span><span class="cl">    <span class="c1"># 測試分支</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="n">test_branches</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">        <span class="s2">&#34;main&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="s2">&#34;feature/auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">        <span class="s2">&#34;fix/bug-123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">        <span class="s2">&#34;develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="s2">&#34;chore/cleanup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="s2">&#34;release/v1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">
</span></span><span class="line"><span class="ln">208</span><span class="cl">    <span class="c1"># 模擬重複呼叫</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. 模擬 Hook 驗證流程（100 次迭代）:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">    <span class="n">clear_branch_caches</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">211</span><span class="cl">
</span></span><span class="line"><span class="ln">212</span><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">        <span class="n">branch</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">test_branches</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">is_protected_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="n">is_allowed_branch</span><span class="p">(</span><span class="n">branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl">    <span class="n">print_cache_stats</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="c1"># 效能比較</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">2. 效能比較:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">benchmark_with_without_cache</span><span class="p">(</span><span class="n">test_branches</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">10000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">
</span></span><span class="line"><span class="ln">223</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  無快取: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;no_cache_time&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  有快取: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;with_cache_time&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;speedup&#39;</span><span class="p">]</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  快取命中率: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">hits</span> <span class="o">/</span> <span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">hits</span> <span class="o">+</span> <span class="n">result</span><span class="p">[</span><span class="s1">&#39;cache_info&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">misses</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>無快取</th>
          <th>lru_cache</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>記憶體使用</td>
          <td>無額外開銷</td>
          <td>每個不同輸入一個快取項目</td>
      </tr>
      <tr>
          <td>首次呼叫</td>
          <td>直接計算</td>
          <td>計算 + 快取開銷</td>
      </tr>
      <tr>
          <td>重複呼叫</td>
          <td>每次都計算</td>
          <td>O(1) 查表</td>
      </tr>
      <tr>
          <td>程式碼複雜度</td>
          <td>最簡單</td>
          <td>加一行裝飾器</td>
      </tr>
      <tr>
          <td>正確性風險</td>
          <td>無</td>
          <td>配置變更時需清除快取</td>
      </tr>
      <tr>
          <td>可測試性</td>
          <td>直接測試</td>
          <td>需考慮快取狀態</td>
      </tr>
      <tr>
          <td>執行緒安全</td>
          <td>是</td>
          <td>是（lru_cache 內建鎖）</td>
      </tr>
  </tbody>
</table>
<h3 id="何時不該用快取">何時不該用快取</h3>
<h4 id="情況-1函數不是純函數">情況 1：函數不是純函數</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 錯誤示範：結果依賴外部狀態</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">get_env_value</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 問題：環境變數改變後，快取還是返回舊值</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;old&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">get_env_value</span><span class="p">(</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">))</span>  <span class="c1"># &#34;old&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;new&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">get_env_value</span><span class="p">(</span><span class="s2">&#34;MY_VAR&#34;</span><span class="p">))</span>  <span class="c1"># 仍然是 &#34;old&#34;！</span></span></span></code></pre></div><h4 id="情況-2參數不可雜湊">情況 2：參數不可雜湊</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤示範：list 不是 hashable</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">process_items</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>  <span class="c1"># TypeError!</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 解法：用 tuple 代替 list</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">def</span> <span class="nf">process_items</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">items</span><span class="p">)</span></span></span></code></pre></div><h4 id="情況-3計算太簡單">情況 3：計算太簡單</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：快取開銷可能大於計算成本</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">is_empty</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># len() 和 == 操作非常快</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 快取的雜湊計算和查表可能更慢</span></span></span></code></pre></div><h4 id="情況-4輸入值極度多樣">情況 4：輸入值極度多樣</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：每次輸入都不同，快取永遠不會命中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">process_unique_id</span><span class="p">(</span><span class="n">unique_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 如果 unique_id 每次都不同...</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">unique_id</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 快取只會一直 miss，浪費記憶體</span></span></span></code></pre></div><h4 id="情況-5需要即時反映外部變化">情況 5：需要即時反映外部變化</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不建議：配置可能動態變化</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nd">@lru_cache</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">128</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">def</span> <span class="nf">is_feature_enabled</span><span class="p">(</span><span class="n">feature</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 從資料庫讀取 feature flag</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">return</span> <span class="n">db</span><span class="o">.</span><span class="n">get_feature_flag</span><span class="p">(</span><span class="n">feature</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 問題：資料庫更新後，快取還是返回舊值</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 需要額外的快取失效機制</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用 lru_cache</strong>：</p>
<ul>
<li>純函數（相同輸入，相同輸出）</li>
<li>會被重複呼叫，且輸入值有限</li>
<li>計算成本明顯高於雜湊和查表</li>
<li>配置是靜態的，不會在執行時改變</li>
</ul>
<p><strong>不適合使用</strong>：</p>
<ul>
<li>函數有副作用或依賴外部狀態</li>
<li>參數不可雜湊（list、dict、set）</li>
<li>每次輸入都不同（如 UUID、時間戳）</li>
<li>計算非常簡單（如 <code>len()</code>、<code>+</code>）</li>
</ul>
<p><strong>快速決策流程</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">函數是純函數嗎？
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── 否 → 不要用 lru_cache
</span></span><span class="line"><span class="ln">3</span><span class="cl">└── 是 → 參數都是 hashable 嗎？
</span></span><span class="line"><span class="ln">4</span><span class="cl">         ├── 否 → 轉換成 hashable（如 tuple）或不用
</span></span><span class="line"><span class="ln">5</span><span class="cl">         └── 是 → 會被重複呼叫嗎？
</span></span><span class="line"><span class="ln">6</span><span class="cl">                  ├── 否 → 不需要快取
</span></span><span class="line"><span class="ln">7</span><span class="cl">                  └── 是 → 適合用 lru_cache</span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>
<p><strong>測量快取效益</strong>：在你的專案中找一個被重複呼叫的純函數，加入 <code>lru_cache</code>，比較效能差異。</p>
</li>
<li>
<p><strong>監控快取狀態</strong>：寫一個裝飾器，在每次呼叫後印出 <code>cache_info()</code>，觀察快取行為。</p>
</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li><strong>帶 TTL 的快取</strong>：實作一個有過期時間的快取裝飾器。提示：可以結合 <code>time.time()</code> 和 <code>lru_cache</code>。</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">lru_cache</span><span class="p">,</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">timed_lru_cache</span><span class="p">(</span><span class="n">seconds</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">maxsize</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">128</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    帶過期時間的 LRU 快取
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        seconds: 快取過期秒數
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        maxsize: 快取大小
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    用法：
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        @timed_lru_cache(seconds=60)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        def fetch_data(url):
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            ...
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 你的實作</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</span></span></span></code></pre></div><ol start="4">
<li><strong>快取命中率監控</strong>：建立一個監控系統，追蹤多個快取函式的命中率，當命中率低於閾值時發出警告。</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li><strong>動態配置的快取失效</strong>：修改 <code>is_protected_branch</code>，支援在執行時新增保護分支，並自動失效相關快取（而不是清除所有快取）。</li>
</ol>
<p>提示：考慮用 <code>typed=True</code> 選項，或自訂快取類別。</p>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></em>
<em>下一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇</a></em></p>
]]></content:encoded></item><item><title>案例：資料結構選擇</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何選擇正確的資料結構來優化成員查詢效能。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化&lt;/a>&lt;/li>
&lt;li>基本的時間複雜度概念（O(n)、O(1)）&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="成員查詢的效能差異">成員查詢的效能差異&lt;/h3>
&lt;p>在 Python 中，檢查某個元素是否存在於容器中（成員查詢）是最常見的操作之一：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">container&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 做某些事&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但這行簡單的程式碼，背後的效能差異可能高達 &lt;strong>100 倍以上&lt;/strong>，取決於 &lt;code>container&lt;/code> 是什麼資料結構。&lt;/p>
&lt;h4 id="list-的成員查詢on">list 的成員查詢：O(n)&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">my_list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">my_list&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># 最壞情況要檢查 n 個元素&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>list 是有序的線性結構，Python 必須從頭開始逐一比對，直到找到目標或走完整個 list。&lt;/p>
&lt;h4 id="set-的成員查詢o1">set 的成員查詢：O(1)&lt;/h4>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="n">my_set&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">my_set&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># 平均只需要 1 次雜湊計算&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>set 使用雜湊表（hash table）實作，透過計算元素的雜湊值直接定位，不受容器大小影響。&lt;/p>
&lt;h3 id="真實案例測試檔案存在性檢查">真實案例：測試檔案存在性檢查&lt;/h3>
&lt;p>&lt;code>hook_validator.py&lt;/code> 的 &lt;code>check_test_exists&lt;/code> 方法會檢查每個 Hook 是否有對應的測試檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查對應的測試檔案是否存在
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2"> hook_path: Hook 檔案路徑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> list[ValidationIssue]: 發現的問題
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 生成測試檔案名稱&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">test_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;test_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;-&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試檔案應該在 .claude/lib/tests/ 或 .claude/hooks/tests/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">possible_test_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">test_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">test_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">test_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">any&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">possible_test_paths&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">test_exists&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;info&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;未找到對應的測試檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;建議在以下位置建立測試:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; .claude/lib/tests/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">issues&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個程式碼有一個隱藏的效能問題：當需要驗證大量 Hook 時，每次都要呼叫 &lt;code>p.exists()&lt;/code> 進行檔案系統操作。&lt;/p>
&lt;p>假設我們要驗證 100 個 Hook，而測試目錄下有 200 個測試檔案：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hooks_dir&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationResult&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證所有 Hook 檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 省略部分程式碼 ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hook_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">sorted&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hooks_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;*.py&amp;#34;&lt;/span>&lt;span class="p">)):&lt;/span> &lt;span class="c1"># 100 個 Hook&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">hook_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;_&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate_hook&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hook_file&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 每個 validate_hook 會呼叫 check_test_exists&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="c1"># check_test_exists 會呼叫 2 次 Path.exists()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 總共：100 * 2 = 200 次檔案系統操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">results&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="問題分析">問題分析&lt;/h3>
&lt;p>原始程式碼的瓶頸：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>重複的檔案系統操作&lt;/strong>：每個 Hook 都要呼叫 &lt;code>exists()&lt;/code> 檢查測試是否存在&lt;/li>
&lt;li>&lt;strong>沒有快取&lt;/strong>：相同的檢查可能被重複執行&lt;/li>
&lt;li>&lt;strong>線性搜尋&lt;/strong>：如果測試清單很長，用 list 儲存會導致 O(n) 的查詢時間&lt;/li>
&lt;/ol>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="用-set-取代-list">用 set 取代 list&lt;/h3>
&lt;p>核心思路：&lt;strong>先掃描一次測試目錄，建立測試檔案的 set，然後用 O(1) 查詢取代檔案系統操作&lt;/strong>。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Hook 合規性驗證器（優化版）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">project_root&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 優化：預先建立測試檔案的 set&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">test_files&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> 取得所有測試檔案名稱（快取）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2"> set[str]: 測試檔案名稱集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_scan_test_files&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_test_files_cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_scan_test_files&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> 掃描所有測試目錄，建立測試檔案集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> set[str]: 測試檔案名稱集合
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">test_dirs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;.claude&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;hooks&amp;#34;&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;tests&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl"> &lt;span class="n">test_files&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">set&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 使用 set 而非 list&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">test_dir&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">test_dirs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">test_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_dir&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">test_file&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">test_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test_*.py&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="n">test_files&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">test_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">test_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">check_test_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> 檢查對應的測試檔案是否存在（優化版）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">&lt;span class="s2"> 使用 set 進行 O(1) 查詢，取代檔案系統操作。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">53&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">55&lt;/span>&lt;span class="cl"> &lt;span class="n">hook_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stem&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl"> &lt;span class="n">test_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;test_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hook_name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;-&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 優化：O(1) 的 set 查詢，取代 O(n) 的 exists() 呼叫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="n">test_exists&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">test_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">test_files&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">test_exists&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="n">issues&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl"> &lt;span class="n">ValidationIssue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="n">level&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;info&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;未找到對應的測試檔案: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;建議在以下位置建立測試:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; .claude/lib/tests/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">test_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">issues&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1識別重複查詢">步驟 1：識別重複查詢&lt;/h4>
&lt;p>找出程式碼中哪些查詢會被重複執行：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何選擇正確的資料結構來優化成員查詢效能。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">入門系列 3.8 效能優化</a></li>
<li>基本的時間複雜度概念（O(n)、O(1)）</li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="成員查詢的效能差異">成員查詢的效能差異</h3>
<p>在 Python 中，檢查某個元素是否存在於容器中（成員查詢）是最常見的操作之一：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">if</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">container</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 做某些事</span></span></span></code></pre></div><p>但這行簡單的程式碼，背後的效能差異可能高達 <strong>100 倍以上</strong>，取決於 <code>container</code> 是什麼資料結構。</p>
<h4 id="list-的成員查詢on">list 的成員查詢：O(n)</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">my_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">n</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">my_list</span><span class="p">:</span>  <span class="c1"># 最壞情況要檢查 n 個元素</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><p>list 是有序的線性結構，Python 必須從頭開始逐一比對，直到找到目標或走完整個 list。</p>
<h4 id="set-的成員查詢o1">set 的成員查詢：O(1)</h4>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">my_set</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">...</span><span class="p">,</span> <span class="n">n</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">if</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">my_set</span><span class="p">:</span>  <span class="c1"># 平均只需要 1 次雜湊計算</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="o">...</span></span></span></code></pre></div><p>set 使用雜湊表（hash table）實作，透過計算元素的雜湊值直接定位，不受容器大小影響。</p>
<h3 id="真實案例測試檔案存在性檢查">真實案例：測試檔案存在性檢查</h3>
<p><code>hook_validator.py</code> 的 <code>check_test_exists</code> 方法會檢查每個 Hook 是否有對應的測試檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    檢查對應的測試檔案是否存在
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        hook_path: Hook 檔案路徑
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        list[ValidationIssue]: 發現的問題
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 生成測試檔案名稱</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">hook_name</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># 測試檔案應該在 .claude/lib/tests/ 或 .claude/hooks/tests/</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">possible_test_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span> <span class="o">/</span> <span class="n">test_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">test_exists</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_test_paths</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">test_exists</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;未找到對應的測試檔案: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;建議在以下位置建立測試:</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;  .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><p>這個程式碼有一個隱藏的效能問題：當需要驗證大量 Hook 時，每次都要呼叫 <code>p.exists()</code> 進行檔案系統操作。</p>
<p>假設我們要驗證 100 個 Hook，而測試目錄下有 200 個測試檔案：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">validate_all_hooks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hooks_dir</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證所有 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># ... 省略部分程式碼 ...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">hooks_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;*.py&#34;</span><span class="p">)):</span>  <span class="c1"># 100 個 Hook</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">if</span> <span class="n">hook_file</span><span class="o">.</span><span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&#34;_&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">validate_hook</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">hook_file</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="c1"># 每個 validate_hook 會呼叫 check_test_exists</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># check_test_exists 會呼叫 2 次 Path.exists()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 總共：100 * 2 = 200 次檔案系統操作</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">results</span></span></span></code></pre></div><h3 id="問題分析">問題分析</h3>
<p>原始程式碼的瓶頸：</p>
<ol>
<li><strong>重複的檔案系統操作</strong>：每個 Hook 都要呼叫 <code>exists()</code> 檢查測試是否存在</li>
<li><strong>沒有快取</strong>：相同的檢查可能被重複執行</li>
<li><strong>線性搜尋</strong>：如果測試清單很長，用 list 儲存會導致 O(n) 的查詢時間</li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="用-set-取代-list">用 set 取代 list</h3>
<p>核心思路：<strong>先掃描一次測試目錄，建立測試檔案的 set，然後用 O(1) 查詢取代檔案系統操作</strong>。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 合規性驗證器（優化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">project_root</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">if</span> <span class="n">project_root</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="n">project_root</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                <span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># 優化：預先建立測試檔案的 set</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        取得所有測試檔案名稱（快取）
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">            set[str]: 測試檔案名稱集合
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_scan_test_files</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">_scan_test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        掃描所有測試目錄，建立測試檔案集合
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">            set[str]: 測試檔案名稱集合
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">test_dirs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span> <span class="o">/</span> <span class="s2">&#34;tests&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># 使用 set 而非 list</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">for</span> <span class="n">test_dir</span> <span class="ow">in</span> <span class="n">test_dirs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="k">if</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                    <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="k">return</span> <span class="n">test_files</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">def</span> <span class="nf">check_test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationIssue</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">        檢查對應的測試檔案是否存在（優化版）
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">        使用 set 進行 O(1) 查詢，取代檔案系統操作。
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">,</span> <span class="s1">&#39;_&#39;</span><span class="p">)</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="c1"># 優化：O(1) 的 set 查詢，取代 O(n) 的 exists() 呼叫</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="n">test_exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">test_files</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">test_exists</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                    <span class="n">level</span><span class="o">=</span><span class="s2">&#34;info&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;未找到對應的測試檔案: </span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                        <span class="sa">f</span><span class="s2">&#34;建議在以下位置建立測試:</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                        <span class="sa">f</span><span class="s2">&#34;  .claude/lib/tests/</span><span class="si">{</span><span class="n">test_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">
</span></span><span class="line"><span class="ln">73</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1識別重複查詢">步驟 1：識別重複查詢</h4>
<p>找出程式碼中哪些查詢會被重複執行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 原始程式碼：每次驗證都會執行檔案系統操作</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">hooks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="c1"># possible_test_paths 中的 Path.exists() 被呼叫 2 次</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">test_exists</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">possible_test_paths</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2收集所有可能的查詢目標">步驟 2：收集所有可能的查詢目標</h4>
<p>在初始化時掃描一次，建立完整的資料集：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">_scan_test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;一次性掃描所有測試檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">for</span> <span class="n">test_dir</span> <span class="ow">in</span> <span class="n">test_directories</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">test_files</span></span></span></code></pre></div><h4 id="步驟-3用-set-取代-list">步驟 3：用 set 取代 list</h4>
<p>確保查詢容器是 set 而非 list：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：用 list 儲存</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">test_files</span> <span class="o">=</span> <span class="p">[]</span>  <span class="c1"># O(n) 查詢</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">test_files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 正確：用 set 儲存</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">test_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># O(1) 查詢</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">for</span> <span class="n">test_file</span> <span class="ow">in</span> <span class="n">test_dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;test_*.py&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">test_files</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">test_file</span><span class="o">.</span><span class="n">name</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4加入快取機制">步驟 4：加入快取機制</h4>
<p>避免重複掃描：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">test_files</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;延遲初始化 + 快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_scan_test_files</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">clear_cache</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;需要時可以清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_test_files_cache</span> <span class="o">=</span> <span class="kc">None</span></span></span></code></pre></div><h2 id="效能測量">效能測量</h2>
<p>讓我們用 <code>timeit</code> 測量 list 和 set 的查詢效能差異：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">string</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_membership_test</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較 list 和 set 的成員查詢效能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 準備測試資料</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">generate_filename</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="s1">&#39;&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">random</span><span class="o">.</span><span class="n">choices</span><span class="p">(</span><span class="n">string</span><span class="o">.</span><span class="n">ascii_lowercase</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">100</span><span class="p">,</span> <span class="mi">1000</span><span class="p">,</span> <span class="mi">10000</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">for</span> <span class="n">size</span> <span class="ow">in</span> <span class="n">sizes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 建立測試資料</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">items</span> <span class="o">=</span> <span class="p">[</span><span class="n">generate_filename</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">size</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">test_list</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">test_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># 準備查詢目標（一半存在、一半不存在）</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">existing_items</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">sample</span><span class="p">(</span><span class="n">items</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="n">size</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">non_existing_items</span> <span class="o">=</span> <span class="p">[</span><span class="n">generate_filename</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">query_items</span> <span class="o">=</span> <span class="n">existing_items</span> <span class="o">+</span> <span class="n">non_existing_items</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">random</span><span class="o">.</span><span class="n">shuffle</span><span class="p">(</span><span class="n">query_items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1"># 測量 list 查詢</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">def</span> <span class="nf">list_lookup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">query_items</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="n">_</span> <span class="o">=</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">test_list</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">list_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">list_lookup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># 測量 set 查詢</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">def</span> <span class="nf">set_lookup</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">query_items</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="n">_</span> <span class="o">=</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">test_set</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">set_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">set_lookup</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="c1"># 計算加速比</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">speedup</span> <span class="o">=</span> <span class="n">list_time</span> <span class="o">/</span> <span class="n">set_time</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">元素數量: </span><span class="si">{</span><span class="n">size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list 查詢: </span><span class="si">{</span><span class="n">list_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set 查詢:  </span><span class="si">{</span><span class="n">set_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比:    </span><span class="si">{</span><span class="n">speedup</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">benchmark_membership_test</span><span class="p">()</span></span></span></code></pre></div><p><strong>典型執行結果</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln"> 1</span><span class="cl">元素數量: 100
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">  list 查詢: 0.0234 秒
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  set 查詢:  0.0012 秒
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  加速比:    19.5x
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">元素數量: 1,000
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  list 查詢: 0.2156 秒
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  set 查詢:  0.0013 秒
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  加速比:    165.8x
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">元素數量: 10,000
</span></span><span class="line"><span class="ln">12</span><span class="cl">  list 查詢: 2.1847 秒
</span></span><span class="line"><span class="ln">13</span><span class="cl">  set 查詢:  0.0014 秒
</span></span><span class="line"><span class="ln">14</span><span class="cl">  加速比:    1560.5x</span></span></code></pre></div><p><strong>觀察重點</strong>：</p>
<ol>
<li><strong>set 的查詢時間幾乎不變</strong>：無論元素數量是 100 還是 10,000，set 的查詢時間都在 0.001 秒左右</li>
<li><strong>list 的查詢時間線性增長</strong>：元素增加 10 倍，查詢時間也增加約 10 倍</li>
<li><strong>加速比隨資料量增加</strong>：元素越多，set 的優勢越明顯</li>
</ol>
<h3 id="實際場景測試">實際場景測試</h3>
<p>模擬 Hook 驗證器的真實使用場景：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">timeit</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Set</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">benchmark_hook_validation</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬 Hook 驗證器的效能差異&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 模擬 200 個測試檔案</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">test_files_list</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;test_hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">.py&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">200</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">test_files_set</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">test_files_list</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 模擬 100 個 Hook 要檢查</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">hooks_to_check</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;hook_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">check_with_list</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;使用 list 進行查詢&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">for</span> <span class="n">hook_name</span> <span class="ow">in</span> <span class="n">hooks_to_check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">test_files_list</span>  <span class="c1"># O(n)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">exists</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">check_with_set</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;使用 set 進行查詢&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">for</span> <span class="n">hook_name</span> <span class="ow">in</span> <span class="n">hooks_to_check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="n">test_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;test_</span><span class="si">{</span><span class="n">hook_name</span><span class="si">}</span><span class="s2">.py&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">exists</span> <span class="o">=</span> <span class="n">test_name</span> <span class="ow">in</span> <span class="n">test_files_set</span>  <span class="c1"># O(1)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">exists</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="c1"># 測量效能</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">list_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">check_with_list</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">set_time</span> <span class="o">=</span> <span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="n">check_with_set</span><span class="p">,</span> <span class="n">number</span><span class="o">=</span><span class="mi">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;驗證 100 個 Hook（執行 1000 次）：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list 版本: </span><span class="si">{</span><span class="n">list_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set 版本:  </span><span class="si">{</span><span class="n">set_time</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2"> 秒&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  加速比:    </span><span class="si">{</span><span class="n">list_time</span> <span class="o">/</span> <span class="n">set_time</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">benchmark_hook_validation</span><span class="p">()</span></span></span></code></pre></div><p><strong>典型執行結果</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">驗證 100 個 Hook（執行 1000 次）：
</span></span><span class="line"><span class="ln">2</span><span class="cl">  list 版本: 0.3892 秒
</span></span><span class="line"><span class="ln">3</span><span class="cl">  set 版本:  0.0087 秒
</span></span><span class="line"><span class="ln">4</span><span class="cl">  加速比:    44.7x</span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>資料結構</th>
          <th>查詢</th>
          <th>插入</th>
          <th>刪除</th>
          <th>記憶體</th>
          <th>有序</th>
          <th>重複元素</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>list</td>
          <td>O(n)</td>
          <td>O(1)*</td>
          <td>O(n)</td>
          <td>低</td>
          <td>是</td>
          <td>允許</td>
      </tr>
      <tr>
          <td>set</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>高</td>
          <td>否</td>
          <td>不允許</td>
      </tr>
      <tr>
          <td>dict</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>O(1)</td>
          <td>高</td>
          <td>是**</td>
          <td>鍵不允許</td>
      </tr>
  </tbody>
</table>
<p>*list 的 append 是 O(1)，insert 是 O(n)</p>
<p>**Python 3.7+ 的 dict 保持插入順序</p>
<h3 id="記憶體使用比較">記憶體使用比較</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">compare_memory</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;比較 list 和 set 的記憶體使用&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">items</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">list_version</span> <span class="o">=</span> <span class="n">items</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">set_version</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">list_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">list_version</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">set_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="n">set_version</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;10,000 個整數：&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  list: </span><span class="si">{</span><span class="n">list_size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set:  </span><span class="si">{</span><span class="n">set_size</span><span class="si">:</span><span class="s2">,</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  set/list 比率: </span><span class="si">{</span><span class="n">set_size</span> <span class="o">/</span> <span class="n">list_size</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">x&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 輸出：</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 10,000 個整數：</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1">#   list: 87,624 bytes</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">#   set:  524,512 bytes</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">#   set/list 比率: 5.99x</span></span></span></code></pre></div><p><strong>權衡分析</strong>：</p>
<ul>
<li>set 的記憶體使用約為 list 的 4-8 倍</li>
<li>但查詢速度可以快 10-100 倍以上</li>
<li>對於大多數場景，記憶體增加可以接受</li>
</ul>
<h3 id="何時該用-list">何時該用 list？</h3>
<ol>
<li><strong>需要保持順序</strong>：元素的順序很重要</li>
<li><strong>需要重複元素</strong>：同一個值可能出現多次</li>
<li><strong>需要索引存取</strong>：經常用 <code>items[i]</code> 存取</li>
<li><strong>主要操作是遍歷</strong>：很少做成員查詢</li>
<li><strong>資料量很小</strong>：少於 10 個元素時差異不大</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 適合用 list 的場景</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">commands</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;init&#34;</span><span class="p">,</span> <span class="s2">&#34;build&#34;</span><span class="p">,</span> <span class="s2">&#34;test&#34;</span><span class="p">,</span> <span class="s2">&#34;deploy&#34;</span><span class="p">]</span>  <span class="c1"># 需要順序</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">scores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">95</span><span class="p">,</span> <span class="mi">87</span><span class="p">,</span> <span class="mi">95</span><span class="p">,</span> <span class="mi">92</span><span class="p">,</span> <span class="mi">87</span><span class="p">]</span>  <span class="c1"># 需要重複</span></span></span></code></pre></div><h3 id="何時該用-set">何時該用 set？</h3>
<ol>
<li><strong>主要操作是成員查詢</strong>：頻繁使用 <code>in</code> 運算子</li>
<li><strong>需要去重</strong>：自動排除重複元素</li>
<li><strong>需要集合運算</strong>：交集、聯集、差集</li>
<li><strong>資料量較大</strong>：幾百個以上的元素</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 適合用 set 的場景</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">valid_extensions</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;.py&#34;</span><span class="p">,</span> <span class="s2">&#34;.js&#34;</span><span class="p">,</span> <span class="s2">&#34;.ts&#34;</span><span class="p">}</span>  <span class="c1"># 成員查詢</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">seen_files</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>  <span class="c1"># 追蹤已處理的檔案（去重）</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">common_tags</span> <span class="o">=</span> <span class="n">tags_a</span> <span class="o">&amp;</span> <span class="n">tags_b</span>  <span class="c1"># 集合運算</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用的情況">適合使用的情況</h3>
<ol>
<li><strong>頻繁的成員查詢</strong>：程式碼中有很多 <code>if x in container</code> 的檢查</li>
<li><strong>查詢容器較大</strong>：容器有幾百個以上的元素</li>
<li><strong>查詢頻率高</strong>：同一個容器被查詢多次</li>
<li><strong>不需要元素順序</strong>：只關心「有沒有」而非「在哪裡」</li>
</ol>
<h3 id="不建議使用的情況">不建議使用的情況</h3>
<ol>
<li><strong>需要保持插入順序</strong>：用 list 或 dict</li>
<li><strong>需要重複元素</strong>：用 list 或 collections.Counter</li>
<li><strong>容器很小</strong>：10 個以下元素時差異不明顯</li>
<li><strong>元素不可雜湊</strong>：list、dict 等 mutable 物件無法放入 set</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不可雜湊的物件無法用 set</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">items</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]]</span>  <span class="c1"># list of lists</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># set(items)  # TypeError: unhashable type: &#39;list&#39;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 解法：轉換為 tuple</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">items_set</span> <span class="o">=</span> <span class="p">{</span><span class="nb">tuple</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">}</span>  <span class="c1"># OK</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>實作去重函式</strong>：寫一個函式，用 set 去除 list 中的重複元素，同時保持原本的順序</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">deduplicate_ordered</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="nb">list</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    去除重複元素，保持順序
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        &gt;&gt;&gt; deduplicate_ordered([3, 1, 2, 1, 3, 2])
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        [3, 1, 2]
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><ol start="2">
<li><strong>優化單字計數器</strong>：以下程式碼檢查一段文字中有多少個「停用詞」，請用 set 優化它</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">STOP_WORDS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;the&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="s2">&#34;an&#34;</span><span class="p">,</span> <span class="s2">&#34;is&#34;</span><span class="p">,</span> <span class="s2">&#34;are&#34;</span><span class="p">,</span> <span class="s2">&#34;was&#34;</span><span class="p">,</span> <span class="s2">&#34;were&#34;</span><span class="p">,</span> <span class="s2">&#34;be&#34;</span><span class="p">,</span> <span class="s2">&#34;been&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">              <span class="s2">&#34;being&#34;</span><span class="p">,</span> <span class="s2">&#34;have&#34;</span><span class="p">,</span> <span class="s2">&#34;has&#34;</span><span class="p">,</span> <span class="s2">&#34;had&#34;</span><span class="p">,</span> <span class="s2">&#34;do&#34;</span><span class="p">,</span> <span class="s2">&#34;does&#34;</span><span class="p">,</span> <span class="s2">&#34;did&#34;</span><span class="p">,</span> <span class="s2">&#34;will&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">              <span class="s2">&#34;would&#34;</span><span class="p">,</span> <span class="s2">&#34;could&#34;</span><span class="p">,</span> <span class="s2">&#34;should&#34;</span><span class="p">,</span> <span class="s2">&#34;may&#34;</span><span class="p">,</span> <span class="s2">&#34;might&#34;</span><span class="p">,</span> <span class="s2">&#34;must&#34;</span><span class="p">,</span> <span class="s2">&#34;can&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">count_stop_words_slow</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;計算停用詞數量（待優化）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">words</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">words</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">if</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">STOP_WORDS</span><span class="p">:</span>  <span class="c1"># O(n) 查詢</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 請優化這個函式</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">count_stop_words_fast</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;&#34;&#34;計算停用詞數量（優化版）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<ol start="3">
<li><strong>實作檔案比對工具</strong>：比較兩個目錄中的檔案，找出只在其中一個目錄存在的檔案</li>
</ol>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">compare_directories</span><span class="p">(</span><span class="n">dir1</span><span class="p">:</span> <span class="n">Path</span><span class="p">,</span> <span class="n">dir2</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    比較兩個目錄的檔案
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        dict: {
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">            &#34;only_in_dir1&#34;: set[str],
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">            &#34;only_in_dir2&#34;: set[str],
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">            &#34;in_both&#34;: set[str],
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        }
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    提示：使用 set 的交集、差集運算
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># Your implementation here</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><ol start="4">
<li><strong>優化 Hook 驗證器</strong>：參考本文的優化方案，為 <code>HookValidator</code> 加入以下功能：</li>
</ol>
<ul>
<li>快取測試檔案列表</li>
<li>支援多個測試目錄</li>
<li>在測試目錄變更時自動更新快取</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></em>
<em>返回：<a href="/blog/python-advanced/08-practical-optimization/case-studies/" data-link-title="案例研究：效能優化實戰" data-link-desc="基於 .claude/lib 的效能優化實戰案例">案例研究索引</a></em></p>
]]></content:encoded></item><item><title>ECS Fargate 成本分析與優化</title><link>https://tarrragon.github.io/blog/infra/05-core-services/ecs-fargate-cost-optimization/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/ecs-fargate-cost-optimization/</guid><description>&lt;p>Fargate 把運算的維運面外包給 AWS — 不需要管 EC2 instance、不需要管 AMI 更新、不需要管 capacity provider 的擴縮邏輯。這份簡化的代價是單位成本較高。當服務規模小或流量不穩定時，Fargate 的簡化值回票價；當服務規模穩定且持續運行時，EC2 launch type 的單位成本優勢會累積到值得切換的量級。本篇的目標是讓讀者能判斷自己的服務在成本曲線的哪個位置、以及有哪些槓桿可以調。&lt;/p>
&lt;h2 id="fargate-計價模型">Fargate 計價模型&lt;/h2>
&lt;p>Fargate 按 task 的 vCPU 時數和記憶體時數分別計費，從 task 啟動（pull image 完成、進入 RUNNING）到停止。計費的最小粒度是一分鐘，不足一分鐘按一分鐘算。&lt;/p>
&lt;p>以 ap-northeast-1（東京）為例的單價（截至撰寫時的量級參考，實際以 AWS 定價頁為準）：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>資源&lt;/th>
 &lt;th>單價（每小時）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1 vCPU&lt;/td>
 &lt;td>~$0.05056&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>1 GB RAM&lt;/td>
 &lt;td>~$0.00553&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>一個 1 vCPU / 2 GB 的 task 持續運行一個月（730 小時）的費用約為 $0.05056 × 730 + $0.00553 × 2 × 730 ≈ $44.97。這個數字是所有後續比較的基線。&lt;/p>
&lt;p>Fargate 的計費粒度還有一個常被忽略的面向：task 規格只能從 AWS 預定義的 vCPU/memory 組合中選。如果應用只需要 0.3 vCPU / 512 MB，最小可選的配置是 0.25 vCPU / 0.5 GB，但如果需要 0.3 vCPU / 1 GB，就得選 0.5 vCPU / 1 GB — 多付了 0.2 vCPU 的費用。這個「階梯式浪費」在小規格 task 上比例最高。&lt;/p>
&lt;h2 id="fargate-vs-ec2-launch-type-的成本比較">Fargate vs EC2 launch type 的成本比較&lt;/h2>
&lt;p>EC2 launch type 的成本結構不同：付的是 EC2 instance 的時數（不管上面跑幾個 task），加上 ECS 本身不收費。省的是 Fargate 的 markup，多的是 instance 管理（AMI 更新、capacity provider 設定、instance 閒置時仍計費）。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>場景&lt;/th>
 &lt;th>Fargate 月費&lt;/th>
 &lt;th>EC2（t3.medium）月費&lt;/th>
 &lt;th>差異&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1 task, 1 vCPU / 2 GB, 持續&lt;/td>
 &lt;td>~$45&lt;/td>
 &lt;td>~$30（共享 instance）&lt;/td>
 &lt;td>+50%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5 tasks, 各 0.5 vCPU / 1 GB&lt;/td>
 &lt;td>~$113&lt;/td>
 &lt;td>~$30（1 台 t3.medium 裝得下）&lt;/td>
 &lt;td>+277%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>20 tasks, 各 1 vCPU / 2 GB&lt;/td>
 &lt;td>~$900&lt;/td>
 &lt;td>~$240（4 台 t3.xlarge）&lt;/td>
 &lt;td>+275%&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>流量波動大，尖峰 10 tasks / 離峰 1&lt;/td>
 &lt;td>~$180（加權平均）&lt;/td>
 &lt;td>~$150（需預留尖峰容量）&lt;/td>
 &lt;td>+20%&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>幾個判讀要點：&lt;/p>
&lt;ul>
&lt;li>task 數量少且持續運行時，Fargate 的溢價比例最高（+50% 到 +277%），但絕對金額小（$15-$80/月的差距），不值得為此承擔 instance 管理的維運負擔&lt;/li>
&lt;li>task 數量多且持續運行時，EC2 的絕對節省量開始可觀（$660/月），這時候切換的維運成本有回報&lt;/li>
&lt;li>流量波動大時，Fargate 的優勢是按需計費 — 離峰時 task 數降下來就停止計費，EC2 instance 閒置時仍然計費。波動越大，Fargate 的成本效益越接近或超過 EC2&lt;/li>
&lt;/ul>
&lt;h2 id="fargate-spot">Fargate Spot&lt;/h2>
&lt;p>Fargate Spot 使用 AWS 的閒置容量，價格約為 on-demand 的 30%（折扣幅度 ~70%），代價是 AWS 可以隨時回收容量、task 會收到 SIGTERM 後被終止。&lt;/p></description><content:encoded><![CDATA[<p>Fargate 把運算的維運面外包給 AWS — 不需要管 EC2 instance、不需要管 AMI 更新、不需要管 capacity provider 的擴縮邏輯。這份簡化的代價是單位成本較高。當服務規模小或流量不穩定時，Fargate 的簡化值回票價；當服務規模穩定且持續運行時，EC2 launch type 的單位成本優勢會累積到值得切換的量級。本篇的目標是讓讀者能判斷自己的服務在成本曲線的哪個位置、以及有哪些槓桿可以調。</p>
<h2 id="fargate-計價模型">Fargate 計價模型</h2>
<p>Fargate 按 task 的 vCPU 時數和記憶體時數分別計費，從 task 啟動（pull image 完成、進入 RUNNING）到停止。計費的最小粒度是一分鐘，不足一分鐘按一分鐘算。</p>
<p>以 ap-northeast-1（東京）為例的單價（截至撰寫時的量級參考，實際以 AWS 定價頁為準）：</p>
<table>
  <thead>
      <tr>
          <th>資源</th>
          <th>單價（每小時）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 vCPU</td>
          <td>~$0.05056</td>
      </tr>
      <tr>
          <td>1 GB RAM</td>
          <td>~$0.00553</td>
      </tr>
  </tbody>
</table>
<p>一個 1 vCPU / 2 GB 的 task 持續運行一個月（730 小時）的費用約為 $0.05056 × 730 + $0.00553 × 2 × 730 ≈ $44.97。這個數字是所有後續比較的基線。</p>
<p>Fargate 的計費粒度還有一個常被忽略的面向：task 規格只能從 AWS 預定義的 vCPU/memory 組合中選。如果應用只需要 0.3 vCPU / 512 MB，最小可選的配置是 0.25 vCPU / 0.5 GB，但如果需要 0.3 vCPU / 1 GB，就得選 0.5 vCPU / 1 GB — 多付了 0.2 vCPU 的費用。這個「階梯式浪費」在小規格 task 上比例最高。</p>
<h2 id="fargate-vs-ec2-launch-type-的成本比較">Fargate vs EC2 launch type 的成本比較</h2>
<p>EC2 launch type 的成本結構不同：付的是 EC2 instance 的時數（不管上面跑幾個 task），加上 ECS 本身不收費。省的是 Fargate 的 markup，多的是 instance 管理（AMI 更新、capacity provider 設定、instance 閒置時仍計費）。</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>Fargate 月費</th>
          <th>EC2（t3.medium）月費</th>
          <th>差異</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1 task, 1 vCPU / 2 GB, 持續</td>
          <td>~$45</td>
          <td>~$30（共享 instance）</td>
          <td>+50%</td>
      </tr>
      <tr>
          <td>5 tasks, 各 0.5 vCPU / 1 GB</td>
          <td>~$113</td>
          <td>~$30（1 台 t3.medium 裝得下）</td>
          <td>+277%</td>
      </tr>
      <tr>
          <td>20 tasks, 各 1 vCPU / 2 GB</td>
          <td>~$900</td>
          <td>~$240（4 台 t3.xlarge）</td>
          <td>+275%</td>
      </tr>
      <tr>
          <td>流量波動大，尖峰 10 tasks / 離峰 1</td>
          <td>~$180（加權平均）</td>
          <td>~$150（需預留尖峰容量）</td>
          <td>+20%</td>
      </tr>
  </tbody>
</table>
<p>幾個判讀要點：</p>
<ul>
<li>task 數量少且持續運行時，Fargate 的溢價比例最高（+50% 到 +277%），但絕對金額小（$15-$80/月的差距），不值得為此承擔 instance 管理的維運負擔</li>
<li>task 數量多且持續運行時，EC2 的絕對節省量開始可觀（$660/月），這時候切換的維運成本有回報</li>
<li>流量波動大時，Fargate 的優勢是按需計費 — 離峰時 task 數降下來就停止計費，EC2 instance 閒置時仍然計費。波動越大，Fargate 的成本效益越接近或超過 EC2</li>
</ul>
<h2 id="fargate-spot">Fargate Spot</h2>
<p>Fargate Spot 使用 AWS 的閒置容量，價格約為 on-demand 的 30%（折扣幅度 ~70%），代價是 AWS 可以隨時回收容量、task 會收到 SIGTERM 後被終止。</p>
<p>適用條件：task 能在 120 秒內優雅停止、應用有重試機制或上游有 load balancer 自動移除不健康的 target。批次處理、背景 worker、可中斷的佇列消費者是典型的 Spot 候選。對外直接服務的 API 通常混合部署 — 基線容量用 on-demand、彈性擴張部分用 Spot。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_service&#34; &#34;api&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">  # ...
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="k">capacity_provider_strategy</span> {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">    capacity_provider</span> <span class="o">=</span> <span class="s2">&#34;FARGATE&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    weight</span>            <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    base</span>              <span class="o">=</span> <span class="m">2</span><span class="c1">  # 至少 2 個 on-demand task 保底
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"></span>  }
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="k">capacity_provider_strategy</span> {
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    capacity_provider</span> <span class="o">=</span> <span class="s2">&#34;FARGATE_SPOT&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    weight</span>            <span class="o">=</span> <span class="m">3</span><span class="c1">  # 擴張時 3/4 的 task 用 Spot
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"></span>  }
</span></span><span class="line"><span class="ln">14</span><span class="cl">}</span></span></code></pre></div><p><code>base = 2</code> 確保至少有兩個 on-demand task 在線（不會被回收），<code>weight</code> 比例讓後續擴張的 task 優先使用 Spot。中斷發生時 ECS 會自動在 on-demand 上補充，但補充需要時間（task 啟動 + health check 通過），這段期間服務容量會短暫下降。</p>
<h2 id="compute-savings-plans">Compute Savings Plans</h2>
<p>Compute Savings Plans 是對 Fargate（和 EC2、Lambda）的預付承諾折扣：承諾每小時固定消費 X 美元的運算量，換取 1 年或 3 年的折扣（1 年約 -20%、3 年約 -40%，視具體方案）。</p>
<p>關鍵判斷：承諾量（$/hr）設在實際用量的多少比例。保守做法是設在過去 3 個月最低用量的 80% — 這部分幾乎確定會用到，享受折扣；超過承諾量的部分自動按 on-demand 計費，不會浪費。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查過去 90 天的 Fargate 用量趨勢</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">aws ce get-cost-and-usage <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --time-period <span class="nv">Start</span><span class="o">=</span>2026-03-01,End<span class="o">=</span>2026-06-01 <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --granularity MONTHLY <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --metrics <span class="s2">&#34;UnblendedCost&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --filter <span class="s1">&#39;{&#34;Dimensions&#34;:{&#34;Key&#34;:&#34;SERVICE&#34;,&#34;Values&#34;:[&#34;Amazon Elastic Container Service&#34;]}}&#39;</span></span></span></code></pre></div><p>Savings Plans 跟 Fargate Spot 可以疊加：Spot task 的費用也能用 Savings Plans 折抵。先用 Savings Plans 降低基線成本，再用 Spot 降低彈性擴張的成本，兩層折扣疊起來可以把 Fargate 的實際單價壓到接近 EC2 on-demand。</p>
<h2 id="task-規格的-rightsizing">Task 規格的 rightsizing</h2>
<p>Fargate task 的 vCPU 和記憶體配置如果設得過大，多出來的資源每小時都在計費。rightsizing 的目標是讓 task 規格貼合實際使用量，但留足安全餘裕。</p>
<h3 id="量測實際使用量">量測實際使用量</h3>
<p>開啟 CloudWatch Container Insights 後，每個 task 的 CPU 和記憶體使用量會自動上報。觀察 7-14 天的 p95 值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 查 ECS service 過去 7 天的 CPU p95</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">aws cloudwatch get-metric-statistics <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --namespace ECS/ContainerInsights <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --metric-name CpuUtilized <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --dimensions <span class="nv">Name</span><span class="o">=</span>ServiceName,Value<span class="o">=</span>api <span class="nv">Name</span><span class="o">=</span>ClusterName,Value<span class="o">=</span>prod <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --start-time 2026-06-19T00:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="se"></span>  --end-time 2026-06-26T00:00:00Z <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  --period <span class="m">3600</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="se"></span>  --statistics p95</span></span></code></pre></div><h3 id="判斷調整方向">判斷調整方向</h3>
<table>
  <thead>
      <tr>
          <th>p95 使用率</th>
          <th>判斷</th>
          <th>動作</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU &lt; 30%</td>
          <td>過度配置，浪費明顯</td>
          <td>降一級 vCPU</td>
      </tr>
      <tr>
          <td>CPU 30-70%</td>
          <td>合理範圍，有足夠餘裕應對尖峰</td>
          <td>維持</td>
      </tr>
      <tr>
          <td>CPU &gt; 80%</td>
          <td>餘裕不足，尖峰時可能觸發 throttling</td>
          <td>升一級 vCPU 或增加 task 數</td>
      </tr>
      <tr>
          <td>Memory &lt; 40%</td>
          <td>過度配置</td>
          <td>降一級 memory</td>
      </tr>
      <tr>
          <td>Memory &gt; 80%</td>
          <td>OOM kill 風險</td>
          <td>升一級 memory</td>
      </tr>
  </tbody>
</table>
<p>調整後觀察 3-5 天確認沒有效能退化再進入下一輪。每次只調一個維度（CPU 或 memory），避免同時改兩個變數無法歸因。</p>
<h3 id="fargate-可選的規格組合">Fargate 可選的規格組合</h3>
<p>Fargate 的 vCPU 和 memory 不能任意搭配。常用的組合：</p>
<table>
  <thead>
      <tr>
          <th>vCPU</th>
          <th>可選 Memory 範圍</th>
          <th>典型用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0.25</td>
          <td>0.5 / 1 / 2 GB</td>
          <td>輕量 sidecar、cron job</td>
      </tr>
      <tr>
          <td>0.5</td>
          <td>1 / 2 / 3 / 4 GB</td>
          <td>小型 API、worker</td>
      </tr>
      <tr>
          <td>1</td>
          <td>2 / 3 / 4 / 5 / 6 / 7 / 8 GB</td>
          <td>標準 API、中型 worker</td>
      </tr>
      <tr>
          <td>2</td>
          <td>4 ~ 16 GB</td>
          <td>高負載 API、批次處理</td>
      </tr>
      <tr>
          <td>4</td>
          <td>8 ~ 30 GB</td>
          <td>資料處理、ML inference</td>
      </tr>
  </tbody>
</table>
<p>選的時候從最小的「能跑」組合開始，用 Container Insights 量測後再調。常見的浪費是把所有 task 都設成 1 vCPU / 2 GB — 一個只用 0.1 vCPU / 256 MB 的 sidecar 也配了同樣的規格。</p>
<h2 id="何時從-fargate-切到-ec2">何時從 Fargate 切到 EC2</h2>
<p>切換的判斷不只看成本差額，還要看維運能力。EC2 launch type 需要管理：AMI 更新（安全 patch）、instance draining（rolling update 時把 task 遷走再關 instance）、capacity provider 的擴縮邏輯、instance 的 security group 與 IAM role。</p>
<table>
  <thead>
      <tr>
          <th>判斷維度</th>
          <th>留在 Fargate</th>
          <th>切到 EC2</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>月費差額</td>
          <td>&lt; $200</td>
          <td>&gt; $500 且持續 3 個月</td>
      </tr>
      <tr>
          <td>團隊維運能力</td>
          <td>沒有專人管 instance</td>
          <td>有平台工程師或 DevOps</td>
      </tr>
      <tr>
          <td>流量型態</td>
          <td>波動大、有明顯離峰</td>
          <td>穩定、24/7 持續運行</td>
      </tr>
      <tr>
          <td>GPU 需求</td>
          <td>不需要</td>
          <td>需要（Fargate 不支援 GPU）</td>
      </tr>
      <tr>
          <td>啟動速度</td>
          <td>可接受 cold start</td>
          <td>需要 &lt;1s 啟動（EC2 instance 已在線）</td>
      </tr>
  </tbody>
</table>
<p>混合部署是常見的中間路線：基線容量用 EC2（成本低、啟動快），尖峰彈性用 Fargate Spot（按需、不需預留）。這需要同時維護兩種 capacity provider，複雜度較高。</p>
<h2 id="成本監控">成本監控</h2>
<p>把 ECS 的成本歸因到服務層級需要兩個機制：task 層的 tag propagation 和 Cost Explorer 的 tag 維度。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_ecs_service&#34; &#34;api&#34;</span> {<span class="c1">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">  # ...
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"></span><span class="n">  propagate_tags</span> <span class="o">=</span> <span class="s2">&#34;SERVICE&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  tags</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    service</span>     <span class="o">=</span> <span class="s2">&#34;payment-api&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    env</span>         <span class="o">=</span> <span class="s2">&#34;prod&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    cost-center</span> <span class="o">=</span> <span class="s2">&#34;cc-payments&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  }
</span></span><span class="line"><span class="ln">10</span><span class="cl">}</span></span></code></pre></div><p><code>propagate_tags = &quot;SERVICE&quot;</code> 讓 service 的 tag 自動傳播到每個 task，Cost Explorer 就能按 <code>service</code> 或 <code>cost-center</code> 維度拆分 Fargate 費用。這跟<a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>的 tagging 規範對齊 — tag 是成本可見性的地基。</p>
<p>定期（月初或月中）檢查 Cost Explorer 的 Fargate 費用趨勢：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">aws ce get-cost-and-usage <span class="se">\
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="se"></span>  --time-period <span class="nv">Start</span><span class="o">=</span>2026-06-01,End<span class="o">=</span>2026-06-26 <span class="se">\
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="se"></span>  --granularity DAILY <span class="se">\
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="se"></span>  --metrics <span class="s2">&#34;UnblendedCost&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="se"></span>  --group-by <span class="nv">Type</span><span class="o">=</span>TAG,Key<span class="o">=</span>service <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --filter <span class="s1">&#39;{&#34;Dimensions&#34;:{&#34;Key&#34;:&#34;SERVICE&#34;,&#34;Values&#34;:[&#34;Amazon Elastic Container Service&#34;]}}&#39;</span></span></span></code></pre></div><p>費用突然跳升時，先看是 task 數增加（auto-scaling 觸發）還是單價變化（Savings Plans 過期或 Spot 中斷後自動回補為 on-demand）。這兩者的處理方式不同：前者檢查 scaling policy、後者檢查 Savings Plans 到期日和 Spot 回收頻率。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/05-core-services/compute-ecs-eks/" data-link-title="運算平台上 IaC — ECS 與 EKS" data-link-desc="容器運算平台的 IaC 描述：ECS 與 EKS 選型、task definition 與映像版本解耦、IAM task role 分離、auto-scaling 策略">運算平台上 IaC</a>：ECS vs EKS 選型、Fargate 的定位</li>
<li>→ <a href="/blog/infra/08-governance-habits/" data-link-title="模組八：治理好習慣 — 規模長大後不失控的最小節奏" data-link-desc="tagging 規範、secrets 不進 code、成本可見性、最小可行節奏，規模長大後不失控">模組八：治理好習慣</a>：tagging 與成本可見性的地基</li>
<li>→ <a href="/blog/devops/08-cost-management/" data-link-title="模組八：成本管理" data-link-desc="雲端帳單怎麼不失控 — reserved instance、spot instance、right-sizing、成本監控告警">devops 模組八：成本管理</a>：運行期的 RI / Spot / rightsizing 策略</li>
</ul>
]]></content:encoded></item><item><title>模組八：實戰效能優化</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/</guid><description>&lt;p>本模組將入門系列學到的「並行處理」和「效能優化」知識，應用於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何分析、測量、優化真實系統。&lt;/p>
&lt;h2 id="與入門系列的關係">與入門系列的關係&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">入門系列 本模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">3.7 並行處理 ──────────────────→ 並行處理實戰
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> (概念與 API) (應用於真實系統)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3.8 效能優化 ──────────────────→ 效能調優實戰
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> (原則與工具) (實際測量與改善)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>入門系列教你「工具怎麼用」，本模組教你「什麼時候用、用了效果如何」。&lt;/p>
&lt;h2 id="為什麼需要這個模組">為什麼需要這個模組？&lt;/h2>
&lt;p>在入門系列中，我們學習了：&lt;/p>
&lt;ul>
&lt;li>&lt;code>ThreadPoolExecutor&lt;/code> 和 &lt;code>ProcessPoolExecutor&lt;/code> 的用法&lt;/li>
&lt;li>&lt;code>timeit&lt;/code>、&lt;code>cProfile&lt;/code> 等效能測量工具&lt;/li>
&lt;li>正則表達式、資料結構選擇等優化技巧&lt;/li>
&lt;/ul>
&lt;p>但在實際專案中，你可能會問：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>這段程式碼值得優化嗎？&lt;/strong> → 需要先測量&lt;/li>
&lt;li>&lt;strong>用並行能快多少？&lt;/strong> → 需要實際比較&lt;/li>
&lt;li>&lt;strong>優化後維護成本增加多少？&lt;/strong> → 需要權衡&lt;/li>
&lt;/ul>
&lt;p>本模組透過真實案例回答這些問題。&lt;/p>
&lt;h2 id="章節列表">章節列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>關鍵收穫&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1&lt;/a>&lt;/td>
 &lt;td>並行處理實戰&lt;/td>
 &lt;td>將 I/O 密集任務並行化&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2&lt;/a>&lt;/td>
 &lt;td>效能調優實戰&lt;/td>
 &lt;td>測量、分析、優化的完整流程&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="案例研究">案例研究&lt;/h2>
&lt;p>基於 &lt;code>.claude/lib&lt;/code> 實際程式碼的進階案例：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>學習重點&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>ThreadPoolExecutor&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>as_completed + 進度報告&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>re.compile 效能提升&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取&lt;/a>&lt;/td>
 &lt;td>git_utils.py&lt;/td>
 &lt;td>functools.lru_cache&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>list vs set 查詢效能&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="可執行程式碼">可執行程式碼&lt;/h2>
&lt;p>本模組提供完整的可執行程式碼，放在 &lt;code>/static/code/practical-optimization/&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">practical-optimization/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── original/ # 原始版本（對照組）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── optimized/ # 優化版本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── benchmarks/ # 效能測試腳本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── profiling/ # 效能分析腳本&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>你可以下載這些程式碼，在自己的環境中執行效能測試。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>需要先讀&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>8.1 並行處理實戰&lt;/td>
 &lt;td>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>8.2 效能調優實戰&lt;/td>
 &lt;td>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">3.8 效能優化&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習目標">學習目標&lt;/h2>
&lt;p>完成本模組後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>識別優化機會&lt;/strong>：判斷哪些程式碼值得優化&lt;/li>
&lt;li>&lt;strong>測量效能基準&lt;/strong>：使用 cProfile、timeit 建立基準數據&lt;/li>
&lt;li>&lt;strong>應用並行處理&lt;/strong>：選擇適當的並行模式加速 I/O 任務&lt;/li>
&lt;li>&lt;strong>實施效能優化&lt;/strong>：正則預編譯、快取策略、資料結構選擇&lt;/li>
&lt;li>&lt;strong>評估優化效果&lt;/strong>：比較優化前後的效能差異&lt;/li>
&lt;/ol>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>每章節約 30-45 分鐘，全模組約 2-3 小時&lt;/p>
&lt;hr>
&lt;p>上一模組：&lt;a href="https://tarrragon.github.io/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">模組七：打包與發布&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本模組將入門系列學到的「並行處理」和「效能優化」知識，應用於 <code>.claude/lib</code> 的實際程式碼，展示如何分析、測量、優化真實系統。</p>
<h2 id="與入門系列的關係">與入門系列的關係</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">入門系列                          本模組
</span></span><span class="line"><span class="ln">2</span><span class="cl">3.7 並行處理 ──────────────────→ 並行處理實戰
</span></span><span class="line"><span class="ln">3</span><span class="cl">    (概念與 API)                   (應用於真實系統)
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">3.8 效能優化 ──────────────────→ 效能調優實戰
</span></span><span class="line"><span class="ln">6</span><span class="cl">    (原則與工具)                   (實際測量與改善)</span></span></code></pre></div><p>入門系列教你「工具怎麼用」，本模組教你「什麼時候用、用了效果如何」。</p>
<h2 id="為什麼需要這個模組">為什麼需要這個模組？</h2>
<p>在入門系列中，我們學習了：</p>
<ul>
<li><code>ThreadPoolExecutor</code> 和 <code>ProcessPoolExecutor</code> 的用法</li>
<li><code>timeit</code>、<code>cProfile</code> 等效能測量工具</li>
<li>正則表達式、資料結構選擇等優化技巧</li>
</ul>
<p>但在實際專案中，你可能會問：</p>
<ul>
<li><strong>這段程式碼值得優化嗎？</strong> → 需要先測量</li>
<li><strong>用並行能快多少？</strong> → 需要實際比較</li>
<li><strong>優化後維護成本增加多少？</strong> → 需要權衡</li>
</ul>
<p>本模組透過真實案例回答這些問題。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1</a></td>
          <td>並行處理實戰</td>
          <td>將 I/O 密集任務並行化</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2</a></td>
          <td>效能調優實戰</td>
          <td>測量、分析、優化的完整流程</td>
      </tr>
  </tbody>
</table>
<h2 id="案例研究">案例研究</h2>
<p>基於 <code>.claude/lib</code> 實際程式碼的進階案例：</p>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>學習重點</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></td>
          <td>markdown_link_checker.py</td>
          <td>ThreadPoolExecutor</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></td>
          <td>hook_validator.py</td>
          <td>as_completed + 進度報告</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></td>
          <td>hook_validator.py</td>
          <td>re.compile 效能提升</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></td>
          <td>git_utils.py</td>
          <td>functools.lru_cache</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇</a></td>
          <td>hook_validator.py</td>
          <td>list vs set 查詢效能</td>
      </tr>
  </tbody>
</table>
<h2 id="可執行程式碼">可執行程式碼</h2>
<p>本模組提供完整的可執行程式碼，放在 <code>/static/code/practical-optimization/</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">practical-optimization/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── original/           # 原始版本（對照組）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── optimized/          # 優化版本
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── benchmarks/         # 效能測試腳本
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── profiling/          # 效能分析腳本</span></span></code></pre></div><p>你可以下載這些程式碼，在自己的環境中執行效能測試。</p>
<h2 id="先備知識">先備知識</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>需要先讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>8.1 並行處理實戰</td>
          <td>入門系列 <a href="/blog/python/03-stdlib/concurrency/" data-link-title="3.7 並行處理 - threading、multiprocessing、concurrent.futures" data-link-desc="Python 並行處理的三種方式與選擇指南">3.7 並行處理</a></td>
      </tr>
      <tr>
          <td>8.2 效能調優實戰</td>
          <td>入門系列 <a href="/blog/python/03-stdlib/performance/" data-link-title="3.8 效能迷思與優化策略" data-link-desc="Python 效能的真相、常見誤解與優化方法">3.8 效能優化</a></td>
      </tr>
  </tbody>
</table>
<h2 id="學習目標">學習目標</h2>
<p>完成本模組後，你將能夠：</p>
<ol>
<li><strong>識別優化機會</strong>：判斷哪些程式碼值得優化</li>
<li><strong>測量效能基準</strong>：使用 cProfile、timeit 建立基準數據</li>
<li><strong>應用並行處理</strong>：選擇適當的並行模式加速 I/O 任務</li>
<li><strong>實施效能優化</strong>：正則預編譯、快取策略、資料結構選擇</li>
<li><strong>評估優化效果</strong>：比較優化前後的效能差異</li>
</ol>
<h2 id="學習時間">學習時間</h2>
<p>每章節約 30-45 分鐘，全模組約 2-3 小時</p>
<hr>
<p>上一模組：<a href="/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">模組七：打包與發布</a></p>
]]></content:encoded></item><item><title>案例研究：效能優化實戰</title><link>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用並行處理和效能優化技術改善真實系統。&lt;/p>
&lt;h2 id="案例列表">案例列表&lt;/h2>
&lt;h3 id="並行處理">並行處理&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>技術&lt;/th>
 &lt;th>預期加速&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查&lt;/a>&lt;/td>
 &lt;td>markdown_link_checker.py&lt;/td>
 &lt;td>ThreadPoolExecutor&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>as_completed + 進度&lt;/td>
 &lt;td>3-5x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="效能調優">效能調優&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>案例&lt;/th>
 &lt;th>素材&lt;/th>
 &lt;th>技術&lt;/th>
 &lt;th>預期加速&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>re.compile&lt;/td>
 &lt;td>1.2-1.3x&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取&lt;/a>&lt;/td>
 &lt;td>git_utils.py&lt;/td>
 &lt;td>functools.lru_cache&lt;/td>
 &lt;td>視命中率&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>set vs list&lt;/td>
 &lt;td>10-100x&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">並行檔案檢查（入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">並行 Hook 驗證（進階：含進度報告）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">正則表達式預編譯（效能調優入門）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">LRU 快取（快取策略）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">資料結構選擇（演算法思維）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="可執行程式碼">可執行程式碼&lt;/h2>
&lt;p>所有案例都有對應的可執行程式碼：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 下載程式碼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="nb">cd&lt;/span> /path/to/your/workspace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行效能測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">python benchmarks/benchmark_parallel.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">python benchmarks/benchmark_regex.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">python benchmarks/benchmark_cache.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">python benchmarks/benchmark_data_structure.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="c1"># 執行效能分析&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">python profiling/profile_link_checker.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;p>建議先完成以下章節：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2 效能調優實戰&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八：實戰效能優化&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用並行處理和效能優化技術改善真實系統。</p>
<h2 id="案例列表">案例列表</h2>
<h3 id="並行處理">並行處理</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>技術</th>
          <th>預期加速</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-file-check/" data-link-title="案例：並行檔案檢查" data-link-desc="使用 ThreadPoolExecutor 加速 Markdown 連結檢查">並行檔案檢查</a></td>
          <td>markdown_link_checker.py</td>
          <td>ThreadPoolExecutor</td>
          <td>3-5x</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/parallel-hook-validation/" data-link-title="案例：並行 Hook 驗證" data-link-desc="使用 ThreadPoolExecutor 並行驗證 Hook，並實現進度報告">並行 Hook 驗證</a></td>
          <td>hook_validator.py</td>
          <td>as_completed + 進度</td>
          <td>3-5x</td>
      </tr>
  </tbody>
</table>
<h3 id="效能調優">效能調優</h3>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>技術</th>
          <th>預期加速</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/regex-precompile/" data-link-title="案例：正則表達式預編譯" data-link-desc="用 re.compile 減少重複編譯開銷">正則表達式預編譯</a></td>
          <td>hook_validator.py</td>
          <td>re.compile</td>
          <td>1.2-1.3x</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/lru-cache-branch/" data-link-title="案例：LRU 快取" data-link-desc="用 functools.lru_cache 快取重複計算">LRU 快取</a></td>
          <td>git_utils.py</td>
          <td>functools.lru_cache</td>
          <td>視命中率</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/08-practical-optimization/case-studies/data-structure-choice/" data-link-title="案例：資料結構選擇" data-link-desc="選擇正確的資料結構：list vs set 的查詢效能差異">資料結構選擇</a></td>
          <td>hook_validator.py</td>
          <td>set vs list</td>
          <td>10-100x</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">並行檔案檢查（入門）
</span></span><span class="line"><span class="ln">2</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">3</span><span class="cl">並行 Hook 驗證（進階：含進度報告）
</span></span><span class="line"><span class="ln">4</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">5</span><span class="cl">正則表達式預編譯（效能調優入門）
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">7</span><span class="cl">LRU 快取（快取策略）
</span></span><span class="line"><span class="ln">8</span><span class="cl">    ↓
</span></span><span class="line"><span class="ln">9</span><span class="cl">資料結構選擇（演算法思維）</span></span></code></pre></div><h2 id="可執行程式碼">可執行程式碼</h2>
<p>所有案例都有對應的可執行程式碼：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 下載程式碼</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nb">cd</span> /path/to/your/workspace
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 執行效能測試</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">python benchmarks/benchmark_parallel.py
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">python benchmarks/benchmark_regex.py
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">python benchmarks/benchmark_cache.py
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">python benchmarks/benchmark_data_structure.py
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 執行效能分析</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">python profiling/profile_link_checker.py</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/08-practical-optimization/parallel-processing/" data-link-title="8.1 並行處理實戰" data-link-desc="將 concurrent.futures 應用於真實的 I/O 密集任務">8.1 並行處理實戰</a></li>
<li><a href="/blog/python-advanced/08-practical-optimization/performance-tuning/" data-link-title="8.2 效能調優實戰" data-link-desc="測量、分析、優化的完整流程">8.2 效能調優實戰</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/08-practical-optimization/" data-link-title="模組八：實戰效能優化" data-link-desc="將入門系列的並行處理與效能優化知識應用於真實系統">模組八：實戰效能優化</a></p>
]]></content:encoded></item></channel></rss>