<?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>Design-Patterns on Tarragon</title><link>https://tarrragon.github.io/blog/tags/design-patterns/</link><description>Recent content in Design-Patterns on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/design-patterns/index.xml" rel="self" type="application/rss+xml"/><item><title>案例：快取生命週期管理</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/config_loader.py&lt;/code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理&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"># 全域快取變數&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">load_quality_rules&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">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">_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="k">if&lt;/span> &lt;span class="n">_quality_rules_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">23&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">24&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_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;quality_rules&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="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">26&lt;/span>&lt;span class="cl"> &lt;span class="n">_quality_rules_cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_get_default_quality_rules&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="k">return&lt;/span> &lt;span class="n">_quality_rules_cache&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">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">30&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">31&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">32&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">33&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;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>API 簡潔&lt;/strong>：呼叫者不需要管理快取&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;h4 id="問題-1測試難以隔離">問題 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="k">def&lt;/span> &lt;span class="nf">test_load_agents_config&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"># 測試 A：預設配置&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&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">test_load_agents_config_custom&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="c1"># 測試 B：自訂配置檔案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 問題：測試 A 的快取會影響測試 B！&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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"># 可能拿到測試 A 的快取結果&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2快取生命週期不明確">問題 2：快取生命週期不明確&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="k">def&lt;/span> &lt;span class="nf">process_hooks&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="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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"># ... 處理完成&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"># 問題：什麼時候應該清除快取？&lt;/span>
&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;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3清除快取容易忘記">問題 3：清除快取容易忘記&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="k">def&lt;/span> &lt;span class="nf">test_something&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;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">environ&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/tmp/test&amp;#34;&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"># 執行測試&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="c1"># 問題：忘記清除快取！&lt;/span>
&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;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取&lt;/h2>
&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;h4 id="步驟-1建立快取管理類別">步驟 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="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&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">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&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">os&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">class&lt;/span> &lt;span class="nc">ConfigManager&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;
&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"> 用 Context Manager 控制快取的生命週期，
&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"> 解決全域快取的問題。
&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"> &amp;#34;&amp;#34;&amp;#34;&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">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">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="s2"> 初始化配置管理器
&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">
&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"> Args:
&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"> project_root: 專案根目錄，預設從環境變數讀取
&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;&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">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">22&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 class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &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">23&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">24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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">Any&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">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="nd">@property&lt;/span>
&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">config_dir&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="n">Path&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 class="k">return&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;config&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>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_config&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">name&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">dict&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="s2">&amp;#34;&amp;#34;&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="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"> Args:
&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"> name: 配置名稱
&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"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&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">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&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">_cache&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="bp">self&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">name&lt;/span>&lt;span class="p">]&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">_load_from_file&lt;/span>&lt;span class="p">(&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">43&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">_cache&lt;/span>&lt;span class="p">[&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">def&lt;/span> &lt;span class="nf">_load_from_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">name&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">dict&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="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">47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_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">config_dir&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">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&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="k">if&lt;/span> &lt;span class="n">yaml_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">49&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_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">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&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">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">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">53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">54&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&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="kc">None&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="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">56&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&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>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援&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="k">class&lt;/span> &lt;span class="nc">ConfigManager&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;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="nd">@contextmanager&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="nf">cached_scope&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="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&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;&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"> 快取範圍 Context Manager
&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"> 離開範圍時自動清除快取。
&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">
&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"> Yields:
&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"> ConfigManager: 自己，方便鏈式呼叫
&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">
&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"> Example:
&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"> manager = ConfigManager()
&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"> with manager.cached_scope():
&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"> config = manager.load_config(&amp;#34;agents&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="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"> # 離開時快取自動清除
&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;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&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">23&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&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">finally&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&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="nd">@contextmanager&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">def&lt;/span> &lt;span class="nf">isolated_scope&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="bp">self&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">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>&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 class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&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="s2">&amp;#34;&amp;#34;&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="s2"> 隔離範圍 Context Manager
&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"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 臨時的專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2">
&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"> Yields:
&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"> ConfigManager: 新的管理器實例
&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"> Example:
&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"> with manager.isolated_scope(&amp;#34;/tmp/test&amp;#34;) as isolated:
&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"> config = isolated.load_config(&amp;#34;agents&amp;#34;)
&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"> # 完全隔離，不影響原本的 manager
&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="n">isolated_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&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 class="n">project_root&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&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="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="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">try&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="k">yield&lt;/span> &lt;span class="n">isolated_manager&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">finally&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">isolated_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-3加入便利方法">步驟 3：加入便利方法&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="k">class&lt;/span> &lt;span class="nc">ConfigManager&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;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">load_agents_config&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">dict&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;載入代理人配置（帶預設值）&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="k">try&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="bp">self&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"> 8&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"> 9&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">_get_default_agents_config&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">def&lt;/span> &lt;span class="nf">load_quality_rules&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">dict&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="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">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="k">return&lt;/span> &lt;span class="bp">self&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;quality_rules&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="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_get_default_quality_rules&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">def&lt;/span> &lt;span class="nf">_get_default_agents_config&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">dict&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;預設代理人配置&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="k">return&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;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;basil&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;thyme&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mint&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;agent_dispatch_rules&amp;#34;&lt;/span>&lt;span class="p">:&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">_get_default_quality_rules&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">dict&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;預設品質規則配置&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="k">return&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;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;allowed_tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Edit&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="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="完整程式碼">完整程式碼&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="ch">#!/usr/bin/env python3&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">展示如何用 Context Manager 管理配置快取的生命週期。
&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&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"> 10&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">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&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="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="kn">import&lt;/span> &lt;span class="nn">yaml&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">HAS_YAML&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"> 16&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&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="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">json&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="k">class&lt;/span> &lt;span class="nc">ConfigManager&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;&amp;#34;&amp;#34;
&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"> 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl">&lt;span class="s2"> 用 Context Manager 控制快取的生命週期。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&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"> 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="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"> 28&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"> 29&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 class="s2">&amp;#34;CLAUDE_PROJECT_DIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &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"> 30&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"> 31&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_cache&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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">Any&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"> 32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 33&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"> 34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">config_dir&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="n">Path&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="k">return&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;config&amp;#34;&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="c1"># ===== 載入方法 =====&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="k">def&lt;/span> &lt;span class="nf">load_config&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">name&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">dict&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="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"> 41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">not&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">_cache&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="bp">self&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">name&lt;/span>&lt;span class="p">]&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">_load_from_file&lt;/span>&lt;span class="p">(&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"> 43&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">_cache&lt;/span>&lt;span class="p">[&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">def&lt;/span> &lt;span class="nf">_load_from_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">name&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">dict&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="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"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">yaml_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">config_dir&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">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&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="n">json_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">config_dir&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">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.json&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">yaml_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">HAS_YAML&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_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"> 52&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">json_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"> 55&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">json_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"> 56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&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"> 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="k">raise&lt;/span> &lt;span class="ne">FileNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config not found: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">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"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">clear_cache&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="kc">None&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="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"> 62&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== Context Manager =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 65&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">cached_scope&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="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&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="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"> 69&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"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="bp">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&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 class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nd">@contextmanager&lt;/span>
&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">isolated_scope&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">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>&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">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ConfigManager&amp;#34;&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;隔離範圍：建立獨立的配置環境&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="n">isolated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">project_root&lt;/span> &lt;span class="ow">or&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="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&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"> 82&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">isolated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&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 class="n">isolated&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear_cache&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ===== 便利方法 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&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="bp">self&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"> 89&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"> 90&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"> 91&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">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"> 92&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"> 93&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[],&lt;/span> &lt;span class="s2">&amp;#34;agent_dispatch_rules&amp;#34;&lt;/span>&lt;span class="p">:&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&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">dict&lt;/span>&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="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"> 97&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"> 98&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">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;quality_rules&amp;#34;&lt;/span>&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 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">100&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;trigger_conditions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{},&lt;/span> &lt;span class="s2">&amp;#34;cache&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;ttl_minutes&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 全域便利函式（相容舊 API）=====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl">&lt;span class="n">_default_manager&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&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">105&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&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">108&lt;/span>&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">_default_manager&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">110&lt;/span>&lt;span class="cl"> &lt;span class="n">_default_manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">_default_manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&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">114&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入代理人配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">load_quality_rules&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">118&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;相容舊 API：載入品質規則配置&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">get_config_manager&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl">&lt;span class="nd">@contextmanager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">config_scope&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 class="o">-&amp;gt;&lt;/span> &lt;span class="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&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">124&lt;/span>&lt;span class="cl">&lt;span class="s2"> 配置範圍 Context Manager
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立一個有明確生命週期的配置環境。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&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">129&lt;/span>&lt;span class="cl">&lt;span class="s2"> project_root: 專案根目錄
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl">&lt;span class="s2"> Yields:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl">&lt;span class="s2"> ConfigManager: 配置管理器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl">&lt;span class="s2"> with config_scope(&amp;#34;/path/to/project&amp;#34;) as config:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl">&lt;span class="s2"> agents = config.load_agents_config()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">137&lt;/span>&lt;span class="cl">&lt;span class="s2"> rules = config.load_quality_rules()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl">&lt;span class="s2"> # 離開時快取自動清除
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&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">140&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&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">141&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">144&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 測試範例 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">146&lt;/span>&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &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">147&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">tempfile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">150&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">151&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&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">tmpdir&lt;/span>&lt;span class="p">)&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;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&lt;/span>&lt;span class="cl"> &lt;span class="n">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&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">153&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 寫入測試配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">155&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">156&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent-1&amp;#34;, &amp;#34;test-agent-2&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">158&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用 Context Manager&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">160&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">161&lt;/span>&lt;span class="cl"> &lt;span class="n">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">162&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">agents&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">163&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">164&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 第二次呼叫會使用快取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">165&lt;/span>&lt;span class="cl"> &lt;span class="n">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">166&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">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&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">167&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">168&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 離開範圍後快取已清除&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">169&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">170&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">171&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 測試隔離範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">172&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;&lt;/span>&lt;span class="se">\n&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">173&lt;/span>&lt;span class="cl"> &lt;span class="n">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">174&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">175&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">176&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 隔離環境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">177&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">178&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">isolated&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;nonexistent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">179&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">FileNotFoundError&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">180&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">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;span class="line">&lt;span class="ln">181&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">182&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;/code>&lt;/pre>&lt;/div>&lt;h3 id="使用範例">使用範例&lt;/h3>
&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"># 建立配置管理器&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">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&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>&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="k">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cached_scope&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">agents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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">rules&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_quality_rules&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="n">agents2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="n">agents&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">agents2&lt;/span> &lt;span class="c1"># 同一個物件&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;/code>&lt;/pre>&lt;/div>&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="kn">import&lt;/span> &lt;span class="nn">pytest&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">import&lt;/span> &lt;span class="nn">tempfile&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="nd">@pytest.fixture&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">def&lt;/span> &lt;span class="nf">config_manager&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;測試用的配置管理器&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="k">with&lt;/span> &lt;span class="n">tempfile&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TemporaryDirectory&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tmpdir&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="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="n">config_dir&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">tmpdir&lt;/span>&lt;span class="p">)&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;config&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">config_dir&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mkdir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parents&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">12&lt;/span>&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">config_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;agents.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">write_text&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;{&amp;#34;known_agents&amp;#34;: [&amp;#34;test-agent&amp;#34;]}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &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">manager&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ConfigManager&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">with&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isolated_scope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tmpdir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">isolated&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">yield&lt;/span> &lt;span class="n">isolated&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">test_load_agents_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&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;&amp;#34;&amp;#34;測試載入代理人配置&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="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;known_agents&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;test-agent&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>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">test_cache_works&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_manager&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;測試快取功能&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 class="n">config1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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">config2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&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="k">assert&lt;/span> &lt;span class="n">config1&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">config2&lt;/span> &lt;span class="c1"># 同一個物件&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="相容舊-api">相容舊 API&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"># 如果有既有程式碼使用舊 API&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">config_loader&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config_scope&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_agents_config&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="c1"># 新的呼叫方式有明確的生命週期&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">with&lt;/span> &lt;span class="n">config_scope&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">manager&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">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">manager&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="設計權衡">設計權衡&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>面向&lt;/th>
 &lt;th>全域快取&lt;/th>
 &lt;th>Context Manager&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>簡單性&lt;/td>
 &lt;td>最簡單&lt;/td>
 &lt;td>需要理解 Context Manager&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>生命週期&lt;/td>
 &lt;td>不明確（程式結束前一直存在）&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>並行安全&lt;/td>
 &lt;td>不安全（共用全域變數）&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>API 相容性&lt;/td>
 &lt;td>N/A&lt;/td>
 &lt;td>可以相容舊 API&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/config_loader.py</code> 的實際程式碼，展示如何用 Context Manager 管理快取生命週期。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理</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"># 全域快取變數</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">load_quality_rules</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">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">_quality_rules_cache</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">_quality_rules_cache</span> <span class="ow">is</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</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">26</span><span class="cl">            <span class="n">_quality_rules_cache</span> <span class="o">=</span> <span class="n">_get_default_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">_quality_rules_cache</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">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">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="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">32</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">33</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><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：全域變數是最簡單的快取方式</li>
<li><strong>效能好</strong>：配置只讀取一次</li>
<li><strong>API 簡潔</strong>：呼叫者不需要管理快取</li>
</ol>
<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">test_load_agents_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># 測試 A：預設配置</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">assert</span> <span class="s2">&#34;known_agents&#34;</span> <span class="ow">in</span> <span class="n">config</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">test_load_agents_config_custom</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 測試 B：自訂配置檔案</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 問題：測試 A 的快取會影響測試 B！</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 可能拿到測試 A 的快取結果</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">process_hooks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">load_agents_config</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></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="c1"># 如果配置檔案改了，這裡會用到舊的快取</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="k">def</span> <span class="nf">test_something</span><span class="p">():</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="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;CLAUDE_PROJECT_DIR&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;/tmp/test&#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="c1"># 執行測試</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_agents_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><span class="line"><span class="ln">9</span><span class="cl">    <span class="c1"># 下一個測試會用到這個測試的快取</span></span></span></code></pre></div><h2 id="進階解決方案context-manager-管理快取">進階解決方案：Context Manager 管理快取</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>明確的快取範圍</strong>：快取的生命週期有明確的開始和結束</li>
<li><strong>自動清理</strong>：離開範圍時自動清除快取</li>
<li><strong>測試友好</strong>：每個測試可以有獨立的快取</li>
</ol>
<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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</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">Any</span><span class="p">,</span> <span class="n">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</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></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigManager</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">    配置管理器
</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">    用 Context Manager 控制快取的生命週期，
</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></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 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">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="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">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            project_root: 專案根目錄，預設從環境變數讀取
</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="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">22</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">23</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">24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><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">Any</span><span class="p">]</span> <span class="o">=</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="nd">@property</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</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">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 class="k">return</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;config&#34;</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">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</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">        Args:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">            name: 配置名稱
</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">        Returns:
</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">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</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">42</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">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</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="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</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">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置（內部方法）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_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">51</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </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">53</span><span class="cl">
</span></span><span class="line"><span class="ln">54</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">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="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></code></pre></div><h4 id="步驟-2加入-context-manager-支援">步驟 2：加入 Context Manager 支援</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">ConfigManager</span><span class="p">:</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></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#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;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        快取範圍 Context Manager
</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">        Yields:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            ConfigManager: 自己，方便鏈式呼叫
</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">        Example:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">            manager = ConfigManager()
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            with manager.cached_scope():
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">                config = manager.load_config(&#34;agents&#34;)
</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">            # 離開時快取自動清除
</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</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="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <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></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        隔離範圍 Context Manager
</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">        Args:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            project_root: 臨時的專案根目錄
</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">        Yields:
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">            ConfigManager: 新的管理器實例
</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">        Example:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">            with manager.isolated_scope(&#34;/tmp/test&#34;) as isolated:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">                config = isolated.load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">                # 完全隔離，不影響原本的 manager
</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="n">isolated_manager</span> <span class="o">=</span> <span class="n">ConfigManager</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">project_root</span> <span class="ow">or</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="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 class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated_manager</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">            <span class="n">isolated_manager</span><span class="o">.</span><span class="n">clear_cache</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="k">class</span> <span class="nc">ConfigManager</span><span class="p">:</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></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">load_agents_config</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"> 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">try</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="bp">self</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"> 8</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"> 9</span><span class="cl">            <span class="k">return</span> <span class="bp">self</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">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">load_quality_rules</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">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 class="k">try</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="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#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="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_default_quality_rules</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">_get_default_agents_config</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">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="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;basil&#34;</span><span class="p">,</span> <span class="s2">&#34;thyme&#34;</span><span class="p">,</span> <span class="s2">&#34;mint&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</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">_get_default_quality_rules</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">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="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;allowed_tools&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Write&#34;</span><span class="p">,</span> <span class="s2">&#34;Edit&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <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">快取生命週期管理 - 完整範例
</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">展示如何用 Context Manager 管理配置快取的生命週期。
</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">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln">  9</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"> 10</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">Iterator</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">import</span> <span class="nn">os</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</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">class</span> <span class="nc">ConfigManager</span><span class="p">:</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="s2">    配置管理器
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    用 Context Manager 控制快取的生命週期。
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="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"> 28</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"> 29</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"> 30</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"> 31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><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">Any</span><span class="p">]</span> <span class="o">=</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="nd">@property</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="k">def</span> <span class="nf">config_dir</span><span class="p">(</span><span class="bp">self</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"> 35</span><span class="cl">        <span class="k">return</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;config&#34;</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"># ===== 載入方法 =====</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">load_config</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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"> 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">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</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"> 42</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">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_from_file</span><span class="p">(</span><span class="n">name</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="bp">self</span><span class="o">.</span><span class="n">_cache</span><span class="p">[</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">def</span> <span class="nf">_load_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">yaml_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">json_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.json&#34;</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="k">if</span> <span class="n">yaml_path</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="ow">and</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_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"> 52</span><span class="cl">                <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="ow">or</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">if</span> <span class="n">json_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">            <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">json_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"> 56</span><span class="cl">                <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</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">raise</span> <span class="ne">FileNotFoundError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Config not found: </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"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</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"> 61</span><span class="cl">        <span class="s2">&#34;&#34;&#34;清除所有快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</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"> 63</span><span class="cl">
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="c1"># ===== Context Manager =====</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="k">def</span> <span class="nf">cached_scope</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="s2">&#34;&#34;&#34;快取範圍：離開時自動清除快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">            <span class="k">yield</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="nf">isolated_scope</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">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></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="s2">&#34;ConfigManager&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;隔離範圍：建立獨立的配置環境&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="n">isolated</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span> <span class="ow">or</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="p">))</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="n">isolated</span><span class="o">.</span><span class="n">clear_cache</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="c1"># ===== 便利方法 =====</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">load_agents_config</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"> 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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">            <span class="k">return</span> <span class="bp">self</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"> 92</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"> 93</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;known_agents&#34;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&#34;agent_dispatch_rules&#34;</span><span class="p">:</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="k">def</span> <span class="nf">load_quality_rules</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"> 96</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;quality_rules&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</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">100</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;trigger_conditions&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;cache&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;ttl_minutes&#34;</span><span class="p">:</span> <span class="mi">5</span><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="c1"># ===== 全域便利函式（相容舊 API）=====</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="n">_default_manager</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</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">def</span> <span class="nf">get_config_manager</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">ConfigManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="s2">&#34;&#34;&#34;獲取預設的配置管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">global</span> <span class="n">_default_manager</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">if</span> <span class="n">_default_manager</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="n">_default_manager</span> <span class="o">=</span> <span class="n">ConfigManager</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">_default_manager</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">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">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="k">def</span> <span class="nf">load_quality_rules</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">118</span><span class="cl">    <span class="s2">&#34;&#34;&#34;相容舊 API：載入品質規則配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">return</span> <span class="n">get_config_manager</span><span class="p">()</span><span class="o">.</span><span class="n">load_quality_rules</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl"><span class="k">def</span> <span class="nf">config_scope</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 class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ConfigManager</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">124</span><span class="cl"><span class="s2">    配置範圍 Context Manager
</span></span></span><span class="line"><span class="ln">125</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="s2">    建立一個有明確生命週期的配置環境。
</span></span></span><span class="line"><span class="ln">127</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">128</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">129</span><span class="cl"><span class="s2">        project_root: 專案根目錄
</span></span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">131</span><span class="cl"><span class="s2">    Yields:
</span></span></span><span class="line"><span class="ln">132</span><span class="cl"><span class="s2">        ConfigManager: 配置管理器
</span></span></span><span class="line"><span class="ln">133</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">134</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">135</span><span class="cl"><span class="s2">        with config_scope(&#34;/path/to/project&#34;) as config:
</span></span></span><span class="line"><span class="ln">136</span><span class="cl"><span class="s2">            agents = config.load_agents_config()
</span></span></span><span class="line"><span class="ln">137</span><span class="cl"><span class="s2">            rules = config.load_quality_rules()
</span></span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="s2">        # 離開時快取自動清除
</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">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">(</span><span class="n">project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">yield</span> <span class="n">manager</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"># ===== 測試範例 =====</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="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">147</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="c1"># 建立測試配置</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</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="c1"># 寫入測試配置</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent-1&#34;, &#34;test-agent-2&#34;]}&#39;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">        <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"># 使用 Context Manager</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">with</span> <span class="n">config_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="n">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">162</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">agents</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></span><span class="line"><span class="ln">164</span><span class="cl">            <span class="c1"># 第二次呼叫會使用快取</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">            <span class="n">agents2</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">166</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">agents</span> <span class="ow">is</span> <span class="n">agents2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</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="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">170</span><span class="cl">
</span></span><span class="line"><span class="ln">171</span><span class="cl">    <span class="c1"># 測試隔離範圍</span>
</span></span><span class="line"><span class="ln">172</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">173</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</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="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="c1"># 隔離環境</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">            <span class="n">config</span> <span class="o">=</span> <span class="n">isolated</span><span class="o">.</span><span class="n">load_config</span><span class="p">(</span><span class="s2">&#34;nonexistent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="k">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">180</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">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</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="nb">print</span><span class="p">(</span><span class="s2">&#34;隔離範圍結束&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<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"># 建立配置管理器</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</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="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">cached_scope</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">agents</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">rules</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_quality_rules</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="n">agents2</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">assert</span> <span class="n">agents</span> <span class="ow">is</span> <span class="n">agents2</span>  <span class="c1"># 同一個物件</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></code></pre></div><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="kn">import</span> <span class="nn">pytest</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">import</span> <span class="nn">tempfile</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">@pytest.fixture</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">def</span> <span class="nf">config_manager</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="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</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 class="n">config_dir</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;config&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">config_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="p">(</span><span class="n">config_dir</span> <span class="o">/</span> <span class="s2">&#34;agents.json&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s1">&#39;{&#34;known_agents&#34;: [&#34;test-agent&#34;]}&#39;</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"># 使用隔離範圍</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">manager</span> <span class="o">=</span> <span class="n">ConfigManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">isolated_scope</span><span class="p">(</span><span class="n">tmpdir</span><span class="p">)</span> <span class="k">as</span> <span class="n">isolated</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="k">yield</span> <span class="n">isolated</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">test_load_agents_config</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試載入代理人配置&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">assert</span> <span class="n">config</span><span class="p">[</span><span class="s2">&#34;known_agents&#34;</span><span class="p">]</span> <span class="o">==</span> <span class="p">[</span><span class="s2">&#34;test-agent&#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="k">def</span> <span class="nf">test_cache_works</span><span class="p">(</span><span class="n">config_manager</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試快取功能&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">config1</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">config2</span> <span class="o">=</span> <span class="n">config_manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">assert</span> <span class="n">config1</span> <span class="ow">is</span> <span class="n">config2</span>  <span class="c1"># 同一個物件</span></span></span></code></pre></div><h4 id="相容舊-api">相容舊 API</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"># 如果有既有程式碼使用舊 API</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">config_loader</span> <span class="kn">import</span> <span class="n">load_agents_config</span><span class="p">,</span> <span class="n">config_scope</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">config</span> <span class="o">=</span> <span class="n">load_agents_config</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">with</span> <span class="n">config_scope</span><span class="p">()</span> <span class="k">as</span> <span class="n">manager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>全域快取</th>
          <th>Context Manager</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>簡單性</td>
          <td>最簡單</td>
          <td>需要理解 Context Manager</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>API 相容性</td>
          <td>N/A</td>
          <td>可以相容舊 API</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用-context-manager-管理快取">什麼時候該用 Context Manager 管理快取？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要明確的快取生命週期</li>
<li>測試需要隔離的快取</li>
<li>可能並行存取快取</li>
<li>快取資料量大，需要及時釋放</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>快取需要跨多個函式呼叫共享</li>
<li>快取很小，不需要特別管理</li>
<li>程式很簡單，不需要測試隔離</li>
</ul>
<h2 id="進階與-exitstack-結合">進階：與 ExitStack 結合</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">ExitStack</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">process_with_multiple_caches</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 ExitStack 管理多個快取&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</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="n">config</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">config_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">db_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">database_cache_scope</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">api_cache</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="n">api_cache_scope</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">agents</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">users</span> <span class="o">=</span> <span class="n">db_cache</span><span class="o">.</span><span class="n">get_users</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">api_cache</span><span class="o">.</span><span class="n">fetch_data</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">process</span><span class="p">(</span><span class="n">agents</span><span class="p">,</span> <span class="n">users</span><span class="p">,</span> <span class="n">data</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"># ExitStack 會自動清理所有快取</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>為 <code>ConfigManager</code> 加入 <code>reload_config</code> 方法，強制重新載入配置</li>
<li>實作一個 <code>@cached_config</code> 裝飾器，讓函式自動使用快取範圍</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>加入 TTL（Time To Live）支援，讓快取自動過期</li>
<li>實作一個執行緒安全的 <code>ConfigManager</code>，支援多執行緒存取</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>參考 <code>config_loader.py</code> 的 Fallback Pattern（YAML → JSON → 預設值），用 Context Manager 實現「配置來源優先級」的管理</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/contextlib.html">contextlib 官方文件</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://cachetools.readthedocs.io/">cachetools</a> - 更多快取策略</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/" data-link-title="案例研究：設計模式實戰" data-link-desc="基於 Hook 系統的進階設計模式實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">案例：插件架構設計</a></em></p>
]]></content:encoded></item><item><title>3.5.1 泛型進階</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/</guid><description>&lt;p>入門系列介紹了 &lt;code>TypeVar&lt;/code> 的基本用法。本章深入探討泛型的進階特性，讓你能夠建立型別安全的抽象層。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="typevar-進階">TypeVar 進階&lt;/h2>
&lt;h3 id="bound-參數">bound 參數&lt;/h3>
&lt;p>&lt;code>bound&lt;/code> 限制 TypeVar 必須是某個型別的子型別：&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypeVar&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">Animal&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="k">def&lt;/span> &lt;span class="nf">speak&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">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="k">return&lt;/span> &lt;span class="s2">&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>&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">Dog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Animal&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">def&lt;/span> &lt;span class="nf">speak&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">str&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">return&lt;/span> &lt;span class="s2">&amp;#34;Woof!&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Cat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Animal&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">def&lt;/span> &lt;span class="nf">speak&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">str&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="s2">&amp;#34;Meow!&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>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># T 必須是 Animal 或其子類別&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">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bound&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Animal&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">def&lt;/span> &lt;span class="nf">make_speak&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">animal&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&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">19&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">animal&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">speak&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 型別檢查器知道 animal 有 speak() 方法&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"># 正確使用&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">make_speak&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Dog&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># OK&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">make_speak&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Cat&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># OK&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">make_speak&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;not an animal&amp;#34;&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="bound-vs-限制型別">bound vs 限制型別&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypeVar&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"># 方式一：bound - T 可以是 Animal 或任何子類別&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">T_bound&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_bound&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bound&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Animal&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"># 方式二：限制型別 - T 只能是 Dog 或 Cat，不能是其他 Animal 子類別&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">T_constrained&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_constrained&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dog&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Cat&lt;/span>&lt;span class="p">)&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;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>bound=Animal&lt;/code>&lt;/td>
 &lt;td>T 可以是 Animal 或任何子類別（包括未來新增的）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>TypeVar(&amp;quot;T&amp;quot;, Dog, Cat)&lt;/code>&lt;/td>
 &lt;td>T 只能是明確列出的型別&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="covariant-與-contravariant">covariant 與 contravariant&lt;/h3>
&lt;p>這是泛型最難理解的概念，但對於設計型別安全的 API 非常重要。&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="k">class&lt;/span> &lt;span class="nc">Animal&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">pass&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">class&lt;/span> &lt;span class="nc">Dog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Animal&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">pass&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="c1"># 問題：List[Dog] 是 List[Animal] 的子型別嗎？&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">def&lt;/span> &lt;span class="nf">process_animals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">animals&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Animal&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">animals&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">Animal&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># 如果傳入 list[Dog]，這會破壞型別安全&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="n">dogs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">Dog&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">Dog&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">process_animals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dogs&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 如果允許，dogs 裡面會有 Animal！&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Python 的 &lt;code>list&lt;/code> 是&lt;strong>不變的&lt;/strong>（invariant），所以 &lt;code>list[Dog]&lt;/code> 不是 &lt;code>list[Animal]&lt;/code> 的子型別。&lt;/p>
&lt;h4 id="covariant協變">covariant（協變）&lt;/h4>
&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">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Iterator&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="n">T_co&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_co&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">covariant&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"> 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">class&lt;/span> &lt;span class="nc">ReadOnlyBox&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T_co&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;只能讀取，不能修改&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="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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T_co&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"> 8&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"> 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="k">def&lt;/span> &lt;span class="nf">get&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="n">T_co&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="k">return&lt;/span> &lt;span class="bp">self&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">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"># ReadOnlyBox[Dog] 是 ReadOnlyBox[Animal] 的子型別&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">def&lt;/span> &lt;span class="nf">show_animal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">box&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ReadOnlyBox&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Animal&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">15&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">box&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">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="n">dog_box&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ReadOnlyBox&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dog&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ReadOnlyBox&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Dog&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="n">show_animal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dog_box&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># OK - Dog 是 Animal&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>記憶方式&lt;/strong>：協變 = 輸出方向，子型別可以替代父型別。&lt;/p>
&lt;h4 id="contravariant逆變">contravariant（逆變）&lt;/h4>
&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">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&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="n">T_contra&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_contra&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contravariant&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"> 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">class&lt;/span> &lt;span class="nc">Handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T_contra&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;處理器：只接收值&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="k">def&lt;/span> &lt;span class="nf">handle&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T_contra&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"> 8&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;Handling: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">value&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"> 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"># Handler[Animal] 是 Handler[Dog] 的子型別！（反直覺）&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">def&lt;/span> &lt;span class="nf">setup_dog_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handler&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Handler&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dog&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">12&lt;/span>&lt;span class="cl"> &lt;span class="n">handler&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Dog&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>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="n">animal_handler&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Handler&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Animal&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Handler&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">setup_dog_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">animal_handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># OK - 能處理 Animal 就能處理 Dog&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>記憶方式&lt;/strong>：逆變 = 輸入方向，父型別可以替代子型別。&lt;/p></description><content:encoded><![CDATA[<p>入門系列介紹了 <code>TypeVar</code> 的基本用法。本章深入探討泛型的進階特性，讓你能夠建立型別安全的抽象層。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型</a></li>
</ul>
<h2 id="typevar-進階">TypeVar 進階</h2>
<h3 id="bound-參數">bound 參數</h3>
<p><code>bound</code> 限制 TypeVar 必須是某個型別的子型別：</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">typing</span> <span class="kn">import</span> <span class="n">TypeVar</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">Animal</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">speak</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"> 5</span><span class="cl">        <span class="k">return</span> <span class="s2">&#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="k">class</span> <span class="nc">Dog</span><span class="p">(</span><span class="n">Animal</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">speak</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"> 9</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Woof!&#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="k">class</span> <span class="nc">Cat</span><span class="p">(</span><span class="n">Animal</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">speak</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">13</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Meow!&#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"># T 必須是 Animal 或其子類別</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">Animal</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">make_speak</span><span class="p">(</span><span class="n">animal</span><span class="p">:</span> <span class="n">T</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">19</span><span class="cl">    <span class="k">return</span> <span class="n">animal</span><span class="o">.</span><span class="n">speak</span><span class="p">()</span>  <span class="c1"># 型別檢查器知道 animal 有 speak() 方法</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"># 正確使用</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">make_speak</span><span class="p">(</span><span class="n">Dog</span><span class="p">())</span>  <span class="c1"># OK</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">make_speak</span><span class="p">(</span><span class="n">Cat</span><span class="p">())</span>  <span class="c1"># OK</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">make_speak</span><span class="p">(</span><span class="s2">&#34;not an animal&#34;</span><span class="p">)</span>  <span class="c1"># 型別錯誤</span></span></span></code></pre></div><h3 id="bound-vs-限制型別">bound vs 限制型別</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">typing</span> <span class="kn">import</span> <span class="n">TypeVar</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"># 方式一：bound - T 可以是 Animal 或任何子類別</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">T_bound</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_bound&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">Animal</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"># 方式二：限制型別 - T 只能是 Dog 或 Cat，不能是其他 Animal 子類別</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">T_constrained</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_constrained&#34;</span><span class="p">,</span> <span class="n">Dog</span><span class="p">,</span> <span class="n">Cat</span><span class="p">)</span></span></span></code></pre></div><p>差異：</p>
<table>
  <thead>
      <tr>
          <th>方式</th>
          <th>適用情況</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>bound=Animal</code></td>
          <td>T 可以是 Animal 或任何子類別（包括未來新增的）</td>
      </tr>
      <tr>
          <td><code>TypeVar(&quot;T&quot;, Dog, Cat)</code></td>
          <td>T 只能是明確列出的型別</td>
      </tr>
  </tbody>
</table>
<h3 id="covariant-與-contravariant">covariant 與 contravariant</h3>
<p>這是泛型最難理解的概念，但對於設計型別安全的 API 非常重要。</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="k">class</span> <span class="nc">Animal</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">pass</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">Dog</span><span class="p">(</span><span class="n">Animal</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">pass</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"># 問題：List[Dog] 是 List[Animal] 的子型別嗎？</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">process_animals</span><span class="p">(</span><span class="n">animals</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Animal</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="n">animals</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">Animal</span><span class="p">())</span>  <span class="c1"># 如果傳入 list[Dog]，這會破壞型別安全</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="n">dogs</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Dog</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">Dog</span><span class="p">(),</span> <span class="n">Dog</span><span class="p">()]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">process_animals</span><span class="p">(</span><span class="n">dogs</span><span class="p">)</span>  <span class="c1"># 如果允許，dogs 裡面會有 Animal！</span></span></span></code></pre></div><p>Python 的 <code>list</code> 是<strong>不變的</strong>（invariant），所以 <code>list[Dog]</code> 不是 <code>list[Animal]</code> 的子型別。</p>
<h4 id="covariant協變">covariant（協變）</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Iterator</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="n">T_co</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_co&#34;</span><span class="p">,</span> <span class="n">covariant</span><span class="o">=</span><span class="kc">True</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">class</span> <span class="nc">ReadOnlyBox</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T_co</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_co</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"> 8</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"> 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">get</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T_co</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="bp">self</span><span class="o">.</span><span class="n">_value</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"># ReadOnlyBox[Dog] 是 ReadOnlyBox[Animal] 的子型別</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">def</span> <span class="nf">show_animal</span><span class="p">(</span><span class="n">box</span><span class="p">:</span> <span class="n">ReadOnlyBox</span><span class="p">[</span><span class="n">Animal</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">15</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">box</span><span class="o">.</span><span class="n">get</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="n">dog_box</span><span class="p">:</span> <span class="n">ReadOnlyBox</span><span class="p">[</span><span class="n">Dog</span><span class="p">]</span> <span class="o">=</span> <span class="n">ReadOnlyBox</span><span class="p">(</span><span class="n">Dog</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">show_animal</span><span class="p">(</span><span class="n">dog_box</span><span class="p">)</span>  <span class="c1"># OK - Dog 是 Animal</span></span></span></code></pre></div><p><strong>記憶方式</strong>：協變 = 輸出方向，子型別可以替代父型別。</p>
<h4 id="contravariant逆變">contravariant（逆變）</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</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="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</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">class</span> <span class="nc">Handler</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T_contra</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="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</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"> 8</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Handling: </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"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># Handler[Animal] 是 Handler[Dog] 的子型別！（反直覺）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">setup_dog_handler</span><span class="p">(</span><span class="n">handler</span><span class="p">:</span> <span class="n">Handler</span><span class="p">[</span><span class="n">Dog</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">12</span><span class="cl">    <span class="n">handler</span><span class="o">.</span><span class="n">handle</span><span class="p">(</span><span class="n">Dog</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="n">animal_handler</span><span class="p">:</span> <span class="n">Handler</span><span class="p">[</span><span class="n">Animal</span><span class="p">]</span> <span class="o">=</span> <span class="n">Handler</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">setup_dog_handler</span><span class="p">(</span><span class="n">animal_handler</span><span class="p">)</span>  <span class="c1"># OK - 能處理 Animal 就能處理 Dog</span></span></span></code></pre></div><p><strong>記憶方式</strong>：逆變 = 輸入方向，父型別可以替代子型別。</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="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"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># Callable 的參數是逆變的，返回值是協變的</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Callable[[Animal], Dog] 是 Callable[[Dog], Animal] 的子型別</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">process</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="n">Dog</span><span class="p">],</span> <span class="n">Animal</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"> 7</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="n">Dog</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="n">result</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">def</span> <span class="nf">any_animal_to_dog</span><span class="p">(</span><span class="n">animal</span><span class="p">:</span> <span class="n">Animal</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Dog</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="n">Dog</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="n">process</span><span class="p">(</span><span class="n">any_animal_to_dog</span><span class="p">)</span>  <span class="c1"># OK</span></span></span></code></pre></div><h2 id="generic-類別">Generic 類別</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Optional</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="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#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">class</span> <span class="nc">Stack</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"> 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></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="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="bp">self</span><span class="o">.</span><span class="n">_items</span><span class="p">:</span> <span class="nb">list</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">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">push</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">item</span><span class="p">:</span> <span class="n">T</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">12</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_items</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</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">pop</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_items</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="bp">self</span><span class="o">.</span><span class="n">_items</span><span class="o">.</span><span class="n">pop</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="kc">None</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">peek</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">T</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="bp">self</span><span class="o">.</span><span class="n">_items</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="bp">self</span><span class="o">.</span><span class="n">_items</span><span class="p">[</span><span class="o">-</span><span class="mi">1</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="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="c1"># 使用</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">int_stack</span><span class="p">:</span> <span class="n">Stack</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="n">Stack</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="n">int_stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">int_stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="n">int_stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="s2">&#34;three&#34;</span><span class="p">)</span>  <span class="c1"># 型別錯誤！</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="n">str_stack</span><span class="p">:</span> <span class="n">Stack</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="n">Stack</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="n">str_stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="s2">&#34;hello&#34;</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</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="n">K</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;K&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">V</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;V&#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="k">class</span> <span class="nc">Pair</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">K</span><span class="p">,</span> <span class="n">V</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></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">key</span><span class="p">:</span> <span class="n">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">V</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">10</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">11</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">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">swap</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;Pair[V, K]&#34;</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">Pair</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">key</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">pair</span><span class="p">:</span> <span class="n">Pair</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="n">Pair</span><span class="p">(</span><span class="s2">&#34;age&#34;</span><span class="p">,</span> <span class="mi">25</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">swapped</span><span class="p">:</span> <span class="n">Pair</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="o">=</span> <span class="n">pair</span><span class="o">.</span><span class="n">swap</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</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="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#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">class</span> <span class="nc">Container</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"> 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">value</span><span class="p">:</span> <span class="n">T</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"> 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></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="k">class</span> <span class="nc">Box</span><span class="p">(</span><span class="n">Container</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">unwrap</span><span class="p">(</span><span class="bp">self</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">12</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</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">class</span> <span class="nc">StringBox</span><span class="p">(</span><span class="n">Container</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">def</span> <span class="nf">upper</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">17</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span></span></span></code></pre></div><h2 id="protocol-與結構化子型別">Protocol 與結構化子型別</h2>
<h3 id="什麼是-protocol">什麼是 Protocol？</h3>
<p>Protocol 定義「介面」，任何實現該介面的類別都被視為符合該 Protocol，無需明確繼承。</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">typing</span> <span class="kn">import</span> <span class="n">Protocol</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">Drawable</span><span class="p">(</span><span class="n">Protocol</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">draw</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"> 5</span><span class="cl">        <span class="o">...</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"># 這個類別沒有繼承 Drawable，但符合 Protocol</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">Circle</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="nf">draw</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">10</span><span class="cl">        <span class="k">return</span> <span class="s2">&#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="k">class</span> <span class="nc">Square</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">draw</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">14</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;□&#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="k">def</span> <span class="nf">render</span><span class="p">(</span><span class="n">shape</span><span class="p">:</span> <span class="n">Drawable</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">17</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">shape</span><span class="o">.</span><span class="n">draw</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="n">render</span><span class="p">(</span><span class="n">Circle</span><span class="p">())</span>  <span class="c1"># OK - Circle 有 draw() 方法</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">render</span><span class="p">(</span><span class="n">Square</span><span class="p">())</span>  <span class="c1"># OK - Square 有 draw() 方法</span></span></span></code></pre></div><h3 id="protocol-vs-abc">Protocol vs ABC</h3>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>Protocol</th>
          <th>ABC</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>繼承要求</td>
          <td>不需要</td>
          <td>需要</td>
      </tr>
      <tr>
          <td>型別檢查</td>
          <td>結構化（duck typing）</td>
          <td>名義上（nominal）</td>
      </tr>
      <tr>
          <td>執行期檢查</td>
          <td>需要 <code>runtime_checkable</code></td>
          <td>內建</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>第三方類別、鬆散耦合</td>
          <td>自己控制的類別層級</td>
      </tr>
  </tbody>
</table>





<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">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</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">Protocol</span><span class="p">,</span> <span class="n">runtime_checkable</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"># ABC 方式</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">DrawableABC</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">draw</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"> 8</span><span class="cl">        <span class="o">...</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">CircleABC</span><span class="p">(</span><span class="n">DrawableABC</span><span class="p">):</span>  <span class="c1"># 必須繼承</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">draw</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">12</span><span class="cl">        <span class="k">return</span> <span class="s2">&#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"># Protocol 方式</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">class</span> <span class="nc">DrawableProtocol</span><span class="p">(</span><span class="n">Protocol</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="nf">draw</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">18</span><span class="cl">        <span class="o">...</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">class</span> <span class="nc">CircleProtocol</span><span class="p">:</span>  <span class="c1"># 不需要繼承</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">draw</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">22</span><span class="cl">        <span class="k">return</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="c1"># 執行期檢查</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="nb">isinstance</span><span class="p">(</span><span class="n">CircleProtocol</span><span class="p">(),</span> <span class="n">DrawableProtocol</span><span class="p">))</span>  <span class="c1"># True</span></span></span></code></pre></div><h3 id="何時選擇-protocol">何時選擇 Protocol？</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"># 使用 Protocol 的情況：</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">class</span> <span class="nc">JSONSerializable</span><span class="p">(</span><span class="n">Protocol</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">to_json</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"> 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"># 第三方類別可能已經有 to_json()，不需要修改它們</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="k">class</span> <span class="nc">EventHandler</span><span class="p">(</span><span class="n">Protocol</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">on_event</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</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">13</span><span class="cl">        <span class="o">...</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"># 任何有 on_event 方法的類別或函式都可以使用</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"># 3. 鬆散耦合</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">class</span> <span class="nc">Closeable</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">close</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">20</span><span class="cl">        <span class="o">...</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">cleanup</span><span class="p">(</span><span class="n">resource</span><span class="p">:</span> <span class="n">Closeable</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">23</span><span class="cl">    <span class="n">resource</span><span class="o">.</span><span class="n">close</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"># 檔案、連接、任何有 close() 的東西都可以用</span></span></span></code></pre></div><h3 id="帶有屬性的-protocol">帶有屬性的 Protocol</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">typing</span> <span class="kn">import</span> <span class="n">Protocol</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">Named</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</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="k">class</span> <span class="nc">Person</span><span class="p">:</span>
</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">name</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"> 8</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"> 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">Company</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Acme Corp&#34;</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">greet</span><span class="p">(</span><span class="n">entity</span><span class="p">:</span> <span class="n">Named</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">14</span><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">entity</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">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">greet</span><span class="p">(</span><span class="n">Person</span><span class="p">(</span><span class="s2">&#34;Alice&#34;</span><span class="p">))</span>  <span class="c1"># OK</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">greet</span><span class="p">(</span><span class="n">Company</span><span class="p">())</span>        <span class="c1"># OK</span></span></span></code></pre></div><h2 id="實際範例型別安全的-repository-介面">實際範例：型別安全的 Repository 介面</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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</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"># 定義實體必須有 id 屬性</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">HasId</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</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"># 泛型 Repository 介面</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">HasId</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">class</span> <span class="nc">Repository</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">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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">15</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="nb">id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據 ID 取得實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="o">...</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">@abstractmethod</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</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">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;儲存實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="o">...</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">@abstractmethod</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="nb">int</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;刪除實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">find_all</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">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;取得所有實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="o">...</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="k">class</span> <span class="nc">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</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="nb">id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">name</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">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="nb">id</span>
</span></span><span class="line"><span class="ln">38</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">39</span><span class="cl">
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="k">class</span> <span class="nc">InMemoryUserRepository</span><span class="p">(</span><span class="n">Repository</span><span class="p">[</span><span class="n">User</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;&#34;&#34;記憶體中的 User Repository&#34;&#34;&#34;</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="fm">__init__</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">44</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="n">User</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="bp">self</span><span class="o">.</span><span class="n">_next_id</span> <span class="o">=</span> <span class="mi">1</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">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">User</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="nb">id</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="k">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</span><span class="p">:</span> <span class="n">User</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</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">entity</span><span class="o">.</span><span class="n">id</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">entity</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_next_id</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_next_id</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">[</span><span class="n">entity</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">entity</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">return</span> <span class="n">entity</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">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="nb">int</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">58</span><span class="cl">        <span class="k">if</span> <span class="nb">id</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">[</span><span class="nb">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">return</span> <span class="kc">False</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">def</span> <span class="nf">find_all</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">User</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="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="o">.</span><span class="n">values</span><span class="p">())</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="c1"># 使用</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="k">def</span> <span class="nf">process_user</span><span class="p">(</span><span class="n">repo</span><span class="p">:</span> <span class="n">Repository</span><span class="p">[</span><span class="n">User</span><span class="p">],</span> <span class="n">user_id</span><span class="p">:</span> <span class="nb">int</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">68</span><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="k">if</span> <span class="n">user</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="sa">f</span><span class="s2">&#34;Found user: </span><span class="si">{</span><span class="n">user</span><span class="o">.</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">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="c1"># 型別安全：不能把 UserRepository 傳給需要 Repository[Product] 的函式</span></span></span></code></pre></div><h2 id="常見錯誤">常見錯誤</h2>
<h3 id="1-忘記-typevar-的名稱參數">1. 忘記 TypeVar 的名稱參數</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">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">()</span>  <span class="c1"># TypeError</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">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="2-在類別方法中重複定義-typevar">2. 在類別方法中重複定義 TypeVar</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="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#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="k">class</span> <span class="nc">Box</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"> 4</span><span class="cl">    <span class="c1"># 錯誤：方法內重新定義 T</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="bp">self</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="n">T</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"> 6</span><span class="cl">        <span class="n">T2</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T2&#34;</span><span class="p">)</span>  <span class="c1"># 這是不同的 TypeVar！</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="o">...</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"># 正確：直接使用類別的 T</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">transform</span><span class="p">(</span><span class="bp">self</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="n">T</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">11</span><span class="cl">        <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="p">)</span></span></span></code></pre></div><h3 id="3-誤用-covariantcontravariant">3. 誤用 covariant/contravariant</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="n">T_co</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_co&#34;</span><span class="p">,</span> <span class="n">covariant</span><span class="o">=</span><span class="kc">True</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="k">class</span> <span class="nc">MutableBox</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</span><span class="n">T_co</span><span class="p">]):</span>  <span class="c1"># 錯誤！</span>
</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">value</span><span class="p">:</span> <span class="n">T_co</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">5</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">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="nf">set</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_co</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>  <span class="c1"># 協變型別不能用在參數位置</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">_value</span> <span class="o">=</span> <span class="n">value</span></span></span></code></pre></div><h2 id="小結">小結</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>bound</code></td>
          <td>限制 TypeVar 必須是某型別的子型別</td>
      </tr>
      <tr>
          <td><code>covariant</code></td>
          <td>只讀容器，子型別可替代父型別</td>
      </tr>
      <tr>
          <td><code>contravariant</code></td>
          <td>只寫容器，父型別可替代子型別</td>
      </tr>
      <tr>
          <td><code>Generic[T]</code></td>
          <td>建立泛型類別</td>
      </tr>
      <tr>
          <td><code>Protocol</code></td>
          <td>結構化子型別，不需繼承</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>list</code> 是不變的而不是協變的？</li>
<li>什麼情況下應該用 Protocol 而不是 ABC？</li>
<li>如何為一個既可讀又可寫的容器設計型別安全的介面？</li>
</ol>
<hr>
<p>下一章：<a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構</a></p>
]]></content:encoded></item><item><title>案例：插件架構設計</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.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="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="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"> 5&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"> 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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &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 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">10&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">11&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">12&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">13&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">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="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">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">check_naming_convention&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">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="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">def&lt;/span> &lt;span class="nf">check_lib_imports&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">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">22&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">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&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">check_output_format&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="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">26&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">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>&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">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">30&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">31&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 具體驗證邏輯 ...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>所有邏輯集中管理，容易找到相關程式碼&lt;/li>
&lt;li>沒有額外的抽象層，直接明瞭&lt;/li>
&lt;li>對小型專案來說足夠使用&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要擴展時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>第三方無法新增自己的檢查項&lt;/strong>：想加入「Docstring 檢查」必須修改 &lt;code>HookValidator&lt;/code>&lt;/li>
&lt;li>&lt;strong>修改需要改動核心程式碼&lt;/strong>：每新增一個檢查都要改 &lt;code>validate_hook&lt;/code> 方法&lt;/li>
&lt;li>&lt;strong>違反開放封閉原則&lt;/strong>：對擴展不開放，對修改不封閉&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>定義清晰的插件介面&lt;/strong>：用 Protocol 描述檢查項該有的行為&lt;/li>
&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;h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面&lt;/h4>
&lt;p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：&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">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&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">runtime_checkable&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="nd">@dataclass&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">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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation issue description&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">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"> 9&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">10&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">11&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">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="nd">@dataclass&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">class&lt;/span> &lt;span class="nc">CheckContext&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;&amp;#34;&amp;#34;Context passed to each check&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">hook_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&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">content&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">project_root&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Path&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="nd">@runtime_checkable&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">class&lt;/span> &lt;span class="nc">ValidationCheck&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&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;&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="s2"> Protocol for validation checks
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class implementing this protocol can be used as a validation check.
&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"> No inheritance required - just implement the methods.
&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;&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="nd">@property&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">def&lt;/span> &lt;span class="nf">name&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">str&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="s2">&amp;#34;&amp;#34;&amp;#34;Check name for identification&amp;#34;&amp;#34;&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="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&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">35&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">description&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">str&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="s2">&amp;#34;&amp;#34;&amp;#34;Human-readable description&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="o">...&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="k">def&lt;/span> &lt;span class="nf">check&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">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">CheckContext&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">40&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">41&lt;/span>&lt;span class="cl">&lt;span class="s2"> Execute the validation check
&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"> Args:
&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"> context: Check context with file info
&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">
&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"> Returns:
&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"> List of validation issues found
&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="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？&lt;/h5>
&lt;ul>
&lt;li>Protocol 是「鴨子型別」的靜態版本&lt;/li>
&lt;li>不強制繼承，現有類別只要有對應方法就能用&lt;/li>
&lt;li>&lt;code>@runtime_checkable&lt;/code> 讓我們可以用 &lt;code>isinstance()&lt;/code> 檢查&lt;/li>
&lt;/ul>
&lt;h4 id="步驟-2實作插件註冊機制">步驟 2：實作插件註冊機制&lt;/h4>
&lt;p>註冊機制提供兩種方式：裝飾器和明確註冊：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Protocol 和註冊機制實現可擴展的插件系統。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">2.2 自動註冊機制</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計</a></li>
</ul>
<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="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"> 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 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"># 執行各項檢查 - 硬編碼的驗證規則</span>
</span></span><span class="line"><span class="ln"> 9</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">10</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">11</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">12</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">13</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">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</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">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">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">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="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">def</span> <span class="nf">check_lib_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="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">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;檢查共用模組導入&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># ... 具體驗證邏輯 ...</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">check_output_format</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">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="c1"># ... 具體驗證邏輯 ...</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">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">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="c1"># ... 具體驗證邏輯 ...</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li>所有邏輯集中管理，容易找到相關程式碼</li>
<li>沒有額外的抽象層，直接明瞭</li>
<li>對小型專案來說足夠使用</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要擴展時：</p>
<ul>
<li><strong>第三方無法新增自己的檢查項</strong>：想加入「Docstring 檢查」必須修改 <code>HookValidator</code></li>
<li><strong>修改需要改動核心程式碼</strong>：每新增一個檢查都要改 <code>validate_hook</code> 方法</li>
<li><strong>違反開放封閉原則</strong>：對擴展不開放，對修改不封閉</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>定義清晰的插件介面</strong>：用 Protocol 描述檢查項該有的行為</li>
<li><strong>支援第三方擴展</strong>：外部套件可以新增檢查項</li>
<li><strong>插件可以獨立測試</strong>：每個檢查項是獨立的單元</li>
<li><strong>支援插件的啟用/停用</strong>：可以動態控制要執行哪些檢查</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1用-protocol-定義插件介面">步驟 1：用 Protocol 定義插件介面</h4>
<p>Protocol 讓我們定義「檢查項該有的行為」，而不強制繼承關係：</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">Protocol</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">runtime_checkable</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">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</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">10</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">11</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">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">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">content</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">project_root</span><span class="p">:</span> <span class="n">Path</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="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</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">    Protocol for validation checks
</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">    Any class implementing this protocol can be used as a validation check.
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    No inheritance required - just implement the methods.
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    &#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="nd">@property</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="o">...</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="nd">@property</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">36</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="o">...</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">40</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">        Execute the validation check
</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">        Args:
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">            context: Check context with file info
</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">        Returns:
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            List of validation issues found
</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="o">...</span></span></span></code></pre></div><h5 id="為什麼用-protocol-而不是-abc">為什麼用 Protocol 而不是 ABC？</h5>
<ul>
<li>Protocol 是「鴨子型別」的靜態版本</li>
<li>不強制繼承，現有類別只要有對應方法就能用</li>
<li><code>@runtime_checkable</code> 讓我們可以用 <code>isinstance()</code> 檢查</li>
</ul>
<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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeVar</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"># Type variable for check classes</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</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">CheckRegistry</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">    Registry for validation checks
</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">    Supports both decorator registration and explicit registration.
</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></span><span class="line"><span class="ln">13</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="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</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">_disabled</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="nb">set</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">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</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 class="s2">        Register a check instance
</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">        Args:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">            check: Check instance to register
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            The same check instance (for chaining)
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            registry.register(NamingCheck())
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</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="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="n">check</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</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="s2">        Decorator for registering check classes
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">            name: Optional custom name (default: class name)
</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">        Returns:
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            Class decorator
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            @registry.check()
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">            class MyCheck:
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">                ...
</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">def</span> <span class="nf">decorator</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="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</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">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</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="k">def</span> <span class="nf">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">71</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</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">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><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">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</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="c1"># Global registry instance</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</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>裝飾器 <code>@registry.check()</code></td>
          <td>類別定義時</td>
          <td>簡潔、宣告式</td>
          <td>模組載入時就註冊</td>
      </tr>
      <tr>
          <td>明確註冊 <code>registry.register()</code></td>
          <td>執行時</td>
          <td>更靈活、可延遲</td>
          <td>較囉嗦</td>
      </tr>
  </tbody>
</table>
<h4 id="步驟-3支援-entry_points-自動發現">步驟 3：支援 entry_points 自動發現</h4>
<p><code>entry_points</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 class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</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">Iterator</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">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</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">    Discover checks from installed packages via entry_points
</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">    Args:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        group: Entry point group name
</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">    Yields:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Discovered check instances
</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">    Example:
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        for check in discover_checks():
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            registry.register(check)
</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="c1"># Python 3.10+ API</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># Python 3.9 compatibility</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</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">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="c1"># Load the check class/factory</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</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"># Create instance</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</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"># Validate it implements the protocol</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#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></span><span class="line"><span class="ln">49</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">50</span><span class="cl">            <span class="kn">import</span> <span class="nn">warnings</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </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><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">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</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">54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="s2">    Load all external checks into a registry
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        registry: Target registry
</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">    Returns:
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="s2">        Number of checks loaded
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">63</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">64</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</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">67</span><span class="cl">    <span class="k">return</span> <span class="n">count</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="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></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">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  8</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">  9</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"> 10</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"> 11</span><span class="cl">    <span class="n">checks_run</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">=</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"> 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">__post_init__</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"> 14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate compliance status&#34;&#34;&#34;</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">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"> 16</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"> 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="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</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="s2">    Plugin-based hook validator
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    Uses registered checks to validate hook files.
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</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"> 29</span><span class="cl">        <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"> 30</span><span class="cl">        <span class="n">auto_discover</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 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"> 32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 33</span><span class="cl"><span class="s2">        Initialize validator
</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">        Args:
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">            registry: Check registry (default: global registry)
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">            project_root: Project root directory
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">            auto_discover: Whether to auto-discover external checks
</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="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</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">project_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</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"> 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="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</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="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"> 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="s2">        Validate a single hook file
</span></span></span><span class="line"><span class="ln"> 51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="s2">            hook_path: Path to hook file
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">            Validation result
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</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">_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"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="c1"># Check file exists</span>
</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">path</span><span class="o">.</span><span class="n">exists</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="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 63</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"> 64</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"> 65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</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"> 67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </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"> 68</span><span class="cl">                    <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 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="c1"># Read content</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</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"> 75</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"> 76</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"> 77</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"> 78</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"> 79</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</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"> 81</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </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"> 82</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <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="c1"># Build context</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</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></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="c1"># Run all enabled checks</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">all_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="p">[]</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">checks_run</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">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</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">103</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</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">106</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </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">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">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">111</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">112</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</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">_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">117</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</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">119</span><span class="cl">        <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></span><span class="line"><span class="ln">120</span><span class="cl">            <span class="k">return</span> <span class="n">p</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</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></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">Plugin-based Hook Validator
</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">A validation system using Protocol and registry pattern,
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">allowing third-party extensions via entry_points.
</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></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</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">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">re</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">warnings</span>
</span></span><span class="line"><span class="ln"> 15</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"> 16</span><span class="cl"><span class="kn">from</span> <span class="nn">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</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">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="n">Dict</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="n">Iterator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="n">List</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">Optional</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">Type</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl"><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"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"># Data Classes</span>
</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></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 35</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"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation issue description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</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"> 38</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"> 39</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"> 40</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"> 41</span><span class="cl">
</span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="k">class</span> <span class="nc">CheckContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Context passed to each check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="n">hook_path</span><span class="p">:</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">content</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">project_root</span><span class="p">:</span> <span class="n">Path</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 50</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"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 52</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"> 53</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"> 54</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"> 55</span><span class="cl">    <span class="n">checks_run</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">=</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"> 56</span><span class="cl">
</span></span><span class="line"><span class="ln"> 57</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 class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 58</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"> 59</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"> 60</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="c1"># Protocol Definition</span>
</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></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Protocol for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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"> 72</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check name for identification&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="o">...</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="nd">@property</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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"> 77</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Human-readable description&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="o">...</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Execute the validation check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="o">...</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="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="c1"># Registry</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="c1"># ============================================================</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="n">C</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;C&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">ValidationCheck</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">CheckRegistry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry for validation checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</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="o">-&gt;</span> <span class="kc">None</span><span class="p">:</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">_checks</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</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="nb">set</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</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">check</span><span class="p">:</span> <span class="n">ValidationCheck</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Register a check instance&#34;&#34;&#34;</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="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Check must implement ValidationCheck protocol, &#34;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">check</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">check</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="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">Callable</span><span class="p">[[</span><span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]],</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Decorator for registering check classes&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="k">def</span> <span class="nf">decorator</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="n">C</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Type</span><span class="p">[</span><span class="n">C</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="p">[</span><span class="n">name</span> <span class="ow">or</span> <span class="n">instance</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">instance</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</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">get_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get a check by name&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="k">def</span> <span class="nf">get_enabled_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checks&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="n">check</span> <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="k">def</span> <span class="nf">enable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">127</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">discard</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl">    <span class="k">def</span> <span class="nf">disable</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">131</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_disabled</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">)</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">def</span> <span class="nf">list_checks</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all registered check names&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_checks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># Global registry</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl"><span class="n">default_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</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 class="c1"># Entry Points Discovery</span>
</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">discover_checks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="n">group</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;hook_validator.checks&#34;</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">ValidationCheck</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Discover checks from installed packages via entry_points&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">group</span><span class="p">,</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="k">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">            <span class="n">check_factory</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">check</span> <span class="o">=</span> <span class="n">check_factory</span><span class="p">()</span> <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">check_factory</span><span class="p">)</span> <span class="k">else</span> <span class="n">check_factory</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">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">check</span><span class="p">,</span> <span class="n">ValidationCheck</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="k">yield</span> <span class="n">check</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">                <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;Entry point </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> does not implement &#34;</span>
</span></span><span class="line"><span class="ln">164</span><span class="cl">                    <span class="sa">f</span><span class="s2">&#34;ValidationCheck protocol&#34;</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 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">167</span><span class="cl">            <span class="n">warnings</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load check </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </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><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">load_external_checks</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="n">CheckRegistry</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">170</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load all external checks into a registry&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">171</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">172</span><span class="cl">    <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="n">discover_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">        <span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">174</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">175</span><span class="cl">    <span class="k">return</span> <span class="n">count</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="c1"># Built-in Checks</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></span><span class="line"><span class="ln">181</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl"><span class="k">class</span> <span class="nc">NamingConventionCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook file naming convention&#34;&#34;&#34;</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="n">VALID_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">186</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/03-design-patterns/case-studies/plugin-architecture/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="p">]</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="nd">@property</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">191</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;naming_convention&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">195</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook files follow naming conventions&#34;</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</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">context</span><span class="p">:</span> <span class="n">CheckContext</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">198</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="p">[]</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">context</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">200</span><span class="cl">
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="n">valid</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">202</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">pattern</span><span class="p">,</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">203</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">VALID_PATTERNS</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">
</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="n">valid</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></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">209</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">210</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Invalid file name: </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">211</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake_case or kebab-case naming&#34;</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">214</span><span class="cl">
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">return</span> <span class="n">issues</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="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl"><span class="k">class</span> <span class="nc">LibImportCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that hooks import required libraries&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</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">222</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">223</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">224</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">
</span></span><span class="line"><span class="ln">226</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">228</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;lib_import&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">232</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks import hook_io module&#34;</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">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">235</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="p">[]</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">
</span></span><span class="line"><span class="ln">237</span><span class="cl">        <span class="n">has_import</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">238</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">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">239</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">HOOK_IO_PATTERNS</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">if</span> <span class="ow">not</span> <span class="n">has_import</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">243</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">244</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">245</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">246</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing hook_io import&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: from hook_io import read_hook_input, write_hook_output&#34;</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">            <span class="p">)</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">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl"><span class="k">class</span> <span class="nc">OutputFormatCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check hook output format&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">GOOD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">258</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">259</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">260</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">261</span><span class="cl">    <span class="p">]</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="n">BAD_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">264</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">265</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">266</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">
</span></span><span class="line"><span class="ln">268</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">270</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;output_format&#34;</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">274</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hooks use proper output functions&#34;</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="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">277</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="p">[]</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">
</span></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="n">has_bad</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">280</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">context</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</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">BAD_PATTERNS</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <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="k">if</span> <span class="n">has_bad</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">285</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">286</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">287</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">288</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Using print(json.dumps(...)) instead of write_hook_output()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use write_hook_output() for proper output formatting&#34;</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">            <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="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">
</span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl"><span class="k">class</span> <span class="nc">TestExistsCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check that corresponding test file exists&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">301</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;test_exists&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">305</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a corresponding test file&#34;</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">
</span></span><span class="line"><span class="ln">307</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">context</span><span class="p">:</span> <span class="n">CheckContext</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">308</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="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="n">hook_name</span> <span class="o">=</span> <span class="n">context</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">311</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">312</span><span class="cl">
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="n">possible_paths</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="n">context</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">315</span><span class="cl">            <span class="n">context</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">316</span><span class="cl">        <span class="p">]</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="k">if</span> <span class="ow">not</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_paths</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">319</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">320</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">321</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">322</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;No test file found: </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">323</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Create test at .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">324</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl"><span class="c1"># Validator</span>
</span></span><span class="line"><span class="ln">331</span><span class="cl"><span class="c1"># ============================================================</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="k">class</span> <span class="nc">PluginValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Plugin-based hook validator&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">        <span class="n">registry</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">CheckRegistry</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">339</span><span class="cl">        <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">340</span><span class="cl">        <span class="n">auto_discover</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">341</span><span class="cl">    <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">342</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">registry</span> <span class="o">=</span> <span class="n">registry</span> <span class="ow">or</span> <span class="n">default_registry</span>
</span></span><span class="line"><span class="ln">343</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></span><span class="line"><span class="ln">344</span><span class="cl">            <span class="n">project_root</span> <span class="ow">or</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">345</span><span class="cl">        <span class="p">)</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="k">if</span> <span class="n">auto_discover</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">            <span class="n">load_external_checks</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="p">)</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="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">351</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">352</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">_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">353</span><span class="cl">
</span></span><span class="line"><span class="ln">354</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">355</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">356</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">357</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">358</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">359</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">360</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </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">361</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">            <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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">366</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">367</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">368</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">369</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">370</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">371</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">372</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">373</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read hook file: </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">374</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">375</span><span class="cl">                <span class="p">]</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">            <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="n">context</span> <span class="o">=</span> <span class="n">CheckContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">hook_path</span><span class="o">=</span><span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">            <span class="n">project_root</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">project_root</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">
</span></span><span class="line"><span class="ln">384</span><span class="cl">        <span class="n">all_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="p">[]</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">        <span class="n">checks_run</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">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">386</span><span class="cl">
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="k">for</span> <span class="n">check</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">get_enabled_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">check</span><span class="o">.</span><span class="n">check</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">issues</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">                <span class="n">checks_run</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">392</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">393</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">395</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">396</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Check </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </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">397</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">398</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</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">401</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">402</span><span class="cl">            <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">403</span><span class="cl">            <span class="n">checks_run</span><span class="o">=</span><span class="n">checks_run</span>
</span></span><span class="line"><span class="ln">404</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">405</span><span class="cl">
</span></span><span class="line"><span class="ln">406</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">407</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate all hook files in a directory&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">408</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">409</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">410</span><span class="cl">
</span></span><span class="line"><span class="ln">411</span><span class="cl">        <span class="n">hooks_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">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">412</span><span class="cl">
</span></span><span class="line"><span class="ln">413</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">414</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">415</span><span class="cl">                <span class="n">ValidationResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">416</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_path</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">417</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">418</span><span class="cl">                        <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">419</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">420</span><span class="cl">                            <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hooks directory not found: </span><span class="si">{</span><span class="n">hooks_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">421</span><span class="cl">                        <span class="p">)</span>
</span></span><span class="line"><span class="ln">422</span><span class="cl">                    <span class="p">]</span>
</span></span><span class="line"><span class="ln">423</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">424</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">425</span><span class="cl">
</span></span><span class="line"><span class="ln">426</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">427</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_path</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">428</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">429</span><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="ln">430</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">431</span><span class="cl">
</span></span><span class="line"><span class="ln">432</span><span class="cl">        <span class="k">return</span> <span class="n">results</span>
</span></span><span class="line"><span class="ln">433</span><span class="cl">
</span></span><span class="line"><span class="ln">434</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">435</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">436</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">437</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">438</span><span class="cl">
</span></span><span class="line"><span class="ln">439</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">440</span><span class="cl"><span class="c1"># Demo</span>
</span></span><span class="line"><span class="ln">441</span><span class="cl"><span class="c1"># ============================================================</span>
</span></span><span class="line"><span class="ln">442</span><span class="cl">
</span></span><span class="line"><span class="ln">443</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">444</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">445</span><span class="cl">
</span></span><span class="line"><span class="ln">446</span><span class="cl">    <span class="c1"># Show registered checks</span>
</span></span><span class="line"><span class="ln">447</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Registered checks:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">448</span><span class="cl">    <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">449</span><span class="cl">        <span class="n">check</span> <span class="o">=</span> <span class="n">default_registry</span><span class="o">.</span><span class="n">get_check</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">450</span><span class="cl">        <span class="k">if</span> <span class="n">check</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">451</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">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">check</span><span class="o">.</span><span class="n">description</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">452</span><span class="cl">
</span></span><span class="line"><span class="ln">453</span><span class="cl">    <span class="c1"># Create test hook</span>
</span></span><span class="line"><span class="ln">454</span><span class="cl">    <span class="k">with</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">455</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="n">tmpdir</span><span class="p">)</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;hooks&#34;</span>
</span></span><span class="line"><span class="ln">456</span><span class="cl">        <span class="n">hooks_dir</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">457</span><span class="cl">
</span></span><span class="line"><span class="ln">458</span><span class="cl">        <span class="c1"># Create a hook file</span>
</span></span><span class="line"><span class="ln">459</span><span class="cl">        <span class="n">hook_file</span> <span class="o">=</span> <span class="n">hooks_dir</span> <span class="o">/</span> <span class="s2">&#34;check-permissions.py&#34;</span>
</span></span><span class="line"><span class="ln">460</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">461</span><span class="cl"><span class="s2">#!/usr/bin/env python3
</span></span></span><span class="line"><span class="ln">462</span><span class="cl"><span class="s2">from hook_io import read_hook_input, write_hook_output
</span></span></span><span class="line"><span class="ln">463</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">464</span><span class="cl"><span class="s2">def main():
</span></span></span><span class="line"><span class="ln">465</span><span class="cl"><span class="s2">    data = read_hook_input()
</span></span></span><span class="line"><span class="ln">466</span><span class="cl"><span class="s2">    write_hook_output({&#34;decision&#34;: &#34;allow&#34;})
</span></span></span><span class="line"><span class="ln">467</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">468</span><span class="cl"><span class="s2">if __name__ == &#34;__main__&#34;:
</span></span></span><span class="line"><span class="ln">469</span><span class="cl"><span class="s2">    main()
</span></span></span><span class="line"><span class="ln">470</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">471</span><span class="cl">
</span></span><span class="line"><span class="ln">472</span><span class="cl">        <span class="c1"># Validate</span>
</span></span><span class="line"><span class="ln">473</span><span class="cl">        <span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">project_root</span><span class="o">=</span><span class="n">tmpdir</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">474</span><span class="cl">        <span class="n">result</span> <span class="o">=</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">475</span><span class="cl">
</span></span><span class="line"><span class="ln">476</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">Validation result for </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">hook_path</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">477</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Compliant: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_compliant</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">478</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Checks run: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">checks_run</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">479</span><span class="cl">        <span class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">480</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">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="建立插件">建立插件</h4>
<p>建立自訂檢查項只需要實作 Protocol 定義的介面：</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">typing</span> <span class="kn">import</span> <span class="n">List</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">re</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">DocstringCheck</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">    Check that hooks have proper docstrings
</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">    A custom check that can be added to the validator.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    No inheritance required - just implement the protocol.
</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="nd">@property</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">14</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#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">@property</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">18</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#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="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">21</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="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"># Check for module docstring</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">docstring_pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;^(#!/.*\n)?[\s]*[&#34;</span><span class="se">\&#39;</span><span class="s1">\]</span><span class="si">{3}</span><span class="s1">&#39;</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="ow">not</span> <span class="n">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">docstring_pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</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">28</span><span class="cl">                <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">29</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">30</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing module docstring&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s1">&#39;Add a docstring at the top: &#34;&#34;&#34;Description of hook&#34;&#34;&#34;&#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 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">return</span> <span class="n">issues</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">class</span> <span class="nc">SecurityCheck</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">    Check for potential security issues
</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">    Looks for dangerous patterns like eval(), exec(), subprocess with shell=True
</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></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="n">DANGEROUS_PATTERNS</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="sa">r</span><span class="s1">&#39;\beval\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;eval() is dangerous, consider alternatives&#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="sa">r</span><span class="s1">&#39;\bexec\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;exec() is dangerous, consider alternatives&#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="sa">r</span><span class="s1">&#39;subprocess.*shell\s*=\s*True&#39;</span><span class="p">,</span> <span class="s2">&#34;shell=True is a security risk&#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="sa">r</span><span class="s1">&#39;os\.system\s*\(&#39;</span><span class="p">,</span> <span class="s2">&#34;os.system() is insecure, use subprocess instead&#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><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="nd">@property</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">53</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;security&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">57</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for potential security vulnerabilities&#34;</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="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">60</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="p">[]</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">for</span> <span class="n">pattern</span><span class="p">,</span> <span class="n">message</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">DANGEROUS_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">63</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="n">pattern</span><span class="p">,</span> <span class="n">context</span><span class="o">.</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">64</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">65</span><span class="cl">                    <span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">66</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">67</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="n">message</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">                    <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="k">return</span> <span class="n">issues</span></span></span></code></pre></div><h4 id="註冊插件">註冊插件</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"># Method 1: Using decorator (at class definition time)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@default_registry.check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">MyCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;my_check&#34;</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></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># Method 2: Explicit registration (at runtime)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">docstring_check</span> <span class="o">=</span> <span class="n">DocstringCheck</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">security_check</span> <span class="o">=</span> <span class="n">SecurityCheck</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="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">docstring_check</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">default_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">security_check</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"># Method 3: Create custom registry</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">custom_registry</span> <span class="o">=</span> <span class="n">CheckRegistry</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">DocstringCheck</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">custom_registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">SecurityCheck</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="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">registry</span><span class="o">=</span><span class="n">custom_registry</span><span class="p">,</span> <span class="n">auto_discover</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span></span></span></code></pre></div><h4 id="使用-entry_points">使用 entry_points</h4>
<p>對於要發布為獨立套件的插件，使用 <code>pyproject.toml</code> 設定 entry_points：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># pyproject.toml</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-hook-checks&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;1.0.0&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Custom hook validation checks&#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="nx">dependencies</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c"># hook-validator is the core package</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></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">project</span><span class="p">.</span><span class="nx">entry-points</span><span class="p">.</span><span class="s2">&#34;hook_validator.checks&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c"># Format: name = &#34;module:factory_or_class&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="nx">docstring</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks:DocstringCheck&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">security</span> <span class="p">=</span> <span class="s2">&#34;my_hook_checks.security:SecurityCheck&#34;</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"># my_hook_checks/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">.docstring</span> <span class="kn">import</span> <span class="n">DocstringCheck</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">.security</span> <span class="kn">import</span> <span class="n">SecurityCheck</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">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;DocstringCheck&#34;</span><span class="p">,</span> <span class="s2">&#34;SecurityCheck&#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="c1"># my_hook_checks/docstring.py</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">DocstringCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for module docstrings&#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="nd">@property</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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">13</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;docstring&#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="nd">@property</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">17</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check that hook has a module docstring&#34;</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="c1"># ... implementation ...</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span></span></span></code></pre></div><p>安裝後，<code>PluginValidator</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"># Automatic discovery from entry_points</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">PluginValidator</span><span class="p">(</span><span class="n">auto_discover</span><span class="o">=</span><span class="kc">True</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"># Check what&#39;s loaded</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="n">validator</span><span class="o">.</span><span class="n">registry</span><span class="o">.</span><span class="n">list_checks</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># [&#39;naming_convention&#39;, &#39;lib_import&#39;, &#39;output_format&#39;, &#39;test_exists&#39;,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1">#  &#39;docstring&#39;, &#39;security&#39;]  # &lt;- external checks discovered!</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<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>中：需要理解 Protocol 和註冊機制</td>
      </tr>
      <tr>
          <td><strong>維護成本</strong></td>
          <td>隨功能增加而上升</td>
          <td>穩定：新增功能不改核心</td>
      </tr>
      <tr>
          <td><strong>第三方擴展</strong></td>
          <td>不支援</td>
          <td>支援：透過 entry_points</td>
      </tr>
      <tr>
          <td><strong>測試難度</strong></td>
          <td>需要 mock 整個類別</td>
          <td>容易：每個 check 獨立測試</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>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要支援第三方擴展的工具（如 pytest、flake8、pre-commit）</li>
<li>功能模組化明確，各檢查項獨立</li>
<li>預期會頻繁新增功能</li>
<li>需要讓使用者自訂行為</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>內部使用的小工具</li>
<li>功能很少變動</li>
<li>團隊對 Protocol 和 entry_points 不熟悉</li>
<li>效能關鍵的熱路徑</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習實作格式檢查插件">基礎練習：實作格式檢查插件</h3>
<p>實作一個 <code>IndentationCheck</code>，檢查 Python 檔案是否使用一致的縮排（空格 vs Tab）：</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">IndentationCheck</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check for consistent indentation&#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="nd">@property</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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"> 6</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;indentation&#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="nd">@property</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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">10</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;Check for consistent indentation style&#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="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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">13</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="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"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="c1"># 1. Check each line&#39;s leading whitespace</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># 2. Detect if mixing tabs and spaces</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># 3. Report as warning if inconsistent</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">issues</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>context.content.splitlines()</code> 取得每一行</li>
<li>檢查每行開頭的空白字元 <code>line[:len(line) - len(line.lstrip())]</code></li>
</ul>
<h3 id="進階練習新增優先順序和相依性">進階練習：新增優先順序和相依性</h3>
<p>擴展 <code>ValidationCheck</code> Protocol，支援：</p>
<ol>
<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="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCheck</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">name</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 class="o">...</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">@property</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">description</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 class="o">...</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="nd">@property</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">priority</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">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Lower number = higher priority (default: 100)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="mi">100</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">@property</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">depends_on</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">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="s2">&#34;&#34;&#34;Names of checks that must run before this one&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">CheckContext</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 class="o">...</span></span></span></code></pre></div><p>然後修改 <code>PluginValidator.validate_hook()</code> 使用拓撲排序執行檢查。</p>
<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">class</span> <span class="nc">HotReloadableRegistry</span><span class="p">(</span><span class="n">CheckRegistry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Registry that supports hot-reloading plugins&#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="nf">watch_directory</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="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Watch a directory for new plugin files&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="c1"># TODO: Use watchdog or polling to detect new files</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="k">def</span> <span class="nf">reload_check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">10</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Reload a specific check&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="c1"># TODO: Unload old module, load new version</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>importlib.reload()</code> 重新載入模組</li>
<li>使用 <code>watchdog</code> 套件監控檔案變化</li>
<li>注意處理模組快取 <code>sys.modules</code></li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Protocol">Python typing.Protocol</a></li>
<li><a href="https://docs.python.org/3/library/importlib.metadata.html#entry-points">importlib.metadata entry_points</a></li>
<li><a href="https://docs.pytest.org/en/stable/how-to/writing_plugins.html">pytest 插件系統</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools entry_points</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em></p>
]]></content:encoded></item><item><title>3.5.2 異常設計架構</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/</guid><description>&lt;p>入門系列介紹了異常處理的基本策略。本章深入探討如何為大型專案設計異常架構，包括異常層級、異常鏈、以及 Python 3.11 引入的 ExceptionGroup。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="異常層級設計">異常層級設計&lt;/h2>
&lt;h3 id="為什麼需要層級設計">為什麼需要層級設計？&lt;/h3>
&lt;p>當專案規模增長，你會需要：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>區分錯誤來源&lt;/strong>：資料庫錯誤 vs 網路錯誤 vs 驗證錯誤&lt;/li>
&lt;li>&lt;strong>提供不同處理策略&lt;/strong>：有些錯誤可重試，有些需要通知用戶&lt;/li>
&lt;li>&lt;strong>方便呼叫者選擇處理粒度&lt;/strong>：捕獲所有錯誤 vs 只捕獲特定錯誤&lt;/li>
&lt;/ul>
&lt;h3 id="模組級異常基類">模組級異常基類&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="c1"># myapp/exceptions.py&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">AppError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">Exception&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;應用程式的基礎異常類別
&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"> - except AppError: 捕獲所有應用程式錯誤
&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"> - except SpecificError: 只捕獲特定錯誤
&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;&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">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">message&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">code&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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="kc">None&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">message&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">code&lt;/span> &lt;span class="c1"># 可選的錯誤碼，方便 API 回應&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">__str__&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">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="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">code&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">return&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">] &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&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">19&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">message&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="細粒度異常分類">細粒度異常分類&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="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">class&lt;/span> &lt;span class="nc">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppError&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="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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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">class&lt;/span> &lt;span class="nc">DataAccessError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppError&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;資料存取相關錯誤&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="k">pass&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="k">class&lt;/span> &lt;span class="nc">NetworkError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppError&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="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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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">class&lt;/span> &lt;span class="nc">ConfigurationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AppError&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;&amp;#34;&amp;#34;配置相關錯誤&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="k">pass&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">class&lt;/span> &lt;span class="nc">FieldValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidationError&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>&lt;/span>&lt;span class="line">&lt;span class="ln">22&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">field&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">message&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="kc">None&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">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&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="n">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;VALIDATION_FIELD&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&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">SchemaValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidationError&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;資料結構驗證錯誤&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="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">message&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">errors&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="kc">None&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;VALIDATION_SCHEMA&amp;#34;&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">errors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">errors&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">EntityNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataAccessError&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="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">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="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">entity_type&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">entity_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">|&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="kc">None&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">entity_type&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> with id &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">entity_id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&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">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;NOT_FOUND&amp;#34;&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 class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">entity_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">entity_type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">entity_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">entity_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">DuplicateEntityError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">DataAccessError&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="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">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="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">entity_type&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">field&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="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">48&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">entity_type&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> with &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; already exists&amp;#34;&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 class="n">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;DUPLICATE&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&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="使用範例">使用範例&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">myapp.exceptions&lt;/span> &lt;span class="kn">import&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="n">AppError&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">ValidationError&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">FieldValidationError&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">EntityNotFoundError&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">create_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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="n">User&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="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="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">data&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;email&amp;#34;&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="k">raise&lt;/span> &lt;span class="n">FieldValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Email is required&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>&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="ow">not&lt;/span> &lt;span class="n">is_valid_email&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;email&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="k">raise&lt;/span> &lt;span class="n">FieldValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Invalid email format&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="k">if&lt;/span> &lt;span class="n">user_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;email&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="k">raise&lt;/span> &lt;span class="n">DuplicateEntityError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;User&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;email&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>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">data&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="c1"># 呼叫者可以選擇處理粒度&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">def&lt;/span> &lt;span class="nf">handle_user_creation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">dict&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="k">try&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">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;success&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;user_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&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>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">FieldValidationError&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">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">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&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>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">ValidationError&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">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 處理所有驗證錯誤&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;message&amp;#34;&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">e&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>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="n">AppError&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">37&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 處理所有應用程式錯誤&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="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;code&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">code&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;message&amp;#34;&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">e&lt;/span>&lt;span class="p">)}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="異常鏈的進階用法">異常鏈的進階用法&lt;/h2>
&lt;h3 id="__cause__-vs-__context__">&lt;code>__cause__&lt;/code> vs &lt;code>__context__&lt;/code>&lt;/h3>
&lt;p>Python 有兩種異常鏈機制：&lt;/p></description><content:encoded><![CDATA[<p>入門系列介紹了異常處理的基本策略。本章深入探討如何為大型專案設計異常架構，包括異常層級、異常鏈、以及 Python 3.11 引入的 ExceptionGroup。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></li>
</ul>
<h2 id="異常層級設計">異常層級設計</h2>
<h3 id="為什麼需要層級設計">為什麼需要層級設計？</h3>
<p>當專案規模增長，你會需要：</p>
<ul>
<li><strong>區分錯誤來源</strong>：資料庫錯誤 vs 網路錯誤 vs 驗證錯誤</li>
<li><strong>提供不同處理策略</strong>：有些錯誤可重試，有些需要通知用戶</li>
<li><strong>方便呼叫者選擇處理粒度</strong>：捕獲所有錯誤 vs 只捕獲特定錯誤</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"># myapp/exceptions.py</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">AppError</span><span class="p">(</span><span class="ne">Exception</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">    - except AppError: 捕獲所有應用程式錯誤
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - except SpecificError: 只捕獲特定錯誤
</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">code</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">12</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</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">message</span> <span class="o">=</span> <span class="n">message</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">code</span> <span class="o">=</span> <span class="n">code</span>  <span class="c1"># 可選的錯誤碼，方便 API 回應</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">__str__</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">17</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">code</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="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">message</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="c1"># 第一層：按錯誤類型分類</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationError</span><span class="p">(</span><span class="n">AppError</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">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="k">class</span> <span class="nc">DataAccessError</span><span class="p">(</span><span class="n">AppError</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="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="k">class</span> <span class="nc">NetworkError</span><span class="p">(</span><span class="n">AppError</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">pass</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">ConfigurationError</span><span class="p">(</span><span class="n">AppError</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="k">pass</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">class</span> <span class="nc">FieldValidationError</span><span class="p">(</span><span class="n">ValidationError</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></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">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">message</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">23</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="s2">&#34;VALIDATION_FIELD&#34;</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">field</span> <span class="o">=</span> <span class="n">field</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">SchemaValidationError</span><span class="p">(</span><span class="n">ValidationError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;資料結構驗證錯誤&#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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">errors</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="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="s2">&#34;VALIDATION_SCHEMA&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span> <span class="o">=</span> <span class="n">errors</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">EntityNotFoundError</span><span class="p">(</span><span class="n">DataAccessError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;實體不存在&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">entity_id</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</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">37</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</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;</span><span class="si">{</span><span class="n">entity_type</span><span class="si">}</span><span class="s2"> with id &#39;</span><span class="si">{</span><span class="n">entity_id</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">code</span><span class="o">=</span><span class="s2">&#34;NOT_FOUND&#34;</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 class="bp">self</span><span class="o">.</span><span class="n">entity_type</span> <span class="o">=</span> <span class="n">entity_type</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">entity_id</span> <span class="o">=</span> <span class="n">entity_id</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="k">class</span> <span class="nc">DuplicateEntityError</span><span class="p">(</span><span class="n">DataAccessError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="s2">&#34;&#34;&#34;實體已存在&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">field</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="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">entity_type</span><span class="si">}</span><span class="s2"> with </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">=&#39;</span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">&#39; already exists&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="n">code</span><span class="o">=</span><span class="s2">&#34;DUPLICATE&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <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="kn">from</span> <span class="nn">myapp.exceptions</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">AppError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">ValidationError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">FieldValidationError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">EntityNotFoundError</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">create_user</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span><span class="p">:</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 class="k">if</span> <span class="ow">not</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">raise</span> <span class="n">FieldValidationError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;Email is required&#34;</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">if</span> <span class="ow">not</span> <span class="n">is_valid_email</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;email&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">raise</span> <span class="n">FieldValidationError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;Invalid email format&#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="k">if</span> <span class="n">user_exists</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;email&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">raise</span> <span class="n">DuplicateEntityError</span><span class="p">(</span><span class="s2">&#34;User&#34;</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="n">data</span><span class="p">[</span><span class="s2">&#34;email&#34;</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="k">return</span> <span class="n">User</span><span class="p">(</span><span class="o">**</span><span class="n">data</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="k">def</span> <span class="nf">handle_user_creation</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">24</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">user</span> <span class="o">=</span> <span class="n">create_user</span><span class="p">(</span><span class="n">data</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="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;success&#34;</span><span class="p">,</span> <span class="s2">&#34;user_id&#34;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">id</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">except</span> <span class="n">FieldValidationError</span> <span class="k">as</span> <span class="n">e</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">return</span> <span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">field</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">message</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">except</span> <span class="n">ValidationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</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="k">return</span> <span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;message&#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">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">except</span> <span class="n">AppError</span> <span class="k">as</span> <span class="n">e</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="k">return</span> <span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="s2">&#34;code&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">code</span><span class="p">,</span> <span class="s2">&#34;message&#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></code></pre></div><h2 id="異常鏈的進階用法">異常鏈的進階用法</h2>
<h3 id="__cause__-vs-__context__"><code>__cause__</code> vs <code>__context__</code></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="c1"># __cause__：明確指定的原因（使用 from）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="s2">&#34;not a number&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">raise</span> <span class="n">DataAccessError</span><span class="p">(</span><span class="s2">&#34;Failed to parse ID&#34;</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># e 會被設為 __cause__</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"># __context__：隱式的上下文（在 except 中 raise）</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="s2">&#34;not a number&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">raise</span> <span class="n">DataAccessError</span><span class="p">(</span><span class="s2">&#34;Failed to parse ID&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># 原始的 ValueError 會被設為 __context__</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"># 使用 from（__cause__）
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">DataAccessError: Failed to parse ID
</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">The above exception was the direct cause of the following exception:
</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"># 不使用 from（__context__）
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">DataAccessError: Failed to parse ID
</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">During handling of the above exception, another exception occurred:
</span></span><span class="line"><span class="ln">11</span><span class="cl">...</span></span></code></pre></div><h3 id="何時使用-from">何時使用 from</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：轉換異常型別</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">load_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">database</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;SELECT * FROM users WHERE id = </span><span class="si">{</span><span class="n">user_id</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 class="k">return</span> <span class="n">User</span><span class="p">(</span><span class="o">**</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">except</span> <span class="n">DatabaseError</span> <span class="k">as</span> <span class="n">e</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">raise</span> <span class="n">EntityNotFoundError</span><span class="p">(</span><span class="s2">&#34;User&#34;</span><span class="p">,</span> <span class="n">user_id</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</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="k">def</span> <span class="nf">process_config</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">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</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="k">as</span> <span class="n">f</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">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</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="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</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="k">raise</span> <span class="n">ConfigurationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Invalid JSON in </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span></span></span></code></pre></div><h3 id="suppress-context">suppress context</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">get_value</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">2</span><span class="cl">    <span class="k">try</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">data</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">        <span class="c1"># 使用 from None 切斷異常鏈</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Required key &#39;</span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">)</span> <span class="kn">from</span> <span class="bp">None</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"># 沒有 from None：會顯示原始 KeyError
</span></span><span class="line"><span class="ln">2</span><span class="cl"># 有 from None：只顯示 ValueError，不顯示 KeyError
</span></span><span class="line"><span class="ln">3</span><span class="cl">ValueError: Required key &#39;name&#39; not found</span></span></code></pre></div><p>使用時機：</p>
<ul>
<li>原始異常不相關或會造成困惑</li>
<li>刻意隱藏實作細節</li>
<li>簡化錯誤訊息</li>
</ul>
<h2 id="exceptiongrouppython-311">ExceptionGroup（Python 3.11+）</h2>
<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="c1"># 傳統方式：只能記錄第一個錯誤，或全部收集後手動處理</span>
</span></span><span class="line"><span class="ln"> 2</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"> 3</span><span class="cl"><span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">tasks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">task</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 6</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"> 7</span><span class="cl">        <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</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="k">if</span> <span class="n">errors</span><span class="p">:</span>
</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="k">raise</span> <span class="n">errors</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>  <span class="c1"># 丟失其他錯誤</span></span></span></code></pre></div><h3 id="exceptiongroup-解決方案">ExceptionGroup 解決方案</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"># Python 3.11+</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_all_tasks</span><span class="p">(</span><span class="n">tasks</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Task</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"> 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="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">tasks</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="n">task</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</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"> 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="n">e</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">if</span> <span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span><span class="s2">&#34;Multiple tasks failed&#34;</span><span class="p">,</span> <span class="n">errors</span><span class="p">)</span></span></span></code></pre></div><h3 id="except-語法">except* 語法</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">run_all_tasks</span><span class="p">(</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">except</span><span class="o">*</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># eg 是包含所有 ValueError 的 ExceptionGroup</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;Value errors: </span><span class="si">{</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">except</span><span class="o">*</span> <span class="ne">TypeError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="c1"># eg 是包含所有 TypeError 的 ExceptionGroup</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;Type errors: </span><span class="si">{</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="si">}</span><span class="s2">&#34;</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="kn">import</span> <span class="nn">asyncio</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">Any</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">async</span> <span class="k">def</span> <span class="nf">run_parallel</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">tasks</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[],</span> <span class="n">Any</span><span class="p">]]</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="nb">list</span><span class="p">[</span><span class="n">Any</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></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">run_task</span><span class="p">(</span><span class="n">task</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="n">Any</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">Any</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">task</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"># 使用 TaskGroup（Python 3.11+）</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="p">[]</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">futures</span> <span class="o">=</span> <span class="p">[</span><span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">run_task</span><span class="p">(</span><span class="n">task</span><span class="p">))</span> <span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">tasks</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="c1"># 如果有異常，TaskGroup 會拋出 ExceptionGroup</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">f</span><span class="o">.</span><span class="n">result</span><span class="p">()</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">futures</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="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">task1</span><span class="p">,</span> <span class="n">task2</span><span class="p">,</span> <span class="n">task3</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">try</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="k">await</span> <span class="n">run_parallel</span><span class="p">(</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">eg</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">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</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;Validation failed: </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><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="ne">ConnectionError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</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;Connection failed: </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><h3 id="巢狀-exceptiongroup">巢狀 ExceptionGroup</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"># ExceptionGroup 可以巢狀</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">outer</span> <span class="o">=</span> <span class="n">ExceptionGroup</span><span class="p">(</span><span class="s2">&#34;outer&#34;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;value error&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">ExceptionGroup</span><span class="p">(</span><span class="s2">&#34;inner&#34;</span><span class="p">,</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="ne">TypeError</span><span class="p">(</span><span class="s2">&#34;type error 1&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="ne">TypeError</span><span class="p">(</span><span class="s2">&#34;type error 2&#34;</span><span class="p">),</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 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"># 使用 .subgroup() 過濾</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">type_errors</span> <span class="o">=</span> <span class="n">outer</span><span class="o">.</span><span class="n">subgroup</span><span class="p">(</span><span class="k">lambda</span> <span class="n">e</span><span class="p">:</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="ne">TypeError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 返回只包含 TypeError 的新 ExceptionGroup</span></span></span></code></pre></div><h2 id="異常-vs-返回值">異常 vs 返回值</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="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">connect_database</span><span class="p">(</span><span class="n">url</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Connection</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="ow">not</span> <span class="n">url</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigurationError</span><span class="p">(</span><span class="s2">&#34;Database URL is required&#34;</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></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">validate_schema</span><span class="p">(</span><span class="n">data</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">11</span><span class="cl">    <span class="n">errors</span> <span class="o">=</span> <span class="n">find_schema_errors</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</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">13</span><span class="cl">        <span class="k">raise</span> <span class="n">SchemaValidationError</span><span class="p">(</span><span class="s2">&#34;Invalid data&#34;</span><span class="p">,</span> <span class="n">errors</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. 深層呼叫需要跨多層傳遞錯誤</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">def</span> <span class="nf">process_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Order</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">order</span> <span class="o">=</span> <span class="n">load_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">)</span>  <span class="c1"># 可能 raise EntityNotFoundError</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">validate_order</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>          <span class="c1"># 可能 raise ValidationError</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">return</span> <span class="n">execute_order</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>    <span class="c1"># 可能 raise PaymentError</span></span></span></code></pre></div><h3 id="何時使用-result-模式">何時使用 Result 模式</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">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">typing</span> <span class="kn">import</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</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="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">E</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;E&#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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">Ok</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"> 9</span><span class="cl">    <span class="n">value</span><span class="p">:</span> <span class="n">T</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">Err</span><span class="p">(</span><span class="n">Generic</span><span class="p">[</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="n">error</span><span class="p">:</span> <span class="n">E</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">Result</span> <span class="o">=</span> <span class="n">Ok</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">|</span> <span class="n">Err</span><span class="p">[</span><span class="n">E</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="c1"># 適合使用 Result 的情況：</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. 錯誤是預期的、常見的</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">find_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Result</span><span class="p">[</span><span class="n">User</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">if</span> <span class="n">user</span> <span class="ow">is</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="k">return</span> <span class="n">Err</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;User </span><span class="si">{</span><span class="n">user_id</span><span class="si">}</span><span class="s2"> not found&#34;</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="n">Ok</span><span class="p">(</span><span class="n">user</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"># 2. 需要強制呼叫者處理錯誤</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">def</span> <span class="nf">parse_config</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="n">Result</span><span class="p">[</span><span class="n">Config</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">28</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">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">if</span> <span class="n">errors</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">Err</span><span class="p">(</span><span class="n">errors</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="n">Ok</span><span class="p">(</span><span class="n">config</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"># 使用</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">match</span> <span class="n">parse_config</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">case</span> <span class="n">Ok</span><span class="p">(</span><span class="n">config</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">use_config</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">case</span> <span class="n">Err</span><span class="p">(</span><span class="n">errors</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">show_errors</span><span class="p">(</span><span class="n">errors</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"># 3. 效能敏感的熱點路徑</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="c1"># 異常有效能成本，頻繁拋出會影響效能</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">UserService</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></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">create_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</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="ow">not</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="k">raise</span> <span class="n">FieldValidationError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;required&#34;</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">return</span> <span class="n">user</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">UserAPI</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;API 層：將異常轉換為 Result&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">service</span><span class="p">:</span> <span class="n">UserService</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">14</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">service</span> <span class="o">=</span> <span class="n">service</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="nf">create_user</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Result</span><span class="p">[</span><span class="nb">dict</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">user</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">service</span><span class="o">.</span><span class="n">create_user</span><span class="p">(</span><span class="n">data</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="n">Ok</span><span class="p">({</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">email</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">except</span> <span class="n">FieldValidationError</span> <span class="k">as</span> <span class="n">e</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">Err</span><span class="p">({</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">field</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">message</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">except</span> <span class="n">AppError</span> <span class="k">as</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="k">return</span> <span class="n">Err</span><span class="p">({</span><span class="s2">&#34;code&#34;</span><span class="p">:</span> <span class="n">e</span><span class="o">.</span><span class="n">code</span><span class="p">,</span> <span class="s2">&#34;message&#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></code></pre></div><h2 id="補充contextlibsuppress">補充：contextlib.suppress</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">suppress</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">temp_file</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</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"> 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="c1"># 使用 suppress</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">FileNotFoundError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">temp_file</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">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">FileNotFoundError</span><span class="p">,</span> <span class="ne">PermissionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">temp_file</span><span class="p">)</span></span></span></code></pre></div><p><strong>注意</strong>：只應該用於「真正可以安全忽略」的異常。</p>
<h2 id="設計檢查表">設計檢查表</h2>
<p>設計異常架構時，考慮以下問題：</p>
<table>
  <thead>
      <tr>
          <th>問題</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>錯誤會被如何處理？</td>
          <td>決定用異常還是返回值</td>
      </tr>
      <tr>
          <td>需要區分多少種錯誤？</td>
          <td>決定異常層級深度</td>
      </tr>
      <tr>
          <td>呼叫者需要什麼資訊？</td>
          <td>決定異常屬性</td>
      </tr>
      <tr>
          <td>原始錯誤重要嗎？</td>
          <td>決定是否使用 <code>from</code></td>
      </tr>
      <tr>
          <td>會有並行錯誤嗎？</td>
          <td>考慮 ExceptionGroup</td>
      </tr>
  </tbody>
</table>
<h2 id="小結">小結</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>異常層級</td>
          <td>區分錯誤來源，提供不同處理粒度</td>
      </tr>
      <tr>
          <td><code>raise ... from e</code></td>
          <td>明確指定異常原因，保留追蹤資訊</td>
      </tr>
      <tr>
          <td><code>raise ... from None</code></td>
          <td>切斷異常鏈，隱藏實作細節</td>
      </tr>
      <tr>
          <td>ExceptionGroup</td>
          <td>同時處理多個異常</td>
      </tr>
      <tr>
          <td><code>except*</code></td>
          <td>按型別過濾 ExceptionGroup</td>
      </tr>
      <tr>
          <td>Result 模式</td>
          <td>強制錯誤處理，適合預期的錯誤</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼要使用異常層級而不是一個通用的 <code>AppError</code>？</li>
<li><code>from e</code> 和 <code>from None</code> 分別在什麼情況下使用？</li>
<li>ExceptionGroup 解決了什麼問題？在什麼場景下最有用？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.5.3 進階上下文管理</a></em></p>
]]></content:encoded></item><item><title>案例：同步/非同步橋接</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/sync-async-bridge/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib&lt;/code> 整體架構，展示如何用 &lt;code>run_in_executor&lt;/code> 和 &lt;code>asyncio.run&lt;/code> 在同步與非同步程式碼之間建立橋樑。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib&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="c1"># .claude/lib/__init__.py&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">Claude Hooks 共用程式庫
&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">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
&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">- config_loader: 配置檔案載入
&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">- hook_io: Hook 輸入輸出處理
&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">- hook_validator: Hook 合規性驗證
&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">- markdown_link_checker: Markdown 連結檢查
&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">&amp;#34;&amp;#34;&amp;#34;&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="kn">from&lt;/span> &lt;span class="nn">.git_utils&lt;/span> &lt;span class="kn">import&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">run_git_command&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">get_current_branch&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">get_project_root&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="n">get_worktree_list&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="kn">from&lt;/span> &lt;span class="nn">.config_loader&lt;/span> &lt;span class="kn">import&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">load_config&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">load_agents_config&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="kn">from&lt;/span> &lt;span class="nn">.hook_io&lt;/span> &lt;span class="kn">import&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">read_hook_input&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">write_hook_output&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="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-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># git_utils.py - 同步的 subprocess 呼叫&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">run_git_command&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">args&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">cwd&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="n">timeout&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">10&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="nb">tuple&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">bool&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Execute git command and return result&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">subprocess&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&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 class="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">args&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">cwd&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">cwd&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">capture_output&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">12&lt;/span>&lt;span class="cl"> &lt;span class="n">text&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">13&lt;/span>&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timeout&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &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">if&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">returncode&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">16&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdout&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&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="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stderr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&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>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="c1"># config_loader.py - 同步的檔案 I/O&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">def&lt;/span> &lt;span class="nf">load_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">config_name&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">dict&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;&amp;#34;&amp;#34;Load configuration file&amp;#34;&amp;#34;&amp;#34;&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">config_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_config_dir&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">yaml_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_dir&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">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">.yaml&amp;#34;&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">yaml_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">26&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&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">27&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">&lt;span class="c1"># hook_validator.py - 同步的檔案驗證&lt;/span>
&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="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;Validate a single hook file&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="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">32&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">33&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... validation logic&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>：不需要了解 asyncio，任何 Python 開發者都能使用&lt;/li>
&lt;li>&lt;strong>向後相容&lt;/strong>：可以在任何 Python 環境中執行&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>在非同步環境（如 FastAPI、aiohttp）中使用時：&lt;/p>
&lt;h4 id="問題-1阻塞事件迴圈">問題 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="kn">from&lt;/span> &lt;span class="nn">fastapi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastAPI&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">lib.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_worktree_list&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="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastAPI&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="nd">@app.get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;/git/status&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">get_git_status&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="c1"># BAD: These synchronous calls block the event loop!&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">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 class="c1"># Blocks ~50ms&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">worktrees&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># Blocks ~50ms&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"># During these 100ms, NO other requests can be processed!&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="p">{&lt;/span>&lt;span class="s2">&amp;#34;branch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;worktrees&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">worktrees&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2無法並行執行">問題 2：無法並行執行&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="k">def&lt;/span> &lt;span class="nf">validate_all_hooks&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="nb">str&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="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;Validate all hooks - sequential execution&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">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"> 4&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="n">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 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"> 5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Each validation runs one after another&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">result&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 class="c1"># ~10ms each&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">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">result&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">results&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"># 10 hooks = 100ms total&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"># With parallelization, could be ~10ms!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-3無法有效利用等待時間">問題 3：無法有效利用等待時間&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="k">def&lt;/span> &lt;span class="nf">check_project_health&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"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Check multiple aspects of project health&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"># All I/O operations execute sequentially&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">git_status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;-s&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># Wait...&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">config&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 class="c1"># Wait...&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">links&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">check_markdown_links&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;docs/README.md&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Wait...&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="c1"># Total time = sum of all operations&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">return&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="s2">&amp;#34;git&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">git_status&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="s2">&amp;#34;config&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config&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="s2">&amp;#34;links&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">links&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;h2 id="進階解決方案">進階解決方案&lt;/h2>
&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;li>&lt;strong>保持原有 API 不變&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1run_in_executor---同步到非同步">步驟 1：run_in_executor - 同步到非同步&lt;/h4>
&lt;p>&lt;code>run_in_executor&lt;/code> 將同步函式放到執行緒池中執行，讓事件迴圈可以繼續處理其他任務。&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib</code> 整體架構，展示如何用 <code>run_in_executor</code> 和 <code>asyncio.run</code> 在同步與非同步程式碼之間建立橋樑。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/case-studies/async-subprocess/" data-link-title="案例：非同步 subprocess" data-link-desc="用 asyncio.create_subprocess_exec 實現非阻塞的外部命令執行">1.1 非同步 Subprocess</a></li>
<li><a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">1.4 實戰：與同步程式碼整合</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib</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="c1"># .claude/lib/__init__.py</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">Claude Hooks 共用程式庫
</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">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">- config_loader: 配置檔案載入
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">- hook_io: Hook 輸入輸出處理
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">- hook_validator: Hook 合規性驗證
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">- markdown_link_checker: Markdown 連結檢查
</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></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">get_worktree_list</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="kn">from</span> <span class="nn">.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">load_agents_config</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="kn">from</span> <span class="nn">.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><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"># git_utils.py - 同步的 subprocess 呼叫</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">args</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"> 4</span><span class="cl">    <span class="n">cwd</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="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</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="nb">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"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Execute git command and return result&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;git&#34;</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">10</span><span class="cl">        <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">capture_output</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</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="n">timeout</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 class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">returncode</span> <span class="o">==</span> <span class="mi">0</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="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">strip</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="kc">False</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">strip</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"># config_loader.py - 同步的檔案 I/O</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="n">config_name</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">21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Load configuration file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">config_dir</span> <span class="o">=</span> <span class="n">get_config_dir</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">yaml_path</span> <span class="o">=</span> <span class="n">config_dir</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">.yaml&#34;</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">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">yaml_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">26</span><span class="cl">        <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</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"># hook_validator.py - 同步的檔案驗證</span>
</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="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;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</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">32</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">33</span><span class="cl">    <span class="c1"># ... validation logic</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>簡單直覺</strong>：不需要了解 asyncio，任何 Python 開發者都能使用</li>
<li><strong>向後相容</strong>：可以在任何 Python 環境中執行</li>
<li><strong>測試容易</strong>：同步程式碼的測試更直觀</li>
<li><strong>依賴少</strong>：不需要額外的非同步依賴庫</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>在非同步環境（如 FastAPI、aiohttp）中使用時：</p>
<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">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">get_worktree_list</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">app</span> <span class="o">=</span> <span class="n">FastAPI</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="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/status&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_git_status</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># BAD: These synchronous calls block the event loop!</span>
</span></span><span class="line"><span class="ln"> 9</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 class="c1"># Blocks ~50ms</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">get_worktree_list</span><span class="p">()</span>    <span class="c1"># Blocks ~50ms</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"># During these 100ms, NO other requests can be processed!</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span><span class="p">,</span> <span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span><span class="p">}</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">validate_all_hooks</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">:</span> <span class="nb">str</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"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate all hooks - sequential execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</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"> 4</span><span class="cl">    <span class="k">for</span> <span class="n">hook_file</span> <span class="ow">in</span> <span class="n">Path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</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"> 5</span><span class="cl">        <span class="c1"># Each validation runs one after another</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">result</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 class="c1"># ~10ms each</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="n">append</span><span class="p">(</span><span class="n">result</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">results</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"># 10 hooks = 100ms total</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># With parallelization, could be ~10ms!</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="k">def</span> <span class="nf">check_project_health</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"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check multiple aspects of project health&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># All I/O operations execute sequentially</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">git_status</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">])</span>      <span class="c1"># Wait...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">config</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 class="c1"># Wait...</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="n">check_markdown_links</span><span class="p">(</span><span class="s2">&#34;docs/README.md&#34;</span><span class="p">)</span>       <span class="c1"># Wait...</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"># Total time = sum of all operations</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;git&#34;</span><span class="p">:</span> <span class="n">git_status</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;config&#34;</span><span class="p">:</span> <span class="n">config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="s2">&#34;links&#34;</span><span class="p">:</span> <span class="n">links</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">}</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>在非同步程式碼中安全地呼叫同步函式</strong>（不阻塞事件迴圈）</li>
<li><strong>在同步程式碼中使用非同步函式</strong>（保持向後相容）</li>
<li><strong>避免巢狀事件迴圈問題</strong></li>
<li><strong>保持原有 API 不變</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1run_in_executor---同步到非同步">步驟 1：run_in_executor - 同步到非同步</h4>
<p><code>run_in_executor</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">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</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"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</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">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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># Create a shared thread pool for I/O operations</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">_io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;async_io&#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="k">async</span> <span class="k">def</span> <span class="nf">run_sync_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <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></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><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">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="s2">    Run a synchronous function in a thread pool executor.
</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">    This prevents blocking the event loop when calling
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    synchronous I/O operations from async code.
</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">    Args:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        func: The synchronous function to execute
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        *args: Positional arguments for the function
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        **kwargs: Keyword arguments for the function
</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">    Returns:
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        The result of the function call
</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">    Example:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        # Instead of blocking:
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">        result = sync_function(arg1, arg2)
</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">        # Use executor:
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">        result = await run_sync_in_executor(sync_function, arg1, arg2)
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="c1"># functools.partial handles kwargs</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">_io_executor</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></span></code></pre></div><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="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">get_worktree_list</span><span class="p">,</span> <span class="n">run_git_command</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="n">load_config</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_validator</span> <span class="kn">import</span> <span class="n">validate_hook</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"># Async wrappers for existing sync functions</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</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"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for get_current_branch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">get_current_branch</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">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for get_worktree_list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">get_worktree_list</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">async</span> <span class="k">def</span> <span class="nf">async_load_config</span><span class="p">(</span><span class="n">config_name</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">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for load_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</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">async</span> <span class="k">def</span> <span class="nf">async_validate_hook</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">19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async wrapper for validate_hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="n">validate_hook</span><span class="p">,</span> <span class="n">hook_path</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2asynciorun---非同步到同步">步驟 2：asyncio.run - 非同步到同步</h4>
<p>當你有非同步程式碼，但需要從同步環境呼叫時，使用 <code>asyncio.run</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">asyncio</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">TypeVar</span><span class="p">,</span> <span class="n">Coroutine</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">def</span> <span class="nf">run_async</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</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"> 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">    Run an async function from synchronous code.
</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">    Creates a new event loop if none exists.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Safe to call from synchronous entry points.
</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">        coro: The coroutine to execute
</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">    Returns:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">        The result of the coroutine
</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">    Example:
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        # From a synchronous script:
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">        result = run_async(async_function(arg1, arg2))
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    Warning:
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        Cannot be called when an event loop is already running!
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">        Use nest_asyncio or redesign your code structure.
</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="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</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="c1"># Synchronous API that uses async implementation internally</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">def</span> <span class="nf">get_all_worktree_branches</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</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="s2">    Get branches for all worktrees.
</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">    Uses async implementation internally for parallelization,
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    but provides a synchronous API for compatibility.
</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="k">async</span> <span class="k">def</span> <span class="nf">_async_impl</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">tasks</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">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">tasks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">_get_branch_for_path</span><span class="p">(</span><span class="n">path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">return</span> <span class="nb">dict</span><span class="p">(</span><span class="nb">zip</span><span class="p">([</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">],</span> <span class="n">results</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">run_async</span><span class="p">(</span><span class="n">_async_impl</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">async</span> <span class="k">def</span> <span class="nf">_get_branch_for_path</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">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Get current branch for a specific path&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">cwd</span><span class="o">=</span><span class="n">path</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 class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="s2">&#34;unknown&#34;</span></span></span></code></pre></div><h4 id="步驟-3處理已存在的事件迴圈">步驟 3：處理已存在的事件迴圈</h4>
<p>當你在已有事件迴圈的環境中（如 Jupyter Notebook、某些 Web 框架），直接呼叫 <code>asyncio.run</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"># This will raise RuntimeError in Jupyter or when loop is running</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">some_coroutine</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># RuntimeError: asyncio.run() cannot be called from a running event loop</span></span></span></code></pre></div><h5 id="解決方案-a使用-nest_asyncio快速修復">解決方案 A：使用 nest_asyncio（快速修復）</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="c1"># pip install nest-asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">nest_asyncio</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">run_async_safe</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</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"> 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">    Run async code safely, even from a running event loop.
</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">    Uses nest_asyncio to allow nested event loops.
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    This is a pragmatic solution for environments like Jupyter.
</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">    Note:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        nest_asyncio patches the event loop globally.
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        Use with caution in production code.
</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="c1"># No running loop - safe to use asyncio.run</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</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"># Running loop exists - need to nest</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">nest_asyncio</span><span class="o">.</span><span class="n">apply</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span></span></span></code></pre></div><h5 id="解決方案-b偵測執行環境推薦">解決方案 B：偵測執行環境（推薦）</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">asyncio</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">TypeVar</span><span class="p">,</span> <span class="n">Coroutine</span><span class="p">,</span> <span class="n">Any</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">def</span> <span class="nf">is_event_loop_running</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"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Check if there&#39;s a running event loop&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="kc">True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="kc">False</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">run_async_adaptive</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</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">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="s2">    Run async code with automatic environment detection.
</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">    - If no event loop: uses asyncio.run()
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    - If loop running: uses run_in_executor to run in a new thread
</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">    This is safer than nest_asyncio for production use.
</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="n">is_event_loop_running</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># We&#39;re in an async context - run in a new thread</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="kn">import</span> <span class="nn">concurrent.futures</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">with</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</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">27</span><span class="cl">            <span class="n">future</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">,</span> <span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">            <span class="k">return</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="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="c1"># Safe to run directly</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span></span></span></code></pre></div><h5 id="解決方案-c提供雙重-api最佳實踐">解決方案 C：提供雙重 API（最佳實踐）</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="k">class</span> <span class="nc">GitUtils</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">    Git utilities with both sync and async APIs.
</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">    Usage:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        # Synchronous (traditional)
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        utils = GitUtils()
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        branch = utils.get_current_branch()
</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">        # Asynchronous
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        branch = await utils.async_get_current_branch()
</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 class="n">cwd</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">15</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cwd</span> <span class="o">=</span> <span class="n">cwd</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"># ===== Synchronous API (original) =====</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">get_current_branch</span><span class="p">(</span><span class="bp">self</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">20</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get current branch (sync)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="p">[</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="n">cwd</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">cwd</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 class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="kc">None</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">get_worktree_list</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="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get worktree list (sync)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="c1"># ... original implementation</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="c1"># ===== Asynchronous API =====</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">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</span><span class="p">(</span><span class="bp">self</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">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get current branch (async)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">get_current_branch</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">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</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="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get worktree list (async)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_sync_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">get_worktree_list</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-4建立統一的-api">步驟 4：建立統一的 API</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Sync/Async Bridge Adapter
</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">Provides unified access to .claude/lib modules from both
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">synchronous and asynchronous contexts.
</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">asyncio</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">import</span> <span class="nn">functools</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></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">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Coroutine</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">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"> 14</span><span class="cl">
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="c1"># Shared executor for I/O operations</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="n">_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;lib_async&#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">class</span> <span class="nc">AsyncAdapter</span><span class="p">:</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="s2">    Adapter for converting sync functions to async.
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    Provides both decorator and wrapper patterns for flexibility.
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">        adapter = AsyncAdapter()
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 27</span><span class="cl"><span class="s2">        # As decorator
</span></span></span><span class="line"><span class="ln"> 28</span><span class="cl"><span class="s2">        @adapter.make_async
</span></span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="s2">        def sync_function(x):
</span></span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="s2">            return x * 2
</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">        result = await sync_function(5)
</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">        # As wrapper
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        result = await adapter.run(other_sync_function, arg1, arg2)
</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></span><span class="line"><span class="ln"> 38</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">executor</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">ThreadPoolExecutor</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"> 39</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">executor</span> <span class="o">=</span> <span class="n">executor</span> <span class="ow">or</span> <span class="n">_executor</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">async</span> <span class="k">def</span> <span class="nf">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <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></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <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"> 47</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Run a sync function asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">executor</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></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">make_async</span><span class="p">(</span><span class="bp">self</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">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</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">        Decorator to create async version of sync function.
</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">        Example:
</span></span></span><span class="line"><span class="ln"> 60</span><span class="cl"><span class="s2">            @adapter.make_async
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">            def slow_io_operation(path: str) -&gt; str:
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">                with open(path) as f:
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">                    return f.read()
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">            # Now can be awaited
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">            content = await slow_io_operation(&#34;file.txt&#34;)
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        <span class="k">async</span> <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</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"> 70</span><span class="cl">            <span class="k">return</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">run</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</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">class</span> <span class="nc">SyncAdapter</span><span class="p">:</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="s2">    Adapter for calling async functions from sync code.
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">        adapter = SyncAdapter()
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">        # Run async function synchronously
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        result = adapter.run(async_function(arg1, arg2))
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="nd">@staticmethod</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">coro</span><span class="p">:</span> <span class="n">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</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"> 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="s2">        Run a coroutine from synchronous code.
</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">        Automatically handles the case when an event loop
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">        is already running.
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">            <span class="c1"># Loop is running - use thread</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">            <span class="kn">import</span> <span class="nn">concurrent.futures</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="k">with</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="n">ex</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">                <span class="n">future</span> <span class="o">=</span> <span class="n">ex</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">,</span> <span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">                <span class="k">return</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"> 99</span><span class="cl">        <span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="c1"># No running loop - safe to use asyncio.run</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="nd">@staticmethod</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="k">def</span> <span class="nf">make_sync</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">async_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">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <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">107</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">108</span><span class="cl"><span class="s2">        Decorator to create sync version of async function.
</span></span></span><span class="line"><span class="ln">109</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">110</span><span class="cl"><span class="s2">        Example:
</span></span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="s2">            @SyncAdapter.make_sync
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="s2">            async def async_fetch(url: str) -&gt; str:
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="s2">                async with aiohttp.get(url) as resp:
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="s2">                    return await resp.text()
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="s2">            # Now can be called synchronously
</span></span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="s2">            content = async_fetch(&#34;https://example.com&#34;)
</span></span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">async_func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</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">121</span><span class="cl">            <span class="n">coro</span> <span class="o">=</span> <span class="n">async_func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">            <span class="k">return</span> <span class="n">SyncAdapter</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">return</span> <span class="n">wrapper</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">Sync/Async Bridge for .claude/lib
</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">This module provides async wrappers for the synchronous
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">.claude/lib modules, enabling their use in async contexts
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">like FastAPI without blocking the event loop.
</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">Usage:
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    # In async code (FastAPI, aiohttp, etc.)
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    from lib_async import (
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">        async_get_current_branch,
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">        async_load_config,
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        async_validate_hooks,
</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">    branch = await async_get_current_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">    # In sync code that needs parallelization
</span></span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="s2">    from lib_async import parallel_validate_hooks
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    results = parallel_validate_hooks(hooks_dir)
</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></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln"> 27</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"> 28</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"> 29</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Coroutine</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"># Import original sync modules</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</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="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="n">load_quality_rules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">    <span class="n">write_hook_output</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 class="kn">from</span> <span class="nn">lib.hook_validator</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="n">validate_hook</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">validate_all_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">ValidationResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.markdown_link_checker</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">check_markdown_links</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">check_directory</span> <span class="k">as</span> <span class="n">check_markdown_directory</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">    <span class="n">LinkCheckResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl"><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="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"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="c1"># ===== Shared Resources =====</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"># Thread pool for I/O-bound sync operations</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="n">_io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">max_workers</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="n">thread_name_prefix</span><span class="o">=</span><span class="s2">&#34;lib_async_io&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="c1"># ===== Core Utilities =====</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">async</span> <span class="k">def</span> <span class="nf">run_in_executor</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">    <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></span><span class="line"><span class="ln"> 74</span><span class="cl">    <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl"><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"> 77</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">    Run a synchronous function in the thread pool executor.
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 81</span><span class="cl"><span class="s2">        func: Synchronous function to execute
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl"><span class="s2">        *args: Positional arguments
</span></span></span><span class="line"><span class="ln"> 83</span><span class="cl"><span class="s2">        **kwargs: Keyword arguments
</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">    Returns:
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">        Function result
</span></span></span><span class="line"><span class="ln"> 87</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">    <span class="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">_io_executor</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></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">def</span> <span class="nf">make_async</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">Coroutine</span><span class="p">[</span><span class="n">Any</span><span class="p">,</span> <span class="n">Any</span><span class="p">,</span> <span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">    Decorator to create async version of a sync function.
</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">    Example:
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        @make_async
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        def slow_operation(x):
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">            time.sleep(1)
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">            return x * 2
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">        result = await slow_operation(5)
</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="nd">@functools.wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</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">109</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"># ===== Async Git Utils =====</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">async</span> <span class="k">def</span> <span class="nf">async_run_git_command</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="n">args</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">116</span><span class="cl">    <span class="n">cwd</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">117</span><span class="cl">    <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">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">119</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of run_git_command&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">run_git_command</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</span><span class="o">=</span><span class="n">cwd</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</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">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch</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">123</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_current_branch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_current_branch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_project_root</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">127</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_project_root&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_project_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_worktree_list</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of get_worktree_list&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">get_worktree_list</span><span class="p">)</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="c1"># ===== Async Config Loader =====</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="k">async</span> <span class="k">def</span> <span class="nf">async_load_config</span><span class="p">(</span><span class="n">config_name</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">137</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_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">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_agents_config&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_agents_config</span><span class="p">)</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="k">async</span> <span class="k">def</span> <span class="nf">async_load_quality_rules</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">145</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of load_quality_rules&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">load_quality_rules</span><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="c1"># ===== Async Hook Validator =====</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="k">async</span> <span class="k">def</span> <span class="nf">async_validate_hook</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">151</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of validate_hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">validate_hook</span><span class="p">,</span> <span class="n">hook_path</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="k">async</span> <span class="k">def</span> <span class="nf">async_validate_all_hooks</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">155</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">156</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">157</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">158</span><span class="cl"><span class="s2">    Validate all hooks in parallel.
</span></span></span><span class="line"><span class="ln">159</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">160</span><span class="cl"><span class="s2">    Unlike the sync version that validates sequentially,
</span></span></span><span class="line"><span class="ln">161</span><span class="cl"><span class="s2">    this version validates all hooks concurrently.
</span></span></span><span class="line"><span class="ln">162</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">163</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">164</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="n">Path</span><span class="p">(</span><span class="k">await</span> <span class="n">async_get_project_root</span><span class="p">())</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">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="n">hooks_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">hooks_path</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">169</span><span class="cl">
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="c1"># Collect all hook files</span>
</span></span><span class="line"><span class="ln">171</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">172</span><span class="cl">        <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="nb">sorted</span><span class="p">(</span><span class="n">hooks_path</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">173</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">f</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">174</span><span class="cl">    <span class="p">]</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="c1"># Validate all in parallel</span>
</span></span><span class="line"><span class="ln">177</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">async_validate_hook</span><span class="p">(</span><span class="n">hook</span><span class="p">)</span> <span class="k">for</span> <span class="n">hook</span> <span class="ow">in</span> <span class="n">hook_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="c1"># ===== Async Markdown Link Checker =====</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">async</span> <span class="k">def</span> <span class="nf">async_check_markdown_links</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">183</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Async version of check_markdown_links&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">run_in_executor</span><span class="p">(</span><span class="n">check_markdown_links</span><span class="p">,</span> <span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_check_markdown_directory</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">187</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">188</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">189</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">LinkCheckResult</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">    Check all markdown files in directory in parallel.
</span></span></span><span class="line"><span class="ln">192</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">    <span class="c1"># Get file list synchronously (fast operation)</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="n">dir_path_obj</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">dir_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">dir_path_obj</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="k">return</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">if</span> <span class="n">recursive</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">199</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_obj</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">200</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">201</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_obj</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">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">    <span class="c1"># Check all files in parallel</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">async_check_markdown_links</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="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">md_files</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl"><span class="c1"># ===== Parallel Operations =====</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">async</span> <span class="k">def</span> <span class="nf">async_check_all_worktrees</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</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">    Check status of all worktrees in parallel.
</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: {path: {&#34;branch&#34;: str, &#34;status&#34;: str, &#34;is_clean&#34;: bool}}
</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">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><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">async</span> <span class="k">def</span> <span class="nf">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="c1"># Run git status and branch in parallel for each worktree</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">status_task</span> <span class="o">=</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">        <span class="n">branch_task</span> <span class="o">=</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-current&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">path</span><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="p">(</span><span class="n">status_ok</span><span class="p">,</span> <span class="n">status</span><span class="p">),</span> <span class="p">(</span><span class="n">branch_ok</span><span class="p">,</span> <span class="n">branch</span><span class="p">)</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">status_task</span><span class="p">,</span> <span class="n">branch_task</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">return</span> <span class="n">path</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span> <span class="k">if</span> <span class="n">branch_ok</span> <span class="k">else</span> <span class="n">wt</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;branch&#34;</span><span class="p">,</span> <span class="s2">&#34;unknown&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">            <span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="n">status</span> <span class="k">if</span> <span class="n">status_ok</span> <span class="k">else</span> <span class="s2">&#34;error&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="s2">&#34;is_clean&#34;</span><span class="p">:</span> <span class="n">status_ok</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">status</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">
</span></span><span class="line"><span class="ln">235</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">check_one</span><span class="p">(</span><span class="n">wt</span><span class="p">)</span> <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</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">return</span> <span class="nb">dict</span><span class="p">(</span><span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_project_health_check</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">241</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">242</span><span class="cl"><span class="s2">    Comprehensive project health check with parallel execution.
</span></span></span><span class="line"><span class="ln">243</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">244</span><span class="cl"><span class="s2">    Checks:
</span></span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="s2">    - Git status
</span></span></span><span class="line"><span class="ln">246</span><span class="cl"><span class="s2">    - Configuration validity
</span></span></span><span class="line"><span class="ln">247</span><span class="cl"><span class="s2">    - Hook compliance
</span></span></span><span class="line"><span class="ln">248</span><span class="cl"><span class="s2">    - Documentation links
</span></span></span><span class="line"><span class="ln">249</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">250</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">251</span><span class="cl"><span class="s2">        dict: Health check results
</span></span></span><span class="line"><span class="ln">252</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">    <span class="c1"># Run all checks in parallel</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="n">git_task</span> <span class="o">=</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">    <span class="n">worktrees_task</span> <span class="o">=</span> <span class="n">async_check_all_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="n">config_task</span> <span class="o">=</span> <span class="n">async_load_agents_config</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">    <span class="n">hooks_task</span> <span class="o">=</span> <span class="n">async_validate_all_hooks</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">
</span></span><span class="line"><span class="ln">259</span><span class="cl">    <span class="n">branch</span><span class="p">,</span> <span class="n">worktrees</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">hook_results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="n">git_task</span><span class="p">,</span> <span class="n">worktrees_task</span><span class="p">,</span> <span class="n">config_task</span><span class="p">,</span> <span class="n">hooks_task</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">        <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</span>  <span class="c1"># Don&#39;t fail if one check fails</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="s2">&#34;git&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">            <span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="nb">str</span><span class="p">(</span><span class="n">branch</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">worktrees</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="p">{},</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="s2">&#34;config&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">            <span class="s2">&#34;loaded&#34;</span><span class="p">:</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="s2">&#34;agents_count&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;known_agents&#34;</span><span class="p">,</span> <span class="p">[]))</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">        <span class="s2">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">274</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">hook_results</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hook_results</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">            <span class="s2">&#34;compliant&#34;</span><span class="p">:</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">hook_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 class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hook_results</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">278</span><span class="cl">
</span></span><span class="line"><span class="ln">279</span><span class="cl"><span class="c1"># ===== Sync Wrappers for Async Functions =====</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">def</span> <span class="nf">parallel_validate_hooks</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="nb">list</span><span class="p">[</span><span class="n">ValidationResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">283</span><span class="cl"><span class="s2">    Synchronous API that uses async parallelization internally.
</span></span></span><span class="line"><span class="ln">284</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="s2">    Use this when you want parallelization but are in sync context.
</span></span></span><span class="line"><span class="ln">286</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_validate_all_hooks</span><span class="p">(</span><span class="n">hooks_dir</span><span class="p">))</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">parallel_check_worktrees</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">291</span><span class="cl"><span class="s2">    Synchronous API for parallel worktree checking.
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_check_all_worktrees</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">
</span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="k">def</span> <span class="nf">project_health_check</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">296</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">297</span><span class="cl"><span class="s2">    Synchronous API for comprehensive health check.
</span></span></span><span class="line"><span class="ln">298</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">299</span><span class="cl">    <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">async_project_health_check</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">300</span><span class="cl">
</span></span><span class="line"><span class="ln">301</span><span class="cl"><span class="c1"># ===== Demo =====</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="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Demonstrate the sync/async bridge capabilities&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">
</span></span><span class="line"><span class="ln">307</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">308</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Sync/Async Bridge Demo&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">309</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">310</span><span class="cl">
</span></span><span class="line"><span class="ln">311</span><span class="cl">    <span class="c1"># 1. Basic async wrapper usage</span>
</span></span><span class="line"><span class="ln">312</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">1. Basic async wrapper:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</span><span class="p">()</span>
</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;   Current branch: </span><span class="si">{</span><span class="n">branch</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></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="c1"># 2. Parallel execution comparison</span>
</span></span><span class="line"><span class="ln">317</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. Parallel vs Sequential comparison:&#34;</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"># Sequential (simulated)</span>
</span></span><span class="line"><span class="ln">320</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">321</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_worktree_list</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">322</span><span class="cl">    <span class="n">seq_time</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="k">for</span> <span class="n">wt</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">_</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;-s&#34;</span><span class="p">],</span> <span class="n">cwd</span><span class="o">=</span><span class="n">wt</span><span class="p">[</span><span class="s2">&#34;path&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">        <span class="n">seq_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">326</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">327</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Sequential check: </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">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="c1"># Parallel</span>
</span></span><span class="line"><span class="ln">330</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">331</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_check_all_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="n">par_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">333</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Parallel check: </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">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Speedup: </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">.1f</span><span class="si">}</span><span class="s2">x&#34;</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="c1"># 3. Project health check</span>
</span></span><span class="line"><span class="ln">337</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">3. Project health check (parallel):&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">338</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">339</span><span class="cl">    <span class="n">health</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_project_health_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">340</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">341</span><span class="cl">
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Branch: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;branch&#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">343</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Worktrees: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;worktrees&#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">344</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Hooks compliant: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;compliant&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;total&#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">345</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Time: </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">346</span><span class="cl">
</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;</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">348</span><span class="cl">
</span></span><span class="line"><span class="ln">349</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">350</span><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">demo</span><span class="p">())</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<h4 id="在-fastapi-中使用同步函式">在 FastAPI 中使用同步函式</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">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span><span class="p">,</span> <span class="n">HTTPException</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib_async</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">async_get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">async_get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">async_check_all_worktrees</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">async_validate_all_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">async_project_health_check</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">app</span> <span class="o">=</span> <span class="n">FastAPI</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="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/branch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_branch</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">    Get current git branch.
</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">    Uses async wrapper to prevent blocking the event loop.
</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 class="n">branch</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_get_current_branch</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">branch</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">raise</span> <span class="n">HTTPException</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">&#34;Not a git repository&#34;</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="p">{</span><span class="s2">&#34;branch&#34;</span><span class="p">:</span> <span class="n">branch</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="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/git/worktrees&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">get_worktrees</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">    Get all worktrees with their status.
</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">    Checks all worktrees in parallel for fast response.
</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="n">worktrees</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_check_all_worktrees</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 class="s2">&#34;worktrees&#34;</span><span class="p">:</span> <span class="n">worktrees</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="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">health_check</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">    Comprehensive project health check.
</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">    Runs multiple checks in parallel:
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    - Git status
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">    - Configuration
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    - Hook compliance
</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="n">health</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_project_health_check</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="n">health</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="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/hooks/validate&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">validate_hooks</span><span class="p">():</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="s2">    Validate all hooks in parallel.
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">    Much faster than sequential validation for many hooks.
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">async_validate_all_hooks</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="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">57</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">58</span><span class="cl">        <span class="s2">&#34;compliant&#34;</span><span class="p">:</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">59</span><span class="cl">        <span class="s2">&#34;issues&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">                <span class="s2">&#34;hook&#34;</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="n">hook_path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="s2">&#34;issues&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">                    <span class="p">{</span><span class="s2">&#34;level&#34;</span><span class="p">:</span> <span class="n">i</span><span class="o">.</span><span class="n">level</span><span class="p">,</span> <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="n">i</span><span class="o">.</span><span class="n">message</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">r</span><span class="o">.</span><span class="n">issues</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 class="p">}</span>
</span></span><span class="line"><span class="ln">67</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></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">r</span><span class="o">.</span><span class="n">is_compliant</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></code></pre></div><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="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">Synchronous script that leverages async parallelization internally.
</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></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">from</span> <span class="nn">lib_async</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">parallel_validate_hooks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">parallel_check_worktrees</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">project_health_check</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">main</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="s2">&#34;Project Health Report&#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="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">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="c1"># These functions use asyncio internally for parallelization</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># but provide a synchronous API</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. Check all worktrees in parallel</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;</span><span class="se">\n</span><span class="s2">1. Worktree Status:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">worktrees</span> <span class="o">=</span> <span class="n">parallel_check_worktrees</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">for</span> <span class="n">path</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="n">worktrees</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;clean&#34;</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&#34;is_clean&#34;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;dirty&#34;</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">info</span><span class="p">[</span><span class="s1">&#39;branch&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</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></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># 2. Validate all hooks in parallel</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="s2">&#34;</span><span class="se">\n</span><span class="s2">2. Hook Validation:&#34;</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">parallel_validate_hooks</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">29</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">30</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Compliant: </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">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</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">33</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_compliant</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">result</span><span class="o">.</span><span class="n">hook_path</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 class="k">for</span> <span class="n">issue</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">issues</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">issue</span><span class="o">.</span><span class="n">level</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">issue</span><span class="o">.</span><span class="n">message</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></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="c1"># 3. Comprehensive health check</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="s2">&#34;</span><span class="se">\n</span><span class="s2">3. Overall Health:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">health</span> <span class="o">=</span> <span class="n">project_health_check</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Git branch: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;git&#39;</span><span class="p">][</span><span class="s1">&#39;branch&#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">42</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Config loaded: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;config&#39;</span><span class="p">][</span><span class="s1">&#39;loaded&#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">43</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Hooks OK: </span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;compliant&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">health</span><span class="p">[</span><span class="s1">&#39;hooks&#39;</span><span class="p">][</span><span class="s1">&#39;total&#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">44</span><span class="cl">
</span></span><span class="line"><span class="ln">45</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">46</span><span class="cl">    <span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h4 id="python-39-使用-asyncioto_thread">Python 3.9+ 使用 asyncio.to_thread</h4>
<p>Python 3.9 引入了 <code>asyncio.to_thread</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">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">run_git_command</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.config_loader</span> <span class="kn">import</span> <span class="n">load_config</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"># Python 3.9+ simplified syntax</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_get_current_branch_39</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"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread (Python 3.9+)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">get_current_branch</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">async</span> <span class="k">def</span> <span class="nf">async_load_config_39</span><span class="p">(</span><span class="n">config_name</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">11</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread (Python 3.9+)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">load_config</span><span class="p">,</span> <span class="n">config_name</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">async</span> <span class="k">def</span> <span class="nf">async_run_git_command_39</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">args</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="n">cwd</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">17</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">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">18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Using asyncio.to_thread with keyword arguments&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># to_thread supports kwargs directly in Python 3.9+</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">run_git_command</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">cwd</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"># Comparison: run_in_executor vs to_thread</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">comparison_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">    asyncio.to_thread vs run_in_executor
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">    to_thread advantages:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    - Simpler syntax
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    - Better default executor management
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">    - Direct kwargs support
</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">    run_in_executor advantages:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">    - Works on Python 3.7+
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    - Can use custom executors
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">    - More control over thread pool
</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="c1"># run_in_executor (Python 3.7+)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">result1</span> <span class="o">=</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">get_current_branch</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"># to_thread (Python 3.9+)</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">result2</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">get_current_branch</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="k">assert</span> <span class="n">result1</span> <span class="o">==</span> <span class="n">result2</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>run_in_executor</th>
          <th>asyncio.run</th>
          <th>asyncio.to_thread</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>方向</strong></td>
          <td>同步 -&gt; 非同步</td>
          <td>非同步 -&gt; 同步</td>
          <td>同步 -&gt; 非同步</td>
      </tr>
      <tr>
          <td><strong>執行緒</strong></td>
          <td>使用執行緒池</td>
          <td>建立新事件迴圈</td>
          <td>使用預設執行緒池</td>
      </tr>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>非同步環境呼叫同步 I/O</td>
          <td>同步入口點執行非同步</td>
          <td>非同步環境呼叫同步 I/O</td>
      </tr>
      <tr>
          <td><strong>限制</strong></td>
          <td>需要事件迴圈存在</td>
          <td>不能巢狀呼叫</td>
          <td>需要 Python 3.9+</td>
      </tr>
      <tr>
          <td><strong>效能</strong></td>
          <td>高（可重用執行緒）</td>
          <td>中（建立新迴圈開銷）</td>
          <td>高（優化的執行緒池）</td>
      </tr>
      <tr>
          <td><strong>複雜度</strong></td>
          <td>中</td>
          <td>低</td>
          <td>低</td>
      </tr>
  </tbody>
</table>
<h3 id="threadpoolexecutor-vs-processpoolexecutor">ThreadPoolExecutor vs ProcessPoolExecutor</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>ThreadPoolExecutor</th>
          <th>ProcessPoolExecutor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>適用場景</strong></td>
          <td>I/O 密集操作</td>
          <td>CPU 密集操作</td>
      </tr>
      <tr>
          <td><strong>記憶體</strong></td>
          <td>共享記憶體</td>
          <td>獨立記憶體空間</td>
      </tr>
      <tr>
          <td><strong>GIL</strong></td>
          <td>受 GIL 限制</td>
          <td>繞過 GIL</td>
      </tr>
      <tr>
          <td><strong>啟動成本</strong></td>
          <td>低</td>
          <td>高（進程建立）</td>
      </tr>
      <tr>
          <td><strong>資料傳遞</strong></td>
          <td>直接傳遞</td>
          <td>需要序列化</td>
      </tr>
  </tbody>
</table>





<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">ProcessPoolExecutor</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-bound: use ThreadPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">io_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</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"># CPU-bound: use ProcessPoolExecutor</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">cpu_executor</span> <span class="o">=</span> <span class="n">ProcessPoolExecutor</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></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">async</span> <span class="k">def</span> <span class="nf">io_bound_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;File I/O, network calls, subprocess&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">io_executor</span><span class="p">,</span> <span class="n">sync_io_function</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">async</span> <span class="k">def</span> <span class="nf">cpu_bound_task</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Heavy computation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="n">cpu_executor</span><span class="p">,</span> <span class="n">sync_cpu_function</span><span class="p">)</span></span></span></code></pre></div><h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<h3 id="適合使用">適合使用</h3>
<ul>
<li><strong>漸進式遷移到 asyncio</strong>：有大量同步程式碼，需要逐步遷移</li>
<li><strong>在 FastAPI 中使用同步第三方庫</strong>：如 <code>requests</code>、<code>boto3</code>、資料庫驅動</li>
<li><strong>提供同步/非同步雙 API</strong>：讓使用者選擇適合的模式</li>
<li><strong>並行化現有同步操作</strong>：如批次檔案處理、多 API 呼叫</li>
<li><strong>整合傳統程式碼</strong>：舊系統的同步函式需要在新非同步系統中使用</li>
</ul>
<h3 id="不建議使用">不建議使用</h3>
<ul>
<li><strong>全新專案</strong>：直接用原生 asyncio 設計</li>
<li><strong>CPU 密集操作</strong>：應用 <code>multiprocessing</code> 或 <code>ProcessPoolExecutor</code></li>
<li><strong>簡單的單一操作</strong>：不需要並行的情況</li>
<li><strong>效能極度敏感</strong>：執行緒池有微小開銷</li>
<li><strong>已有原生非同步替代方案</strong>：如用 <code>aiohttp</code> 替代 <code>requests</code></li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>用 run_in_executor 包裝 requests.get</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">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</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"># TODO: Implement this function</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Fetch URL content asynchronously using requests library.
</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">    Hint: Use run_in_executor to wrap requests.get
</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">pass</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"># Test your implementation</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test</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="k">await</span> <span class="n">async_fetch</span><span class="p">(</span><span class="s2">&#34;https://httpbin.org/get&#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="n">content</span><span class="p">[:</span><span class="mi">200</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">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">test</span><span class="p">())</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<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">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</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">async</span> <span class="k">def</span> <span class="nf">async_fetch</span><span class="p">(</span><span class="n">url</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"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fetch URL content asynchronously using requests&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</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="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</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="n">response</span><span class="o">.</span><span class="n">text</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">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">fetch</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"># Or using to_thread (Python 3.9+)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_fetch_39</span><span class="p">(</span><span class="n">url</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">17</span><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">18</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="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">raise_for_status</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">text</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">return</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">fetch</span><span class="p">)</span></span></span></code></pre></div></details>
<h3 id="進階練習">進階練習</h3>
<ol start="2">
<li><strong>建立支援同步/非同步雙模式的 API 客戶端</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"># TODO: Implement a dual-mode API client</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">WeatherClient</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    Weather API client supporting both sync and async modes.
</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">    Usage:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">        client = WeatherClient(api_key=&#34;xxx&#34;)
</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">        # Sync mode
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        weather = client.get_weather(&#34;Tokyo&#34;)
</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">        # Async mode
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        weather = await client.async_get_weather(&#34;Tokyo&#34;)
</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></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">api_key</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="bp">self</span><span class="o">.</span><span class="n">api_key</span> <span class="o">=</span> <span class="n">api_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">base_url</span> <span class="o">=</span> <span class="s2">&#34;https://api.weatherapi.com/v1&#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="nf">get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</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">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Synchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">pass</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">async</span> <span class="k">def</span> <span class="nf">async_get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</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">26</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Asynchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># TODO: Implement</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">async</span> <span class="k">def</span> <span class="nf">async_get_multiple</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cities</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">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Fetch weather for multiple cities in parallel&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<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">requests</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</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="k">class</span> <span class="nc">WeatherClient</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">api_key</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="bp">self</span><span class="o">.</span><span class="n">api_key</span> <span class="o">=</span> <span class="n">api_key</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">base_url</span> <span class="o">=</span> <span class="s2">&#34;https://api.weatherapi.com/v1&#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">def</span> <span class="nf">get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</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">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Synchronous weather fetch&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">url</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">base_url</span><span class="si">}</span><span class="s2">/current.json&#34;</span>
</span></span><span class="line"><span class="ln">13</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></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">url</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">params</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;key&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">api_key</span><span class="p">,</span> <span class="s2">&#34;q&#34;</span><span class="p">:</span> <span class="n">city</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="mi">10</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="n">response</span><span class="o">.</span><span class="n">raise_for_status</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="n">response</span><span class="o">.</span><span class="n">json</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="k">async</span> <span class="k">def</span> <span class="nf">async_get_weather</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">city</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">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Asynchronous weather fetch using run_in_executor&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_weather</span><span class="p">,</span> <span class="n">city</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">async</span> <span class="k">def</span> <span class="nf">async_get_multiple</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cities</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">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Fetch weather for multiple cities in parallel&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">async_get_weather</span><span class="p">(</span><span class="n">city</span><span class="p">)</span> <span class="k">for</span> <span class="n">city</span> <span class="ow">in</span> <span class="n">cities</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="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</span><span class="p">,</span> <span class="n">return_exceptions</span><span class="o">=</span><span class="kc">True</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="p">{</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">city</span><span class="p">:</span> <span class="n">result</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="ne">Exception</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">result</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">city</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">cities</span><span class="p">,</span> <span class="n">results</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="p">}</span></span></span></code></pre></div></details>
<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="c1"># TODO: Implement an adaptive function caller</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">AdaptiveCaller</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">    Automatically detects the execution context and calls
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    functions appropriately.
</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">    - In async context: awaits coroutines, wraps sync functions
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    - In sync context: runs async functions, calls sync directly
</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">    Usage:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        caller = AdaptiveCaller()
</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">        # Works in both sync and async contexts!
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        result = caller.call(some_function, arg1, arg2)
</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></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="bp">self</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="o">**</span><span class="n">kwargs</span><span class="p">):</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 class="s2">        Call a function adaptively based on context.
</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">        - If func is async and we&#39;re in sync: run with asyncio.run
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        - If func is sync and we&#39;re in async: use run_in_executor
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">        - Otherwise: call directly
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># TODO: Implement</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><details>
<summary>參考解答</summary>





<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">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">inspect</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">Any</span><span class="p">,</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="ln"> 4</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"> 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">AdaptiveCaller</span><span class="p">:</span>
</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></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_executor</span> <span class="o">=</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">10</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">def</span> <span class="nf">_is_async_context</span><span class="p">(</span><span class="bp">self</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;Check if we&#39;re in an async context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="kc">True</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">except</span> <span class="ne">RuntimeError</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="kc">False</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">call</span><span class="p">(</span><span class="bp">self</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="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Call function adaptively based on context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">is_async_func</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">in_async_context</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_is_async_context</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="n">is_async_func</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">in_async_context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                <span class="c1"># Return coroutine to be awaited</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="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="c1"># Run async function in new event loop</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</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="o">**</span><span class="n">kwargs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">if</span> <span class="n">in_async_context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">                <span class="c1"># Wrap sync function for async context</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wrap_sync</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">                <span class="c1"># Call sync function directly</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">                <span class="k">return</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="o">**</span><span class="n">kwargs</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">async</span> <span class="k">def</span> <span class="nf">_wrap_sync</span><span class="p">(</span><span class="bp">self</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="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Wrap sync function for async execution&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="kn">import</span> <span class="nn">functools</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</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="k">if</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="n">func</span> <span class="o">=</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_executor</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></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">return</span> <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_executor</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></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">async</span> <span class="k">def</span> <span class="nf">call_async</span><span class="p">(</span><span class="bp">self</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="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Explicitly async version of call&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">is_async_func</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">iscoroutinefunction</span><span class="p">(</span><span class="n">func</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="k">if</span> <span class="n">is_async_func</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">            <span class="k">return</span> <span class="k">await</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">else</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="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_wrap_sync</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="o">**</span><span class="n">kwargs</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="c1"># Usage example</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="n">caller</span> <span class="o">=</span> <span class="n">AdaptiveCaller</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">def</span> <span class="nf">sync_func</span><span class="p">(</span><span class="n">x</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="n">x</span> <span class="o">*</span> <span class="mi">2</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">async</span> <span class="k">def</span> <span class="nf">async_func</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">3</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"># In sync context</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="n">result1</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">sync_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>      <span class="c1"># Direct call</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="n">result2</span> <span class="o">=</span> <span class="n">caller</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">async_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>     <span class="c1"># Uses asyncio.run</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"># In async context</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="n">result3</span> <span class="o">=</span> <span class="k">await</span> <span class="n">caller</span><span class="o">.</span><span class="n">call_async</span><span class="p">(</span><span class="n">sync_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>   <span class="c1"># Uses executor</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="n">result4</span> <span class="o">=</span> <span class="k">await</span> <span class="n">caller</span><span class="o">.</span><span class="n">call_async</span><span class="p">(</span><span class="n">async_func</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>  <span class="c1"># Direct await</span></span></span></code></pre></div></details>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor">run_in_executor 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-runner.html#asyncio.run">asyncio.run 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread">asyncio.to_thread 官方文件</a></li>
<li><a href="https://docs.python.org/3/library/concurrent.futures.html">concurrent.futures 官方文件</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/case-studies/parallel-io/" data-link-title="案例：並行 I/O 操作" data-link-desc="用 asyncio.gather 和 TaskGroup 實現高效的並行 I/O 操作">並行 I/O 操作</a></em>
<em>返回：<a href="/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計</a></em></p>
]]></content:encoded></item><item><title>案例：異常設計架構</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_io.py&lt;/code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>.claude/lib/hook_io.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="k">def&lt;/span> &lt;span class="nf">read_hook_input&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"> 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"> 從 stdin 讀取 Hook 輸入
&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"> Returns:
&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"> dict: 解析後的 JSON 資料，解析失敗時返回空字典
&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;&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">try&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">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stdin&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">except&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&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="k">return&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="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="p">{}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個設計有以下特點：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>捕獲所有異常&lt;/strong>：使用 &lt;code>except Exception&lt;/code> 確保不會崩潰&lt;/li>
&lt;li>&lt;strong>靜默失敗&lt;/strong>：錯誤時返回空字典，不報告錯誤原因&lt;/li>
&lt;li>&lt;strong>無法區分錯誤類型&lt;/strong>：JSON 解析錯誤和其他錯誤用同樣方式處理&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>簡單可靠&lt;/strong>：Hook 不會因為輸入問題而崩潰&lt;/li>
&lt;li>&lt;strong>使用標準異常&lt;/strong>：不需要定義額外類別&lt;/li>
&lt;li>&lt;strong>API 簡潔&lt;/strong>：呼叫者不需要處理異常&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當錯誤處理變複雜時，這個設計會遇到問題：&lt;/p>
&lt;h4 id="問題-1無法區分不同來源的錯誤">問題 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="k">def&lt;/span> &lt;span class="nf">process_hook&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="n">input_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">read_hook_input&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="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">input_data&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="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 無法給出有意義的錯誤訊息&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">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;unknown error&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="問題-2多個錯誤只能報告第一個">問題 2：多個錯誤只能報告第一個&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="k">def&lt;/span> &lt;span class="nf">validate_hook_config&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">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="kc">None&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="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_name&amp;#34;&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 class="k">if&lt;/span> &lt;span class="s2">&amp;#34;tool_input&amp;#34;&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">config&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">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;missing tool_input&amp;#34;&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;h4 id="問題-3錯誤恢復邏輯難以實作">問題 3：錯誤恢復邏輯難以實作&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="k">def&lt;/span> &lt;span class="nf">handle_hook_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&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">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"># 問題：只能用 isinstance 檢查，很難擴展&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&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">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;invalid json&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="k">elif&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="p">,&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">7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;file not found&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">else&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">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&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">error&lt;/span>&lt;span class="p">)}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立清晰的異常階層&lt;/strong>：不同錯誤類型有不同的異常類別&lt;/li>
&lt;li>&lt;strong>支援多重錯誤收集&lt;/strong>：使用 ExceptionGroup 收集多個驗證錯誤&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;h4 id="步驟-1設計異常階層">步驟 1：設計異常階層&lt;/h4>
&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="s2">&amp;#34;&amp;#34;&amp;#34;
&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">Hook 異常階層設計
&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">Exception hierarchy:
&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"> HookError (base)
&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"> ├── HookConfigError (configuration issues)
&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"> │ ├── ConfigNotFoundError
&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"> │ ├── ConfigParseError
&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"> │ └── ConfigValidationError
&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"> ├── HookInputError (input processing)
&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"> │ ├── InputReadError
&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"> │ └── InputValidationError
&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"> └── HookExecutionError (runtime issues)
&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"> ├── ToolNotFoundError
&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"> └── PermissionDeniedError
&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;&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">class&lt;/span> &lt;span class="nc">HookError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">Exception&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;
&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"> Hook 異常基礎類別
&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">
&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"> 所有 Hook 相關的異常都繼承自這個類別，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl">&lt;span class="s2"> 讓呼叫者可以用 `except HookError` 捕獲所有 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="s2"> &amp;#34;&amp;#34;&amp;#34;&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">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">message&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="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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"> 27&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="ow">or&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="k">def&lt;/span> &lt;span class="fm">__str__&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">str&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="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&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="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;, &amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&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">k&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&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">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&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="k">return&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">args&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ctx&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"> 34&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">args&lt;/span>&lt;span class="p">[&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"> 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="c1"># === Configuration Errors ===&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="k">class&lt;/span> &lt;span class="nc">HookConfigError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&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="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"> 40&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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="k">class&lt;/span> &lt;span class="nc">ConfigNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&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="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"> 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">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">config_name&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">search_paths&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">|&lt;/span> &lt;span class="kc">None&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"> 46&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search_paths&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">search_paths&lt;/span> &lt;span class="ow">or&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&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 class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;search_paths&amp;#34;&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">search_paths&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="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigParseError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&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="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"> 55&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&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">config_name&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">line&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">detail&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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"> 57&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">detail&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">detail&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">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to parse config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&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="n">line&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">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; at line &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">line&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="k">if&lt;/span> &lt;span class="n">detail&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">msg&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">detail&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"> 65&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;line&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">line&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ConfigValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookConfigError&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="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"> 69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&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">config_name&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">field&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">reason&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"> 71&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">config_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">config_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 73&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid config &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">reason&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"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;config_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">config_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&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="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Input Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookInputError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&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"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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="k">class&lt;/span> &lt;span class="nc">InputReadError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&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 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"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&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">source&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;stdin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cause&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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"> 89&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">source&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 90&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cause&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to read from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">source&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"> 93&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">source&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="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">cause&lt;/span>&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__cause__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cause&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">InputValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookInputError&lt;/span>&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 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">100&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&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">field&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">expected&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">actual&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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">102&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">expected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">actual&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">actual&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">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Invalid input: field &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">expected&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">106&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">actual&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">actual&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;field&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">field&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;expected&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl">&lt;span class="c1"># === Execution Errors ===&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookExecutionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&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">114&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ToolNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&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">118&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&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">tool_name&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">available_tools&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">|&lt;/span> &lt;span class="kc">None&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">120&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tool_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">available_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">available_tools&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">123&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Tool &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">tool_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;tool_name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">tool_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;available&amp;#34;&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">available_tools&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">125&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">126&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">127&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PermissionDeniedError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">HookExecutionError&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&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">129&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">130&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">action&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">resource&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">reason&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&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">131&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">action&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">action&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">134&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Permission denied: cannot &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">action&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resource&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">135&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">reason&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">136&lt;/span>&lt;span class="cl"> &lt;span class="n">msg&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">reason&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">137&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;action&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">action&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;resource&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">resource&lt;/span>&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2豐富的錯誤資訊">步驟 2：豐富的錯誤資訊&lt;/h4>
&lt;p>異常類別可以攜帶豐富的上下文資訊，幫助除錯和錯誤恢復：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_io.py</code> 的實際程式碼，展示如何設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>.claude/lib/hook_io.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">read_hook_input</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"> 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">    從 stdin 讀取 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">    Returns:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        dict: 解析後的 JSON 資料，解析失敗時返回空字典
</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="k">try</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="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</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="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="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="p">{}</span></span></span></code></pre></div><p>這個設計有以下特點：</p>
<ol>
<li><strong>捕獲所有異常</strong>：使用 <code>except Exception</code> 確保不會崩潰</li>
<li><strong>靜默失敗</strong>：錯誤時返回空字典，不報告錯誤原因</li>
<li><strong>無法區分錯誤類型</strong>：JSON 解析錯誤和其他錯誤用同樣方式處理</li>
</ol>
<h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>簡單可靠</strong>：Hook 不會因為輸入問題而崩潰</li>
<li><strong>使用標準異常</strong>：不需要定義額外類別</li>
<li><strong>API 簡潔</strong>：呼叫者不需要處理異常</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當錯誤處理變複雜時，這個設計會遇到問題：</p>
<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">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">input_data</span> <span class="o">=</span> <span class="n">read_hook_input</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="ow">not</span> <span class="n">input_data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># 問題：不知道是 JSON 解析失敗還是 stdin 讀取失敗</span>
</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">return</span> <span class="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;unknown error&#34;</span><span class="p">}</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">validate_hook_config</span><span class="p">(</span><span class="n">config</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">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="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_name&#34;</span><span class="p">)</span>  <span class="c1"># 第一個錯誤後就停止</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;missing tool_input&#34;</span><span class="p">)</span>  <span class="c1"># 永遠不會執行到</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="k">def</span> <span class="nf">handle_hook_error</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">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"># 問題：只能用 isinstance 檢查，很難擴展</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</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="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;invalid json&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="ne">FileNotFoundError</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="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="s2">&#34;file not found&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="k">else</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="p">{</span><span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">)}</span></span></span></code></pre></div><h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立清晰的異常階層</strong>：不同錯誤類型有不同的異常類別</li>
<li><strong>支援多重錯誤收集</strong>：使用 ExceptionGroup 收集多個驗證錯誤</li>
<li><strong>提供豐富的錯誤資訊</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="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="s2">Hook 異常階層設計
</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">Exception hierarchy:
</span></span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="s2">    HookError (base)
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    ├── HookConfigError (configuration issues)
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    │   ├── ConfigNotFoundError
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    │   ├── ConfigParseError
</span></span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="s2">    │   └── ConfigValidationError
</span></span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="s2">    ├── HookInputError (input processing)
</span></span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="s2">    │   ├── InputReadError
</span></span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="s2">    │   └── InputValidationError
</span></span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="s2">    └── HookExecutionError (runtime issues)
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="s2">        ├── ToolNotFoundError
</span></span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="s2">        └── PermissionDeniedError
</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></span><span class="line"><span class="ln"> 18</span><span class="cl"><span class="k">class</span> <span class="nc">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</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="s2">    Hook 異常基礎類別
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">    所有 Hook 相關的異常都繼承自這個類別，
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">    讓呼叫者可以用 `except HookError` 捕獲所有 Hook 錯誤。
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</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="k">def</span> <span class="fm">__str__</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"> 31</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</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"># === Configuration Errors ===</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">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</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">pass</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">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案不存在&#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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_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 class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</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="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置檔案解析失敗&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</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="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</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">class</span> <span class="nc">ConfigValidationError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;配置內容驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</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">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Invalid config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="p">)</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="c1"># === Input Errors ===</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">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</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">pass</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="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="s2">&#34;&#34;&#34;讀取輸入失敗&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 89</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"> 90</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</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 class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</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">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="s2">&#34;&#34;&#34;輸入驗證失敗&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</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">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</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="c1"># === Execution Errors ===</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行時錯誤的基礎類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="k">pass</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">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="s2">&#34;&#34;&#34;工具不存在&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</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">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</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">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</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">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</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></span><span class="line"><span class="ln">127</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">    <span class="s2">&#34;&#34;&#34;權限不足&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">
</span></span><span class="line"><span class="ln">130</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">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</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="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">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">Any</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">ErrorContext</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">    錯誤上下文資訊
</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="n">timestamp</span><span class="p">:</span> <span class="n">datetime</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="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">hook_name</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"> 15</span><span class="cl">    <span class="n">tool_name</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"> 16</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</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">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</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">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">stack_info</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"> 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">to_dict</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 class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為字典，方便序列化&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <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="k">class</span> <span class="nc">RichHookError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</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="s2">    帶有豐富上下文的 Hook 異常
</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">    - 相關的 Hook 和工具資訊
</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">    - 建議的修復方式
</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="n">error_code</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="n">suggestions</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">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="n">recoverable</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</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">error_code</span> <span class="o">=</span> <span class="n">error_code</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span> <span class="o">=</span> <span class="n">error_context</span> <span class="ow">or</span> <span class="n">ErrorContext</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span> <span class="o">=</span> <span class="n">suggestions</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span> <span class="o">=</span> <span class="n">recoverable</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">format_message</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"> 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">lines</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</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"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</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="s2">&#34;  Hook: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">hook_name</span><span class="si">}</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="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 63</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="s2">&#34;  Tool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">Suggestions:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">            <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">suggestion</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</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="s2">&#34;  </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">. </span><span class="si">{</span><span class="n">suggestion</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</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"> 71</span><span class="cl">
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="nf">to_response</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 class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="s2">&#34;&#34;&#34;轉換為 Hook 回應格式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">            <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="s2">&#34;error_code&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_code</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">error_context</span><span class="o">.</span><span class="n">to_dict</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="s2">&#34;suggestions&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="s2">&#34;recoverable&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable</span><span class="p">,</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"># Example: specific error with rich context</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl"><span class="k">class</span> <span class="nc">HookValidationError</span><span class="p">(</span><span class="n">RichHookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="s2">    Hook 驗證錯誤
</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">    當 Hook 輸入驗證失敗時拋出，
</span></span></span><span class="line"><span class="ln"> 89</span><span class="cl"><span class="s2">    提供詳細的錯誤資訊和修復建議。
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="n">actual_value</span><span class="p">:</span> <span class="n">Any</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">error_context</span><span class="p">:</span> <span class="n">ErrorContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual_value</span> <span class="o">=</span> <span class="n">actual_value</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">
</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;Validation failed for &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">reason</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="n">suggestions</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_generate_suggestions</span><span class="p">(</span><span class="n">field</span><span class="p">,</span> <span class="n">reason</span><span class="p">,</span> <span class="n">actual_value</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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">            <span class="n">message</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="n">error_code</span><span class="o">=</span><span class="s2">&#34;HOOK_VALIDATION_ERROR&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="n">error_context</span><span class="o">=</span><span class="n">error_context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">            <span class="n">suggestions</span><span class="o">=</span><span class="n">suggestions</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">            <span class="n">recoverable</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <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">_generate_suggestions</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="p">,</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="n">reason</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">actual_value</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <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">121</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據錯誤類型生成修復建議&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="n">suggestions</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;required&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Add the required field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; to your input&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="k">if</span> <span class="s2">&#34;type&#34;</span> <span class="ow">in</span> <span class="n">reason</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Check the type of &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; - expected type may differ&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="k">if</span> <span class="n">actual_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">129</span><span class="cl">            <span class="n">suggestions</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Current value: </span><span class="si">{</span><span class="n">actual_value</span><span class="si">!r}</span><span class="s2">&#34;</span><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">return</span> <span class="n">suggestions</span></span></span></code></pre></div><h4 id="步驟-3使用-exceptiongrouppython-311">步驟 3：使用 ExceptionGroup（Python 3.11+）</h4>
<p>Python 3.11 引入的 <code>ExceptionGroup</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">typing</span> <span class="kn">import</span> <span class="n">Callable</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">ValidationCollector</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">    收集多個驗證錯誤，最後用 ExceptionGroup 一次報告。
</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</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">context_name</span> <span class="o">=</span> <span class="n">context_name</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">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</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="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 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="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</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">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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;
</span></span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="s2">        檢查條件，失敗時收集錯誤
</span></span></span><span class="line"><span class="ln"> 22</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 24</span><span class="cl"><span class="s2">            condition: 要檢查的條件
</span></span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="s2">            error: 條件為 False 時要收集的錯誤
</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="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</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="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</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"> 31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;是否有錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 32</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">errors</span><span class="p">)</span> <span class="o">&gt;</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">def</span> <span class="nf">raise_if_errors</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"> 35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        如果有錯誤，拋出 ExceptionGroup
</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">        Raises:
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">            ExceptionGroup: 包含所有收集到的錯誤
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</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">errors</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="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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"> 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">    驗證 Hook 輸入，收集所有錯誤
</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">    使用 ExceptionGroup 一次報告所有驗證錯誤，
</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">
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 55</span><span class="cl"><span class="s2">        data: 待驗證的輸入資料
</span></span></span><span class="line"><span class="ln"> 56</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 57</span><span class="cl"><span class="s2">    Returns:
</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">    Raises:
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">        ExceptionGroup: 包含所有驗證錯誤
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="c1"># Check required fields</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#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 class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#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="c1"># Check types (only if fields exist)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <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="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</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 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"># Check tool-specific validation</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">    <span class="k">if</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;Write&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="s2">&#34;file_path&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.file_path&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="s2">&#34;content&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input.content&#34;</span><span class="p">,</span> <span class="s2">&#34;is required for Write tool&#34;</span><span class="p">)</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="c1"># Raise all errors at once</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</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">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="c1"># Using except* to handle ExceptionGroup (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">handle_validation_errors_demo</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範如何用 except* 處理 ExceptionGroup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">,</span>  <span class="c1"># Wrong type</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="c1"># Missing tool_input</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="c1"># Handle all InputValidationError instances</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;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> validation errors:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">        <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</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 class="sa">f</span><span class="s2">&#34;  - </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">error</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">HookError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="c1"># Handle other HookError instances</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;Found </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> hook errors&#34;</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="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</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">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="s2">&#34;T&#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="k">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</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">    錯誤恢復策略抽象基礎類別
</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></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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;判斷是否可以恢復此錯誤&#34;&#34;&#34;</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行恢復邏輯，返回恢復後的結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="k">pass</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">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl"><span class="s2">    預設值恢復策略
</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">    &#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</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="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 35</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</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="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 38</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</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="k">class</span> <span class="nc">RetryRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</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">    當錯誤是暫時性的，嘗試重試。
</span></span></span><span class="line"><span class="ln"> 45</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="n">operation</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">dict</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 50</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 class="p">,</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">        <span class="n">recoverable_errors</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">type</span><span class="p">[</span><span class="ne">Exception</span><span class="p">],</span> <span class="o">...</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="ne">IOError</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 class="bp">self</span><span class="o">.</span><span class="n">operation</span> <span class="o">=</span> <span class="n">operation</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span> <span class="o">=</span> <span class="n">max_retries</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">recoverable_errors</span> <span class="o">=</span> <span class="n">recoverable_errors</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">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 58</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</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="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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"> 61</span><span class="cl">        <span class="kn">import</span> <span class="nn">time</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">last_error</span> <span class="o">=</span> <span class="n">error</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</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.1</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">**</span> <span class="n">attempt</span><span class="p">))</span>  <span class="c1"># Exponential backoff</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">operation</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="k">except</span> <span class="bp">self</span><span class="o">.</span><span class="n">recoverable_errors</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="n">last_error</span> <span class="o">=</span> <span class="n">e</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="k">continue</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"># All retries failed, re-raise the last error</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="k">raise</span> <span class="n">last_error</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">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    錯誤恢復鏈
</span></span></span><span class="line"><span class="ln"> 78</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">    按順序嘗試多個恢復策略，
</span></span></span><span class="line"><span class="ln"> 80</span><span class="cl"><span class="s2">    直到找到可以處理的策略。
</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></span><span class="line"><span class="ln"> 83</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"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</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="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增恢復策略&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</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="s2">        嘗試恢復錯誤
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">            error: 要恢復的錯誤
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">            (是否成功恢復, 恢復結果或 None)
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</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">read_hook_input_with_recovery</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">111</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">112</span><span class="cl"><span class="s2">    讀取 Hook 輸入，帶有錯誤恢復機制
</span></span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">114</span><span class="cl"><span class="s2">    使用恢復鏈處理不同類型的錯誤。
</span></span></span><span class="line"><span class="ln">115</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">    <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">    <span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="c1"># Set up recovery chain</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="c1"># JSON parsing error - try to recover</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</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="n">recovered</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="n">result</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</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">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="c1"># Multiple validation errors</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">        <span class="c1"># Check if all errors are recoverable</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="n">all_recoverable</span> <span class="o">=</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="p">(</span><span class="n">InputValidationError</span><span class="p">,))</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">        <span class="k">if</span> <span class="n">all_recoverable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">            <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{},</span> <span class="s2">&#34;validation_errors&#34;</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)}</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="k">raise</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">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">147</span><span class="cl">        <span class="c1"># Unknown error</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</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">Hook Exception Hierarchy - Complete Example
</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">Demonstrates how to design a clear exception hierarchy
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">and use ExceptionGroup for multiple error handling.
</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></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</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">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 14</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"> 15</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"> 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">TypeVar</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">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</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"># ===== Exception Hierarchy =====</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">HookError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base class for all Hook-related exceptions.&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">context</span> <span class="ow">or</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="k">def</span> <span class="fm">__str__</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"> 30</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">            <span class="n">ctx</span> <span class="o">=</span> <span class="s2">&#34;, &#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">v</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">items</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="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">ctx</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="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</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">class</span> <span class="nc">HookConfigError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Configuration-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="k">pass</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">class</span> <span class="nc">ConfigNotFoundError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Config file not found.&#34;&#34;&#34;</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">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">search_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 class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</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">config_name</span> <span class="o">=</span> <span class="n">config_name</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">search_paths</span> <span class="o">=</span> <span class="n">search_paths</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;search_paths&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">search_paths</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="k">class</span> <span class="nc">ConfigParseError</span><span class="p">(</span><span class="n">HookConfigError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to parse config file.&#34;&#34;&#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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">config_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">line</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">detail</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config_name</span> <span class="o">=</span> <span class="n">config_name</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">line</span> <span class="o">=</span> <span class="n">line</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">detail</span> <span class="o">=</span> <span class="n">detail</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Failed to parse config &#39;</span><span class="si">{</span><span class="n">config_name</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; at line </span><span class="si">{</span><span class="n">line</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="k">if</span> <span class="n">detail</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;: </span><span class="si">{</span><span class="n">detail</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;config_name&#34;</span><span class="p">:</span> <span class="n">config_name</span><span class="p">,</span> <span class="s2">&#34;line&#34;</span><span class="p">:</span> <span class="n">line</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="k">class</span> <span class="nc">HookInputError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="k">pass</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="k">class</span> <span class="nc">InputReadError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Failed to read input.&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">source</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 72</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"> 73</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">cause</span> <span class="o">=</span> <span class="n">cause</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to read from </span><span class="si">{</span><span class="n">source</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;source&#34;</span><span class="p">:</span> <span class="n">source</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="k">if</span> <span class="n">cause</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="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</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">class</span> <span class="nc">InputValidationError</span><span class="p">(</span><span class="n">HookInputError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Input validation failed.&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">expected</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">actual</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">expected</span> <span class="o">=</span> <span class="n">expected</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">actual</span> <span class="o">=</span> <span class="n">actual</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Invalid input: field &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39; </span><span class="si">{</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">if</span> <span class="n">actual</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;, got </span><span class="si">{</span><span class="n">actual</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;field&#34;</span><span class="p">:</span> <span class="n">field</span><span class="p">,</span> <span class="s2">&#34;expected&#34;</span><span class="p">:</span> <span class="n">expected</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="k">class</span> <span class="nc">HookExecutionError</span><span class="p">(</span><span class="n">HookError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Execution-related errors.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">pass</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="k">class</span> <span class="nc">ToolNotFoundError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Tool not found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</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">tool_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">available_tools</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">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span> <span class="o">=</span> <span class="n">tool_name</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span> <span class="o">=</span> <span class="n">available_tools</span> <span class="ow">or</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;Tool &#39;</span><span class="si">{</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">            <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="n">tool_name</span><span class="p">,</span> <span class="s2">&#34;available&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">available_tools</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="k">class</span> <span class="nc">PermissionDeniedError</span><span class="p">(</span><span class="n">HookExecutionError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Permission denied.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</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">action</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">reason</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;&#34;</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">action</span> <span class="o">=</span> <span class="n">action</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">resource</span> <span class="o">=</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">reason</span> <span class="o">=</span> <span class="n">reason</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Permission denied: cannot </span><span class="si">{</span><span class="n">action</span><span class="si">}</span><span class="s2"> &#39;</span><span class="si">{</span><span class="n">resource</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">        <span class="k">if</span> <span class="n">reason</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34; (</span><span class="si">{</span><span class="n">reason</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">context</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;action&#34;</span><span class="p">:</span> <span class="n">action</span><span class="p">,</span> <span class="s2">&#34;resource&#34;</span><span class="p">:</span> <span class="n">resource</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="c1"># ===== Error Context =====</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">
</span></span><span class="line"><span class="ln">119</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorContext</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Rich context information for errors.&#34;&#34;&#34;</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="n">timestamp</span><span class="p">:</span> <span class="n">datetime</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="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">    <span class="n">hook_name</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">125</span><span class="cl">    <span class="n">tool_name</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">126</span><span class="cl">    <span class="n">tool_input</span><span class="p">:</span> <span class="nb">dict</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">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">    <span class="n">environment</span><span class="p">:</span> <span class="nb">dict</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">dict</span><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">to_dict</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 class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">            <span class="s2">&#34;timestamp&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="o">.</span><span class="n">isoformat</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">            <span class="s2">&#34;hook_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hook_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">tool_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="s2">&#34;environment&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">environment</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">
</span></span><span class="line"><span class="ln">138</span><span class="cl"><span class="c1"># ===== Validation Collector =====</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">
</span></span><span class="line"><span class="ln">140</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationCollector</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Collects validation errors and raises ExceptionGroup.&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">context_name</span> <span class="o">=</span> <span class="n">context_name</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="ne">Exception</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl">    <span class="k">def</span> <span class="nf">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</span><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="k">def</span> <span class="nf">check</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">151</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">error</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="k">def</span> <span class="nf">has_errors</span><span class="p">(</span><span class="bp">self</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">155</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">errors</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</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">raise_if_errors</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">158</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">160</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">context_name</span><span class="si">}</span><span class="s2"> failed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> error(s)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">errors</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">            <span class="p">)</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="c1"># ===== Recovery Strategies =====</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">class</span> <span class="nc">RecoveryStrategy</span><span class="p">(</span><span class="n">ABC</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">167</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Abstract base class for error recovery strategies.&#34;&#34;&#34;</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">171</span><span class="cl">        <span class="k">pass</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">175</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">
</span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="k">class</span> <span class="nc">DefaultValueRecovery</span><span class="p">(</span><span class="n">RecoveryStrategy</span><span class="p">[</span><span class="nb">dict</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">178</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Returns a default value when error occurs.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</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">default</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">default</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</span><span class="cl">    <span class="k">def</span> <span class="nf">can_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">184</span><span class="cl">        <span class="k">return</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="p">(</span><span class="n">ConfigNotFoundError</span><span class="p">,</span> <span class="n">InputReadError</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</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">187</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">copy</span><span class="p">()</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">class</span> <span class="nc">ErrorRecoveryChain</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Chain of recovery strategies.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">
</span></span><span class="line"><span class="ln">192</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">193</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">RecoveryStrategy</span><span class="p">]</span> <span class="o">=</span> <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="k">def</span> <span class="nf">add_strategy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strategy</span><span class="p">:</span> <span class="n">RecoveryStrategy</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ErrorRecoveryChain&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">strategy</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl">    <span class="k">def</span> <span class="nf">try_recover</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">        <span class="k">for</span> <span class="n">strategy</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">strategies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">            <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">can_recover</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">                    <span class="n">result</span> <span class="o">=</span> <span class="n">strategy</span><span class="o">.</span><span class="n">recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">result</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">                <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="kc">False</span><span class="p">,</span> <span class="kc">None</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="c1"># ===== Main Functions =====</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">
</span></span><span class="line"><span class="ln">211</span><span class="cl"><span class="k">def</span> <span class="nf">validate_hook_input</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">212</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">213</span><span class="cl"><span class="s2">    Validate hook input, collecting all errors.
</span></span></span><span class="line"><span class="ln">214</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">215</span><span class="cl"><span class="s2">    Uses ExceptionGroup to report all validation errors at once.
</span></span></span><span class="line"><span class="ln">216</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">217</span><span class="cl">    <span class="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;Hook input validation&#34;</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"># Required fields</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_name&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</span><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 class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">InputValidationError</span><span class="p">(</span><span class="s2">&#34;tool_input&#34;</span><span class="p">,</span> <span class="s2">&#34;is required&#34;</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="c1"># Type checks</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_name&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">],</span> <span class="nb">str</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">233</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">                <span class="s2">&#34;tool_name&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">235</span><span class="cl">                <span class="s2">&#34;must be a string&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">236</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_name&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">237</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">238</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">
</span></span><span class="line"><span class="ln">240</span><span class="cl">    <span class="k">if</span> <span class="s2">&#34;tool_input&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">        <span class="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">],</span> <span class="nb">dict</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">            <span class="n">InputValidationError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">                <span class="s2">&#34;tool_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">245</span><span class="cl">                <span class="s2">&#34;must be a dict&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">                <span class="n">actual</span><span class="o">=</span><span class="nb">type</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;tool_input&#34;</span><span class="p">])</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="n">collector</span><span class="o">.</span><span class="n">raise_if_errors</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">
</span></span><span class="line"><span class="ln">253</span><span class="cl"><span class="k">def</span> <span class="nf">read_hook_input_safe</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">254</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">255</span><span class="cl"><span class="s2">    Read hook input with error recovery.
</span></span></span><span class="line"><span class="ln">256</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">257</span><span class="cl"><span class="s2">    Enhanced version of the original read_hook_input()
</span></span></span><span class="line"><span class="ln">258</span><span class="cl"><span class="s2">    with proper exception handling.
</span></span></span><span class="line"><span class="ln">259</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_input&#34;</span><span class="p">:</span> <span class="p">{}}))</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="k">return</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</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="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">272</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</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">except</span> <span class="n">ExceptionGroup</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">275</span><span class="cl">        <span class="c1"># Re-raise validation errors as-is</span>
</span></span><span class="line"><span class="ln">276</span><span class="cl">        <span class="k">raise</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</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">279</span><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">        <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">281</span><span class="cl">        <span class="k">if</span> <span class="n">recovered</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">            <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="k">raise</span> <span class="n">error</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">
</span></span><span class="line"><span class="ln">285</span><span class="cl"><span class="k">def</span> <span class="nf">create_error_response</span><span class="p">(</span><span class="n">error</span><span class="p">:</span> <span class="n">HookError</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">286</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a standardized error response.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="s2">&#34;reason&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">        <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">291</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">error</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">            <span class="s2">&#34;message&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">error</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">            <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="n">error</span><span class="o">.</span><span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">    <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"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">
</span></span><span class="line"><span class="ln">299</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">300</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Exception Hierarchy Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">
</span></span><span class="line"><span class="ln">302</span><span class="cl">    <span class="c1"># Demo 1: Basic exception</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic exception:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">305</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;/path/1&#34;</span><span class="p">,</span> <span class="s2">&#34;/path/2&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </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><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;   Context: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">context</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"># Demo 2: Exception chaining</span>
</span></span><span class="line"><span class="ln">311</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. Exception chaining:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">312</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">            <span class="k">raise</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">(</span><span class="s2">&#34;Unexpected EOF&#34;</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">315</span><span class="cl">        <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">            <span class="k">raise</span> <span class="n">InputReadError</span><span class="p">(</span><span class="s2">&#34;stdin&#34;</span><span class="p">,</span> <span class="n">cause</span><span class="o">=</span><span class="n">e</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">317</span><span class="cl">    <span class="k">except</span> <span class="n">InputReadError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">318</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught: </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><span class="line"><span class="ln">319</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caused by: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">
</span></span><span class="line"><span class="ln">321</span><span class="cl">    <span class="c1"># Demo 3: ExceptionGroup</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;</span><span class="se">\n</span><span class="s2">3. ExceptionGroup (multiple errors):&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">324</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({</span><span class="s2">&#34;tool_name&#34;</span><span class="p">:</span> <span class="mi">123</span><span class="p">})</span>  <span class="c1"># Wrong type, missing tool_input</span>
</span></span><span class="line"><span class="ln">325</span><span class="cl">    <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught ExceptionGroup: </span><span class="si">{</span><span class="n">eg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">err</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Error </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">
</span></span><span class="line"><span class="ln">330</span><span class="cl">    <span class="c1"># Demo 4: except* syntax (Python 3.11+)</span>
</span></span><span class="line"><span class="ln">331</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">4. Using except* to handle specific error types:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">        <span class="n">validate_hook_input</span><span class="p">({})</span>  <span class="c1"># Missing both fields</span>
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">335</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Caught </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</span><span class="si">}</span><span class="s2"> InputValidationError(s)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">337</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="o">.</span><span class="n">field</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">err</span><span class="o">.</span><span class="n">expected</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">338</span><span class="cl">
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="c1"># Demo 5: Error recovery</span>
</span></span><span class="line"><span class="ln">340</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">5. Error recovery:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="n">recovery</span> <span class="o">=</span> <span class="n">ErrorRecoveryChain</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <span class="n">recovery</span><span class="o">.</span><span class="n">add_strategy</span><span class="p">(</span><span class="n">DefaultValueRecovery</span><span class="p">({</span><span class="s2">&#34;default&#34;</span><span class="p">:</span> <span class="kc">True</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="n">error</span> <span class="o">=</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="s2">&#34;missing_config&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="n">recovered</span><span class="p">,</span> <span class="n">result</span> <span class="o">=</span> <span class="n">recovery</span><span class="o">.</span><span class="n">try_recover</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;   Recovered: </span><span class="si">{</span><span class="n">recovered</span><span class="si">}</span><span class="s2">, Result: </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">347</span><span class="cl">
</span></span><span class="line"><span class="ln">348</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">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<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="kn">from</span> <span class="nn">hook_exceptions</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">HookError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">ConfigNotFoundError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">InputValidationError</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">create_error_response</span><span class="p">,</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">process_hook</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理 Hook 請求&#34;&#34;&#34;</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="c1"># Load configuration</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">config</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">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="c1"># Validate input</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">read_input</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="c1"># Process...</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</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="k">except</span> <span class="n">ConfigNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Specific handling for missing config</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;Warning: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">, using defaults&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;decision&#34;</span><span class="p">:</span> <span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;warning&#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">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">except</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="c1"># Input validation error</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</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="k">except</span> <span class="n">HookError</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"># Catch-all for other hook errors</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">return</span> <span class="n">create_error_response</span><span class="p">(</span><span class="n">e</span><span class="p">)</span></span></span></code></pre></div><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="k">def</span> <span class="nf">validate_batch_inputs</span><span class="p">(</span><span class="n">inputs</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="nb">list</span><span class="p">[</span><span class="nb">dict</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">    &#34;&#34;&#34;</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="p">[]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">all_errors</span> <span class="o">=</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">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">inputs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">validated</span> <span class="o">=</span> <span class="n">validate_hook_input</span><span class="p">(</span><span class="n">data</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="n">append</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 class="s2">&#34;data&#34;</span><span class="p">:</span> <span class="n">validated</span><span class="p">,</span> <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">except</span> <span class="n">ExceptionGroup</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="c1"># Collect errors from this input</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">)</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="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                <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">19</span><span class="cl">                <span class="s2">&#34;errors&#34;</span><span class="p">:</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                <span class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</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 class="k">except</span> <span class="n">HookError</span> <span class="k">as</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">all_errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">append</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 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 class="s2">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">False</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"># Report summary</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">if</span> <span class="n">all_errors</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;Validation completed with </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2"> total errors&#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="k">return</span> <span class="n">results</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"># Using except* to handle different error types differently</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="k">def</span> <span class="nf">handle_mixed_errors</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;&#34;&#34;示範用 except* 分別處理不同類型的錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="c1"># This might raise ExceptionGroup with mixed errors</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">process_complex_operation</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="k">except</span><span class="o">*</span> <span class="n">InputValidationError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="c1"># Handle validation errors - maybe log and continue</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">log_validation_error</span><span class="p">(</span><span class="n">err</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="k">except</span><span class="o">*</span> <span class="n">PermissionDeniedError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">        <span class="c1"># Handle permission errors - need to notify admin</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="n">notify_admin</span><span class="p">(</span><span class="n">err</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="k">except</span><span class="o">*</span> <span class="n">HookExecutionError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="c1"># Handle execution errors - maybe retry</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">for</span> <span class="n">err</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">schedule_retry</span><span class="p">(</span><span class="n">err</span><span class="p">)</span></span></span></code></pre></div><h4 id="異常鏈exception-chaining">異常鏈（Exception Chaining）</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">load_config_with_chain</span><span class="p">(</span><span class="n">name</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"> 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">    使用 `raise ... from ...` 保留異常鏈，
</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">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</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">config_path</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;/config/</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">.yaml&#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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config_path</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">14</span><span class="cl">            <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">f</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">except</span> <span class="ne">FileNotFoundError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="c1"># Wrap in domain-specific exception, preserve original</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigNotFoundError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">[</span><span class="n">config_path</span><span class="p">])</span> <span class="kn">from</span> <span class="nn">e</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">except</span> <span class="n">yaml</span><span class="o">.</span><span class="n">YAMLError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="c1"># Extract line number if available</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">line</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="s2">&#34;problem_mark&#34;</span><span class="p">,</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="n">line_num</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">line</span> <span class="k">if</span> <span class="n">line</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">raise</span> <span class="n">ConfigParseError</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">line</span><span class="o">=</span><span class="n">line_num</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</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">except</span> <span class="ne">PermissionError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="c1"># Convert to domain exception</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">raise</span> <span class="n">PermissionDeniedError</span><span class="p">(</span><span class="s2">&#34;read&#34;</span><span class="p">,</span> <span class="n">config_path</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="kn">from</span> <span class="nn">e</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"># When catching, you can access the full chain</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="k">def</span> <span class="nf">debug_config_error</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">config</span> <span class="o">=</span> <span class="n">load_config_with_chain</span><span class="p">(</span><span class="s2">&#34;agents&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="k">except</span> <span class="n">HookConfigError</span> <span class="k">as</span> <span class="n">e</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;Error: </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><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;Original cause: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">__cause__</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></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="c1"># Print full traceback including cause</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="kn">import</span> <span class="nn">traceback</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">traceback</span><span class="o">.</span><span class="n">print_exception</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="n">e</span><span class="p">,</span> <span class="n">e</span><span class="o">.</span><span class="n">__traceback__</span><span class="p">)</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>標準異常</th>
          <th>自定義階層</th>
          <th>ExceptionGroup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>學習成本</td>
          <td>低</td>
          <td>中</td>
          <td>中高</td>
      </tr>
      <tr>
          <td>錯誤辨識</td>
          <td>困難</td>
          <td>清晰</td>
          <td>清晰</td>
      </tr>
      <tr>
          <td>錯誤資訊</td>
          <td>有限</td>
          <td>豐富</td>
          <td>豐富</td>
      </tr>
      <tr>
          <td>多重錯誤</td>
          <td>只報一個</td>
          <td>只報一個</td>
          <td>全部報告</td>
      </tr>
      <tr>
          <td>恢復策略</td>
          <td>難實作</td>
          <td>易實作</td>
          <td>可分類處理</td>
      </tr>
      <tr>
          <td>Python 版本</td>
          <td>所有版本</td>
          <td>所有版本</td>
          <td>3.11+</td>
      </tr>
      <tr>
          <td>程式碼量</td>
          <td>最少</td>
          <td>中等</td>
          <td>較多</td>
      </tr>
  </tbody>
</table>
<h3 id="何時使用哪種方案">何時使用哪種方案？</h3>
<h4 id="使用標準異常如-hook_iopy-的做法">使用標準異常（如 <code>hook_io.py</code> 的做法）</h4>
<ul>
<li>簡單的腳本或工具</li>
<li>錯誤處理很簡單</li>
<li>不需要區分錯誤類型</li>
</ul>
<h4 id="使用自定義階層">使用自定義階層</h4>
<ul>
<li>函式庫或框架</li>
<li>需要區分不同錯誤類型</li>
<li>需要提供錯誤恢復建議</li>
</ul>
<h4 id="使用-exceptiongroup">使用 ExceptionGroup</h4>
<ul>
<li>批次處理需要收集所有錯誤</li>
<li>驗證邏輯有多個檢查點</li>
<li>需要讓使用者一次看到所有問題</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要區分不同錯誤類型的函式庫</li>
<li>批次處理需要收集所有錯誤</li>
<li>需要提供錯誤恢復建議</li>
<li>需要保留完整的錯誤來源（異常鏈）</li>
<li>Python 3.11+ 環境</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的腳本</li>
<li>錯誤類型很少（&lt; 3 種）</li>
<li>不需要精細的錯誤處理</li>
<li>需要支援舊版 Python（&lt; 3.11）使用 ExceptionGroup</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li><strong>設計異常階層</strong>：為一個配置載入模組設計異常階層，包含：
<ul>
<li>檔案不存在</li>
<li>格式錯誤</li>
<li>欄位缺失</li>
<li>型別錯誤</li>
</ul>
</li>
</ol>
<p>提示：先畫出階層圖，再實作類別。</p>
<h3 id="進階練習">進階練習</h3>
<ol>
<li><strong>實作 ExceptionGroup 驗證器</strong>：寫一個表單驗證器，可以：
<ul>
<li>收集所有欄位的驗證錯誤</li>
<li>用 ExceptionGroup 一次報告</li>
<li>支援 <code>except*</code> 分類處理</li>
</ul>
</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"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">validate_form</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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"> 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="n">collector</span> <span class="o">=</span> <span class="n">ValidationCollector</span><span class="p">(</span><span class="s2">&#34;form&#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="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span> <span class="o">&gt;=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;username&#34;</span><span class="p">,</span> <span class="s2">&#34;must be at least 3 characters&#34;</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="n">collector</span><span class="o">.</span><span class="n">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;@&#34;</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">FieldError</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="s2">&#34;must contain @&#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">collector</span><span class="o">.</span><span class="n">raise_if_errors</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">data</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<ol>
<li><strong>實作帶有自動修復建議的異常</strong>：設計一個異常系統，可以：
<ul>
<li>根據錯誤類型自動生成修復建議</li>
<li>支援結構化的錯誤報告（JSON 格式）</li>
<li>提供「可能的修復」和「參考文件」連結</li>
</ul>
</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"># 目標 API</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">validate_config</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">except</span> <span class="n">ConfigError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</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="n">e</span><span class="o">.</span><span class="n">format_message</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># [CONFIG_001] Invalid config &#39;agents.yaml&#39;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1">#   Field: known_agents</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1">#   Error: expected list, got string</span>
</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="c1"># Suggestions:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1">#   1. Change &#39;known_agents: &#34;basil&#34;&#39; to &#39;known_agents: [&#34;basil&#34;]&#39;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1">#   2. See: https://docs.example.com/config#known_agents</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0654/">PEP 654 - Exception Groups and except*</a></li>
<li><a href="https://peps.python.org/pep-3134/">PEP 3134 - Exception Chaining</a></li>
<li><a href="https://docs.python.org/3/tutorial/errors.html">Python 異常最佳實踐</a></li>
<li><a href="https://realpython.com/python311-exception-groups/">Real Python - Exception Groups</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></em></p>
]]></content:encoded></item><item><title>1.3 設計模式與最佳實踐</title><link>https://tarrragon.github.io/blog/python-advanced/01-asyncio/patterns/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/01-asyncio/patterns/</guid><description>&lt;p>本章介紹 asyncio 的常見設計模式，以及如何避免常見陷阱。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>使用異步上下文管理器管理資源&lt;/li>
&lt;li>實作異步迭代器處理串流資料&lt;/li>
&lt;li>使用 Semaphore 控制並發&lt;/li>
&lt;li>避免阻塞事件迴圈的陷阱&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="原理層異步協議">【原理層】異步協議&lt;/h2>
&lt;h3 id="異步上下文管理器">異步上下文管理器&lt;/h3>
&lt;p>同步版本使用 &lt;code>__enter__&lt;/code> 和 &lt;code>__exit__&lt;/code>，異步版本使用 &lt;code>__aenter__&lt;/code> 和 &lt;code>__aexit__&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">class&lt;/span> &lt;span class="nc">AsyncResource&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="fm">__aenter__&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"> 3&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&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">return&lt;/span> &lt;span class="bp">self&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="fm">__aexit__&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">exc_type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">exc_val&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">exc_tb&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="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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">disconnect&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">connect&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">disconnect&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">15&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">AsyncResource&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">resource&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="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;/code>&lt;/pre>&lt;/div>&lt;h3 id="異步迭代器">異步迭代器&lt;/h3>
&lt;p>同步版本使用 &lt;code>__iter__&lt;/code> 和 &lt;code>__next__&lt;/code>，異步版本使用 &lt;code>__aiter__&lt;/code> 和 &lt;code>__anext__&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">class&lt;/span> &lt;span class="nc">AsyncCounter&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">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">stop&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="o">.&lt;/span>&lt;span class="n">current&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stop&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="fm">__aiter__&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="fm">__anext__&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">10&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">current&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="bp">self&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">11&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">StopAsyncIteration&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">current&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&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">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">current&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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">async&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">AsyncCounter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&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;/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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">async_range&lt;/span>&lt;span class="p">(&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">2&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">stop&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="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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="k">yield&lt;/span> &lt;span class="n">i&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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">async&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">async_range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">5&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層常見模式">【設計層】常見模式&lt;/h2>
&lt;h3 id="協程鏈chaining">協程鏈（Chaining）&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&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="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">session&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="k">as&lt;/span> &lt;span class="n">response&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">html&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="c1"># 假設這是 CPU 密集操作，應該放到執行緒池&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&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 class="k">return&lt;/span> &lt;span class="n">html&lt;/span>&lt;span class="p">[:&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">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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;處理完成：&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">data&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">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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">pipeline&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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">html&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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="n">parsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">html&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="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">process&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parsed&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="n">result&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="semaphore-並發控制">Semaphore 並發控制&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">fetch_with_limit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sem&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">url&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">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">sem&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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">sem&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Semaphore&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 最多 10 個並發&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">urls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;https://example.com/&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="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">100&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="n">tasks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">fetch_with_limit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sem&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">url&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">url&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">urls&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">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">tasks&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="生產者-消費者">生產者-消費者&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">producer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">queue&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">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&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="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.1&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="k">await&lt;/span> &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">put&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"> 5&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">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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">queue&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">put&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">None&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"> 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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">consumer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">queue&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">while&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">10&lt;/span>&lt;span class="cl"> &lt;span class="n">item&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">queue&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">11&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">item&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">break&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">0.2&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="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">item&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">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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&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="n">queue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Queue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">maxsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&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">producer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">queue&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">consumer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">queue&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;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="實作層實用範例">【實作層】實用範例&lt;/h2>
&lt;h3 id="異步-rate-limiter">異步 Rate Limiter&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="k">class&lt;/span> &lt;span class="nc">RateLimiter&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">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">rate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">per&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="o">.&lt;/span>&lt;span class="n">rate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rate&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="o">.&lt;/span>&lt;span class="n">per&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">per&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowance&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rate&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">last_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_event_loop&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">acquire&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">current&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_event_loop&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&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">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">current&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">last_check&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">last_check&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">current&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowance&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">elapsed&lt;/span> &lt;span class="o">*&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">rate&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">per&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>&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowance&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rate&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowance&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">rate&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">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">allowance&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mf">1.0&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="n">wait_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mf">1.0&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">allowance&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&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">per&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">rate&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wait_time&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">allowance&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="k">else&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">allowance&lt;/span> &lt;span class="o">-=&lt;/span> &lt;span class="mf">1.0&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="重試模式">重試模式&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">retry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">coro_func&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_retries&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delay&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">1.0&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">for&lt;/span> &lt;span class="n">attempt&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">max_retries&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="k">try&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">coro_func&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">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">6&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">attempt&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">max_retries&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&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">raise&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&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">attempt&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">/&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">max_retries&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">9&lt;/span>&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">delay&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">attempt&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="陷阱與避免">【陷阱與避免】&lt;/h2>
&lt;h3 id="阻塞事件迴圈">阻塞事件迴圈&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">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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">bad_example&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">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">good_example&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">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&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"> 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="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">run_blocking&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">loop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_running_loop&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">await&lt;/span> &lt;span class="n">loop&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run_in_executor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="資源洩漏">資源洩漏&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="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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">bad&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">session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&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">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">session&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;https://example.com&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 class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&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="c1"># 正確：使用 async with&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">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">good&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">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">aiohttp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ClientSession&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">session&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">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">session&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;https://example.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">response&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="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="思考題">思考題&lt;/h2>
&lt;ol>
&lt;li>為什麼 Semaphore 在 asyncio 中不需要擔心執行緒安全？&lt;/li>
&lt;li>異步生成器在什麼場景下比異步迭代器更適合？&lt;/li>
&lt;li>如何檢測程式碼是否阻塞了事件迴圈？&lt;/li>
&lt;/ol>
&lt;h2 id="實作練習">實作練習&lt;/h2>
&lt;ol>
&lt;li>實作一個異步連線池&lt;/li>
&lt;li>實作一個帶有指數退避的重試裝飾器&lt;/li>
&lt;li>實作一個異步的 debounce 函式&lt;/li>
&lt;/ol>
&lt;h2 id="延伸閱讀">延伸閱讀&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.python.org/3/library/asyncio-queue.html">Python 官方文件 - asyncio Queue&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/aio-libs">aio-libs 系列函式庫&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>上一章：&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">協程與 Task 管理&lt;/a>&lt;/em>
&lt;em>下一章：&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">實戰：與同步程式碼整合&lt;/a>&lt;/em>&lt;/p></description><content:encoded><![CDATA[<p>本章介紹 asyncio 的常見設計模式，以及如何避免常見陷阱。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">1.2 協程與 Task 管理</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>使用異步上下文管理器管理資源</li>
<li>實作異步迭代器處理串流資料</li>
<li>使用 Semaphore 控制並發</li>
<li>避免阻塞事件迴圈的陷阱</li>
</ol>
<hr>
<h2 id="原理層異步協議">【原理層】異步協議</h2>
<h3 id="異步上下文管理器">異步上下文管理器</h3>
<p>同步版本使用 <code>__enter__</code> 和 <code>__exit__</code>，異步版本使用 <code>__aenter__</code> 和 <code>__aexit__</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">AsyncResource</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</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"> 4</span><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">connect</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="bp">self</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">async</span> <span class="k">def</span> <span class="fm">__aexit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_type</span><span class="p">,</span> <span class="n">exc_val</span><span class="p">,</span> <span class="n">exc_tb</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="s2">&#34;釋放資源&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">disconnect</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">async</span> <span class="k">def</span> <span class="nf">connect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</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">async</span> <span class="k">def</span> <span class="nf">disconnect</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="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncResource</span><span class="p">()</span> <span class="k">as</span> <span class="n">resource</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</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></code></pre></div><h3 id="異步迭代器">異步迭代器</h3>
<p>同步版本使用 <code>__iter__</code> 和 <code>__next__</code>，異步版本使用 <code>__aiter__</code> 和 <code>__anext__</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">AsyncCounter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</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">stop</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="o">.</span><span class="n">current</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">stop</span> <span class="o">=</span> <span class="n">stop</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">__aiter__</span><span class="p">(</span><span class="bp">self</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="bp">self</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">async</span> <span class="k">def</span> <span class="fm">__anext__</span><span class="p">(</span><span class="bp">self</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="bp">self</span><span class="o">.</span><span class="n">current</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">stop</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="k">raise</span> <span class="ne">StopAsyncIteration</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1"># 模擬異步操作</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">current</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">current</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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">AsyncCounter</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">num</span><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="k">async</span> <span class="k">def</span> <span class="nf">async_range</span><span class="p">(</span><span class="n">stop</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</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">stop</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">yield</span> <span class="n">i</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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">async</span> <span class="k">for</span> <span class="n">num</span> <span class="ow">in</span> <span class="n">async_range</span><span class="p">(</span><span class="mi">5</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="n">num</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="設計層常見模式">【設計層】常見模式</h2>
<h3 id="協程鏈chaining">協程鏈（Chaining）</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">async</span> <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">session</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="k">as</span> <span class="n">response</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="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">text</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">async</span> <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="n">html</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># 假設這是 CPU 密集操作，應該放到執行緒池</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>  <span class="c1"># 讓出控制權</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">return</span> <span class="n">html</span><span class="p">[:</span><span class="mi">100</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">async</span> <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</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;處理完成：</span><span class="si">{</span><span class="n">data</span><span class="si">}</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="k">async</span> <span class="k">def</span> <span class="nf">pipeline</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">html</span> <span class="o">=</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">parsed</span> <span class="o">=</span> <span class="k">await</span> <span class="n">parse</span><span class="p">(</span><span class="n">html</span><span class="p">)</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="k">await</span> <span class="n">process</span><span class="p">(</span><span class="n">parsed</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="n">result</span></span></span></code></pre></div><h3 id="semaphore-並發控制">Semaphore 並發控制</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">async</span> <span class="k">def</span> <span class="nf">fetch_with_limit</span><span class="p">(</span><span class="n">sem</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">sem</span><span class="p">:</span>  <span class="c1"># 獲取信號量</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">return</span> <span class="k">await</span> <span class="n">fetch</span><span class="p">(</span><span class="n">url</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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">sem</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Semaphore</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>  <span class="c1"># 最多 10 個並發</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">urls</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;https://example.com/</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"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">tasks</span> <span class="o">=</span> <span class="p">[</span><span class="n">fetch_with_limit</span><span class="p">(</span><span class="n">sem</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">results</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="o">*</span><span class="n">tasks</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="k">async</span> <span class="k">def</span> <span class="nf">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</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">10</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="k">await</span> <span class="n">queue</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">i</span><span class="p">)</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;生產：</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"> 6</span><span class="cl">    <span class="k">await</span> <span class="n">queue</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="kc">None</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="k">async</span> <span class="k">def</span> <span class="nf">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">item</span> <span class="o">=</span> <span class="k">await</span> <span class="n">queue</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">if</span> <span class="n">item</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.2</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;消費：</span><span class="si">{</span><span class="n">item</span><span class="si">}</span><span class="s2">&#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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">queue</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Queue</span><span class="p">(</span><span class="n">maxsize</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">producer</span><span class="p">(</span><span class="n">queue</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">consumer</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="實作層實用範例">【實作層】實用範例</h2>
<h3 id="異步-rate-limiter">異步 Rate Limiter</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">RateLimiter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</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">rate</span><span class="p">,</span> <span class="n">per</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="o">.</span><span class="n">rate</span> <span class="o">=</span> <span class="n">rate</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">per</span> <span class="o">=</span> <span class="n">per</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">allowance</span> <span class="o">=</span> <span class="n">rate</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">last_check</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span><span class="o">.</span><span class="n">time</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">async</span> <span class="k">def</span> <span class="nf">acquire</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">current</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">elapsed</span> <span class="o">=</span> <span class="n">current</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">last_check</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">last_check</span> <span class="o">=</span> <span class="n">current</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">allowance</span> <span class="o">+=</span> <span class="n">elapsed</span> <span class="o">*</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">rate</span> <span class="o">/</span> <span class="bp">self</span><span class="o">.</span><span class="n">per</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">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">allowance</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">rate</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">allowance</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">rate</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="bp">self</span><span class="o">.</span><span class="n">allowance</span> <span class="o">&lt;</span> <span class="mf">1.0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="n">wait_time</span> <span class="o">=</span> <span class="p">(</span><span class="mf">1.0</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">allowance</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">per</span> <span class="o">/</span> <span class="bp">self</span><span class="o">.</span><span class="n">rate</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">wait_time</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">allowance</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="k">else</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">allowance</span> <span class="o">-=</span> <span class="mf">1.0</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">async</span> <span class="k">def</span> <span class="nf">retry</span><span class="p">(</span><span class="n">coro_func</span><span class="p">,</span> <span class="n">max_retries</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">delay</span><span class="o">=</span><span class="mf">1.0</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_retries</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="k">try</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="k">await</span> <span class="n">coro_func</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">5</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">6</span><span class="cl">            <span class="k">if</span> <span class="n">attempt</span> <span class="o">==</span> <span class="n">max_retries</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">                <span class="k">raise</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;重試 </span><span class="si">{</span><span class="n">attempt</span> <span class="o">+</span> <span class="mi">1</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">max_retries</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">            <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">delay</span> <span class="o">*</span> <span class="p">(</span><span class="n">attempt</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span></span></span></code></pre></div><hr>
<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="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">async</span> <span class="k">def</span> <span class="nf">bad_example</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</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="k">async</span> <span class="k">def</span> <span class="nf">good_example</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 非阻塞</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="k">async</span> <span class="k">def</span> <span class="nf">run_blocking</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">await</span> <span class="n">loop</span><span class="o">.</span><span class="n">run_in_executor</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">,</span> <span class="mi">1</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="c1"># 錯誤：沒有正確關閉</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">bad</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">session</span> <span class="o">=</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</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="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">text</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"># 正確：使用 async with</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">good</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://example.com&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</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="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">()</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 Semaphore 在 asyncio 中不需要擔心執行緒安全？</li>
<li>異步生成器在什麼場景下比異步迭代器更適合？</li>
<li>如何檢測程式碼是否阻塞了事件迴圈？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>實作一個異步連線池</li>
<li>實作一個帶有指數退避的重試裝飾器</li>
<li>實作一個異步的 debounce 函式</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/asyncio-queue.html">Python 官方文件 - asyncio Queue</a></li>
<li><a href="https://github.com/aio-libs">aio-libs 系列函式庫</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/01-asyncio/coroutines-tasks/" data-link-title="1.2 協程與 Task 管理" data-link-desc="深入理解協程、Task 與 Future，掌握 async/await 的進階用法">協程與 Task 管理</a></em>
<em>下一章：<a href="/blog/python-advanced/01-asyncio/real-world/" data-link-title="1.4 實戰：與同步程式碼整合" data-link-desc="在現有專案中引入 asyncio，處理同步與異步的混合場景">實戰：與同步程式碼整合</a></em></p>
]]></content:encoded></item><item><title>3.5.3 進階上下文管理</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/</guid><description>&lt;p>入門系列介紹了 &lt;code>with&lt;/code> 語句的基本使用。本章深入探討上下文管理器的實現原理與進階應用，包括 &lt;code>contextlib&lt;/code> 工具、嵌套組合、以及非同步上下文管理。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列的 &lt;code>with&lt;/code> 語句使用&lt;/li>
&lt;li>基本的類別定義與魔術方法&lt;/li>
&lt;/ul>
&lt;h2 id="上下文管理器協議">上下文管理器協議&lt;/h2>
&lt;h3 id="__enter__-與-__exit__">&lt;code>__enter__&lt;/code> 與 &lt;code>__exit__&lt;/code>&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="k">class&lt;/span> &lt;span class="nc">ManagedResource&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>&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">name&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="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="bp">self&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">name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&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;Creating &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">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"> 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="fm">__enter__&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="s2">&amp;#34;ManagedResource&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;&amp;#34;&amp;#34;進入 with 區塊時呼叫
&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">
&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"> Returns:
&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"> as 子句綁定的物件
&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;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&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;Entering &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">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">15&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span> &lt;span class="c1"># 這會被 as 子句捕獲&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="fm">__exit__&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="bp">self&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">exc_type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="ne">BaseException&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">20&lt;/span>&lt;span class="cl"> &lt;span class="n">exc_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">BaseException&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">21&lt;/span>&lt;span class="cl"> &lt;span class="n">exc_tb&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">TracebackType&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 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">23&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;離開 with 區塊時呼叫
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&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">26&lt;/span>&lt;span class="cl">&lt;span class="s2"> exc_type: 異常類型（無異常時為 None）
&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"> exc_val: 異常實例（無異常時為 None）
&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"> exc_tb: 追蹤資訊（無異常時為 None）
&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"> Returns:
&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"> True 表示已處理異常，不再傳播
&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"> False 或 None 表示讓異常繼續傳播
&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Exiting &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">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">35&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">exc_type&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&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">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; Exception: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">exc_type&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">exc_val&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">37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span> &lt;span class="c1"># 讓異常繼續傳播&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="c1"># 使用&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">with&lt;/span> &lt;span class="n">ManagedResource&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;test&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">resource&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Using &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resource&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">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">42&lt;/span>&lt;span class="cl"> &lt;span class="c1"># raise ValueError(&amp;#34;oops&amp;#34;) # 取消註解測試異常處理&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="c1"># 輸出：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">&lt;span class="c1"># Creating test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="c1"># Entering test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="c1"># Using test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="c1"># Exiting test&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="__exit__-的異常處理">&lt;code>__exit__&lt;/code> 的異常處理&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">types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TracebackType&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">SuppressErrors&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>&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="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="o">*&lt;/span>&lt;span class="n">exceptions&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="ne">BaseException&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exceptions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">exceptions&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="k">def&lt;/span> &lt;span class="fm">__enter__&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="kc">None&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">pass&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">def&lt;/span> &lt;span class="fm">__exit__&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="bp">self&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">exc_type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="ne">BaseException&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">15&lt;/span>&lt;span class="cl"> &lt;span class="n">exc_val&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="ne">BaseException&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">16&lt;/span>&lt;span class="cl"> &lt;span class="n">exc_tb&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">TracebackType&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">17&lt;/span>&lt;span class="cl"> &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="c1"># 如果是我們要抑制的異常類型，返回 True&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">if&lt;/span> &lt;span class="n">exc_type&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">issubclass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">exc_type&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">exceptions&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Suppressed: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">exc_type&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">exc_val&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">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">True&lt;/span> &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="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用&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">with&lt;/span> &lt;span class="n">SuppressErrors&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="ne">KeyError&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This will be suppressed&amp;#34;&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>&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="s2">&amp;#34;Continues normally&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;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">DatabaseConnection&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;展示 __enter__ 返回值的用法&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">url&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="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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">url&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">_connection&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"> 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="fm">__enter__&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="s2">&amp;#34;Cursor&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_connection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">connect&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">url&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">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cursor&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 返回 cursor，不是 self&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">def&lt;/span> &lt;span class="fm">__exit__&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">*&lt;/span>&lt;span class="n">args&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">13&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">_connection&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_connection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">close&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="k">with&lt;/span> &lt;span class="n">DatabaseConnection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;postgres://...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">cursor&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="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="s2">&amp;#34;SELECT * FROM users&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="c1"># cursor 是 Cursor 物件，不是 DatabaseConnection&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="contextlib-工具">contextlib 工具&lt;/h2>
&lt;h3 id="contextmanager-裝飾器">@contextmanager 裝飾器&lt;/h3>
&lt;p>用生成器函式建立上下文管理器，比定義類別更簡潔：&lt;/p></description><content:encoded><![CDATA[<p>入門系列介紹了 <code>with</code> 語句的基本使用。本章深入探討上下文管理器的實現原理與進階應用，包括 <code>contextlib</code> 工具、嵌套組合、以及非同步上下文管理。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列的 <code>with</code> 語句使用</li>
<li>基本的類別定義與魔術方法</li>
</ul>
<h2 id="上下文管理器協議">上下文管理器協議</h2>
<h3 id="__enter__-與-__exit__"><code>__enter__</code> 與 <code>__exit__</code></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">ManagedResource</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></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">name</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"> 5</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"> 6</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Creating </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"> 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="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ManagedResource&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;進入 with 區塊時呼叫
</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">        Returns:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">            as 子句綁定的物件
</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Entering </span><span class="si">{</span><span class="bp">self</span><span class="o">.</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">15</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>  <span class="c1"># 這會被 as 子句捕獲</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">__exit__</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="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">exc_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="ne">BaseException</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">20</span><span class="cl">        <span class="n">exc_val</span><span class="p">:</span> <span class="ne">BaseException</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">exc_tb</span><span class="p">:</span> <span class="n">TracebackType</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="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;離開 with 區塊時呼叫
</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">            exc_type: 異常類型（無異常時為 None）
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">            exc_val: 異常實例（無異常時為 None）
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            exc_tb: 追蹤資訊（無異常時為 None）
</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">        Returns:
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">            True 表示已處理異常，不再傳播
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">            False 或 None 表示讓異常繼續傳播
</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Exiting </span><span class="si">{</span><span class="bp">self</span><span class="o">.</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">35</span><span class="cl">        <span class="k">if</span> <span class="n">exc_type</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">36</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  Exception: </span><span class="si">{</span><span class="n">exc_type</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">exc_val</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="k">return</span> <span class="kc">False</span>  <span class="c1"># 讓異常繼續傳播</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">with</span> <span class="n">ManagedResource</span><span class="p">(</span><span class="s2">&#34;test&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">resource</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Using </span><span class="si">{</span><span class="n">resource</span><span class="o">.</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">42</span><span class="cl">    <span class="c1"># raise ValueError(&#34;oops&#34;)  # 取消註解測試異常處理</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"># Creating test</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="c1"># Entering test</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="c1"># Using test</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="c1"># Exiting test</span></span></span></code></pre></div><h3 id="__exit__-的異常處理"><code>__exit__</code> 的異常處理</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">types</span> <span class="kn">import</span> <span class="n">TracebackType</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">SuppressErrors</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></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="o">*</span><span class="n">exceptions</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="ne">BaseException</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"> 7</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">exceptions</span> <span class="o">=</span> <span class="n">exceptions</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="fm">__enter__</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">10</span><span class="cl">        <span class="k">pass</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">__exit__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">exc_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="ne">BaseException</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">15</span><span class="cl">        <span class="n">exc_val</span><span class="p">:</span> <span class="ne">BaseException</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">exc_tb</span><span class="p">:</span> <span class="n">TracebackType</span> <span class="o">|</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <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="c1"># 如果是我們要抑制的異常類型，返回 True</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">if</span> <span class="n">exc_type</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">issubclass</span><span class="p">(</span><span class="n">exc_type</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">exceptions</span><span class="p">):</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;Suppressed: </span><span class="si">{</span><span class="n">exc_type</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">exc_val</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="k">return</span> <span class="kc">True</span>  <span class="c1"># 吞掉異常</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="kc">False</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">with</span> <span class="n">SuppressErrors</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">KeyError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;This will be suppressed&#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="nb">print</span><span class="p">(</span><span class="s2">&#34;Continues normally&#34;</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="k">class</span> <span class="nc">DatabaseConnection</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;展示 __enter__ 返回值的用法&#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">url</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"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="n">url</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">_connection</span> <span class="o">=</span> <span class="kc">None</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="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;Cursor&#34;</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">_connection</span> <span class="o">=</span> <span class="n">connect</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">url</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="bp">self</span><span class="o">.</span><span class="n">_connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>  <span class="c1"># 返回 cursor，不是 self</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">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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">13</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_connection</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_connection</span><span class="o">.</span><span class="n">close</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="k">with</span> <span class="n">DatabaseConnection</span><span class="p">(</span><span class="s2">&#34;postgres://...&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">cursor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;SELECT * FROM users&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># cursor 是 Cursor 物件，不是 DatabaseConnection</span></span></span></code></pre></div><h2 id="contextlib-工具">contextlib 工具</h2>
<h3 id="contextmanager-裝飾器">@contextmanager 裝飾器</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</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">Iterator</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">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">timer</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</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="s2">&#34;&#34;&#34;計時上下文管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Starting </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">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span>  <span class="c1"># 這裡是 with 區塊執行的地方</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</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">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">name</span><span class="si">}</span><span class="s2"> took </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">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="k">with</span> <span class="n">timer</span><span class="p">(</span><span class="s2">&#34;data processing&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">process_large_dataset</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"># 如果需要返回值</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="k">def</span> <span class="nf">temp_directory</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;建立臨時目錄，結束後自動清理&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="kn">import</span> <span class="nn">tempfile</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="kn">import</span> <span class="nn">shutil</span>
</span></span><span class="line"><span class="ln">27</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">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</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">tempfile</span><span class="o">.</span><span class="n">mkdtemp</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">yield</span> <span class="n">path</span>  <span class="c1"># path 會被 as 子句捕獲</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">shutil</span><span class="o">.</span><span class="n">rmtree</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></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">with</span> <span class="n">temp_directory</span><span class="p">()</span> <span class="k">as</span> <span class="n">tmpdir</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="p">(</span><span class="n">tmpdir</span> <span class="o">/</span> <span class="s2">&#34;test.txt&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="exitstack">ExitStack</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">ExitStack</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">process_multiple_files</span><span class="p">(</span><span class="n">filenames</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">list</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="s2">&#34;&#34;&#34;處理多個檔案，確保全部關閉&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">files</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">fn</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">for</span> <span class="n">fn</span> <span class="ow">in</span> <span class="n">filenames</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="k">return</span> <span class="p">[</span><span class="n">f</span><span class="o">.</span><span class="n">read</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">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">def</span> <span class="nf">connect_services</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="nb">dict</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">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;根據配置連接多個服務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">services</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">if</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;database&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">services</span><span class="p">[</span><span class="s2">&#34;db&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                <span class="n">DatabaseConnection</span><span class="p">(</span><span class="n">config</span><span class="p">[</span><span class="s2">&#34;database&#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="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;cache&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">services</span><span class="p">[</span><span class="s2">&#34;cache&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                <span class="n">CacheConnection</span><span class="p">(</span><span class="n">config</span><span class="p">[</span><span class="s2">&#34;cache&#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="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;queue&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">            <span class="n">services</span><span class="p">[</span><span class="s2">&#34;queue&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="n">QueueConnection</span><span class="p">(</span><span class="n">config</span><span class="p">[</span><span class="s2">&#34;queue&#34;</span><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></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="k">return</span> <span class="n">do_work</span><span class="p">(</span><span class="n">services</span><span class="p">)</span></span></span></code></pre></div><h3 id="exitstack-的回調功能">ExitStack 的回調功能</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">contextlib</span> <span class="kn">import</span> <span class="n">ExitStack</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">complex_setup</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"> 4</span><span class="cl">    <span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</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="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">&#34;Cleanup 1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">stack</span><span class="o">.</span><span class="n">callback</span><span class="p">(</span><span class="nb">print</span><span class="p">,</span> <span class="s2">&#34;Cleanup 2&#34;</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="n">cm</span> <span class="o">=</span> <span class="n">some_context_manager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">stack</span><span class="o">.</span><span class="n">push</span><span class="p">(</span><span class="n">cm</span><span class="p">)</span>  <span class="c1"># 不立即進入，但會在結束時呼叫 __exit__</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">pass</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="c1"># 1. cm.__exit__()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="c1"># 2. print(&#34;Cleanup 2&#34;)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="c1"># 3. print(&#34;Cleanup 1&#34;)  # 注意順序是相反的</span></span></span></code></pre></div><h3 id="nullcontext">nullcontext</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">nullcontext</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">process_data</span><span class="p">(</span><span class="n">lock</span><span class="p">:</span> <span class="n">Lock</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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"> 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">with</span> <span class="n">lock</span> <span class="k">if</span> <span class="n">lock</span> <span class="k">else</span> <span class="n">nullcontext</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="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="c1"># 更清楚的寫法</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">process_data</span><span class="p">(</span><span class="n">lock</span><span class="p">:</span> <span class="n">Lock</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">11</span><span class="cl">    <span class="n">cm</span> <span class="o">=</span> <span class="n">lock</span> <span class="k">if</span> <span class="n">lock</span> <span class="k">else</span> <span class="n">nullcontext</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">with</span> <span class="n">cm</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="c1"># 帶返回值的 nullcontext</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">nullcontext</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">get_stream</span><span class="p">(</span><span class="n">filename</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">TextIO</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">if</span> <span class="n">filename</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</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">nullcontext</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="p">)</span>  <span class="c1"># 返回 stdout</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">with</span> <span class="n">get_stream</span><span class="p">(</span><span class="kc">None</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">24</span><span class="cl">    <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&#34;Hello&#34;</span><span class="p">)</span>  <span class="c1"># 寫到 stdout</span></span></span></code></pre></div><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="c1"># 傳統嵌套</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;input.txt&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">infile</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="nb">open</span><span class="p">(</span><span class="s2">&#34;output.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">outfile</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">outfile</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">infile</span><span class="o">.</span><span class="n">read</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"># Python 3.9+ 可以用括號分組</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">with</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;input.txt&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">infile</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;output.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">outfile</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 class="n">outfile</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">infile</span><span class="o">.</span><span class="n">read</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"># 多個資源：ExitStack 更靈活</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">with</span> <span class="n">ExitStack</span><span class="p">()</span> <span class="k">as</span> <span class="n">stack</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="n">stack</span><span class="o">.</span><span class="n">enter_context</span><span class="p">(</span><span class="nb">open</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">filenames</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="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</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">Iterator</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">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">database_transaction</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Database</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">Transaction</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">tx</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">begin_transaction</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">yield</span> <span class="n">tx</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">tx</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">tx</span><span class="o">.</span><span class="n">rollback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">raise</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">@contextmanager</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">def</span> <span class="nf">acquire_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">:</span> <span class="n">Lock</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mi">30</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="kc">None</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="k">if</span> <span class="ow">not</span> <span class="n">lock</span><span class="o">.</span><span class="n">acquire</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">raise</span> <span class="ne">TimeoutError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Could not acquire lock within </span><span class="si">{</span><span class="n">timeout</span><span class="si">}</span><span class="s2">s&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">yield</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">lock</span><span class="o">.</span><span class="n">release</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">def</span> <span class="nf">safe_update</span><span class="p">(</span><span class="n">db</span><span class="p">:</span> <span class="n">Database</span><span class="p">,</span> <span class="n">lock</span><span class="p">:</span> <span class="n">Lock</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">Transaction</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 class="k">with</span> <span class="n">acquire_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">with</span> <span class="n">database_transaction</span><span class="p">(</span><span class="n">db</span><span class="p">)</span> <span class="k">as</span> <span class="n">tx</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">yield</span> <span class="n">tx</span></span></span></code></pre></div><h2 id="async-context-manager">async context manager</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="k">class</span> <span class="nc">AsyncResource</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></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="fm">__aenter__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;AsyncResource&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">connect</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></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">async</span> <span class="k">def</span> <span class="fm">__aexit__</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="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">exc_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="ne">BaseException</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">11</span><span class="cl">        <span class="n">exc_val</span><span class="p">:</span> <span class="ne">BaseException</span> <span class="o">|</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">exc_tb</span><span class="p">:</span> <span class="n">TracebackType</span> <span class="o">|</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <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="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="kc">False</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="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncResource</span><span class="p">()</span> <span class="k">as</span> <span class="n">resource</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">await</span> <span class="n">resource</span><span class="o">.</span><span class="n">do_something</span><span class="p">()</span></span></span></code></pre></div><h3 id="asynccontextmanager">@asynccontextmanager</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">contextlib</span> <span class="kn">import</span> <span class="n">asynccontextmanager</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">AsyncIterator</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">@asynccontextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">async_timer</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</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="s2">&#34;&#34;&#34;非同步計時器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Starting </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">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</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">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">name</span><span class="si">}</span><span class="s2"> took </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">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">async_timer</span><span class="p">(</span><span class="s2">&#34;async task&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></span></span></code></pre></div><h3 id="與-asyncio-模組的連結">與 asyncio 模組的連結</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">asyncio</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">asynccontextmanager</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">@asynccontextmanager</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">managed_task</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">asyncio</span><span class="o">.</span><span class="n">Task</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;管理 Task 生命週期&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">yield</span> <span class="n">task</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">task</span><span class="o">.</span><span class="n">done</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">task</span><span class="o">.</span><span class="n">cancel</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="k">await</span> <span class="n">task</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">except</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">CancelledError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                <span class="k">pass</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">async</span> <span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">managed_task</span><span class="p">(</span><span class="n">background_worker</span><span class="p">())</span> <span class="k">as</span> <span class="n">task</span><span class="p">:</span>
</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="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># 離開時自動取消 task</span></span></span></code></pre></div><h2 id="實際範例交易管理器">實際範例：交易管理器</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</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">Iterator</span><span class="p">,</span> <span class="n">Protocol</span>
</span></span><span class="line"><span class="ln">  3</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">  4</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 class="p">,</span> <span class="n">auto</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">TransactionState</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">    <span class="n">PENDING</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl">    <span class="n">ACTIVE</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">COMMITTED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">ROLLED_BACK</span> <span class="o">=</span> <span class="n">auto</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">class</span> <span class="nc">Transactional</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;可參與交易的資源協議&#34;&#34;&#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="k">def</span> <span class="nf">begin</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"> 16</span><span class="cl">        <span class="o">...</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">commit</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"> 19</span><span class="cl">        <span class="o">...</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">rollback</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"> 22</span><span class="cl">        <span class="o">...</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">Transaction</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="nb">id</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">resources</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Transactional</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"> 29</span><span class="cl">    <span class="n">state</span><span class="p">:</span> <span class="n">TransactionState</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">PENDING</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">def</span> <span class="nf">add_resource</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource</span><span class="p">:</span> <span class="n">Transactional</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"> 32</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">&#34;Transaction is not active&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="n">resource</span><span class="o">.</span><span class="n">begin</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">resources</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">resource</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="k">def</span> <span class="nf">commit</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"> 38</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s2">&#34;Transaction is not active&#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="k">try</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">resource</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">resources</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                <span class="n">resource</span><span class="o">.</span><span class="n">commit</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">state</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">COMMITTED</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">rollback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="k">raise</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">def</span> <span class="nf">rollback</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"> 50</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">,</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">PENDING</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">            <span class="k">return</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">errors</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">resources</span><span class="p">):</span>
</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="n">resource</span><span class="o">.</span><span class="n">rollback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 57</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"> 58</span><span class="cl">                <span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">e</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="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ROLLED_BACK</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</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"> 63</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span><span class="s2">&#34;Rollback failed&#34;</span><span class="p">,</span> <span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="k">class</span> <span class="nc">TransactionManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="s2">&#34;&#34;&#34;交易管理器&#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</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"> 69</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span> <span class="o">=</span> <span class="mi">0</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">    <span class="k">def</span> <span class="nf">transaction</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">Transaction</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="s2">&#34;&#34;&#34;建立新交易&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">tx</span> <span class="o">=</span> <span class="n">Transaction</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;tx-</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">tx</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">            <span class="k">yield</span> <span class="n">tx</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="n">tx</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="n">tx</span><span class="o">.</span><span class="n">rollback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="k">raise</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="c1"># 使用範例</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl"><span class="k">class</span> <span class="nc">DatabaseResource</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬資料庫資源&#34;&#34;&#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="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 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"> 90</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"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="k">def</span> <span class="nf">begin</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"> 93</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">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: BEGIN&#34;</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="k">def</span> <span class="nf">commit</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"> 96</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">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: COMMIT&#34;</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">def</span> <span class="nf">rollback</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"> 99</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">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: ROLLBACK&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</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">102</span><span class="cl">    <span class="n">manager</span> <span class="o">=</span> <span class="n">TransactionManager</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="n">db1</span> <span class="o">=</span> <span class="n">DatabaseResource</span><span class="p">(</span><span class="s2">&#34;primary&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="n">db2</span> <span class="o">=</span> <span class="n">DatabaseResource</span><span class="p">(</span><span class="s2">&#34;replica&#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">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">transaction</span><span class="p">()</span> <span class="k">as</span> <span class="n">tx</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">tx</span><span class="o">.</span><span class="n">add_resource</span><span class="p">(</span><span class="n">db1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="n">tx</span><span class="o">.</span><span class="n">add_resource</span><span class="p">(</span><span class="n">db2</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="c1"># 做一些操作...</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Doing work in transaction&#34;</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="c1"># 如果拋出異常，兩個資源都會 rollback</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">        <span class="c1"># raise ValueError(&#34;Something went wrong&#34;)</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="c1"># 正常結束時，兩個資源都會 commit</span></span></span></code></pre></div><h2 id="常見錯誤">常見錯誤</h2>
<h3 id="1-忘記-yield">1. 忘記 yield</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">broken</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;enter&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="c1"># 忘記 yield！</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="s2">&#34;exit&#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="c1"># 使用時會報錯：generator didn&#39;t yield</span></span></span></code></pre></div><h3 id="2-在-finally-中-yield">2. 在 finally 中 yield</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">also_broken</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">yield</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">yield</span>  <span class="c1"># 錯誤！不能在 finally 中 yield</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">risky</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">resource</span> <span class="o">=</span> <span class="n">acquire_resource</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">yield</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">release_resource</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>  <span class="c1"># 如果 with 區塊拋異常，這行不會執行！</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">safe</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">resource</span> <span class="o">=</span> <span class="n">acquire_resource</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">yield</span> <span class="n">resource</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">release_resource</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>  <span class="c1"># 一定會執行</span></span></span></code></pre></div><h2 id="小結">小結</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>__enter__</code>/<code>__exit__</code></td>
          <td>上下文管理器協議</td>
      </tr>
      <tr>
          <td><code>@contextmanager</code></td>
          <td>用生成器建立上下文管理器</td>
      </tr>
      <tr>
          <td><code>ExitStack</code></td>
          <td>動態管理多個上下文</td>
      </tr>
      <tr>
          <td><code>nullcontext</code></td>
          <td>可選的上下文管理器</td>
      </tr>
      <tr>
          <td><code>async with</code></td>
          <td>非同步上下文管理</td>
      </tr>
      <tr>
          <td><code>@asynccontextmanager</code></td>
          <td>用非同步生成器建立上下文管理器</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li><code>__exit__</code> 返回 <code>True</code> 和 <code>False</code> 的差別是什麼？</li>
<li>什麼時候應該用 <code>ExitStack</code> 而不是巢狀 <code>with</code>？</li>
<li><code>@contextmanager</code> 中 <code>yield</code> 前後的程式碼分別對應什麼？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.5.2 異常設計架構</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計</a></em></p>
]]></content:encoded></item><item><title>模組三：進階設計模式</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/</guid><description>&lt;p>本模組將元編程的「機制理解」轉化為「架構設計能力」，學習如何建立型別安全、錯誤處理完善、可擴展的 Python 系統。&lt;/p>
&lt;h2 id="為什麼需要這個模組">為什麼需要這個模組？&lt;/h2>
&lt;p>在入門系列中，我們學習了：&lt;/p>
&lt;ul>
&lt;li>型別提示的基礎用法&lt;/li>
&lt;li>異常處理的基本策略&lt;/li>
&lt;li>&lt;code>with&lt;/code> 語句的使用&lt;/li>
&lt;li>單例與快取模式&lt;/li>
&lt;/ul>
&lt;p>這些知識足以應付日常開發，但當你要設計：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>可重用的 Repository 層&lt;/strong>：需要泛型進階技巧&lt;/li>
&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;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">模組二 元編程 → 模組 3.5 設計模式 → 模組三 CPython
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&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;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/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1&lt;/a>&lt;/td>
 &lt;td>泛型進階&lt;/td>
 &lt;td>建立型別安全的抽象層&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2&lt;/a>&lt;/td>
 &lt;td>異常設計架構&lt;/td>
 &lt;td>設計大型專案的錯誤處理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3&lt;/a>&lt;/td>
 &lt;td>進階上下文管理&lt;/td>
 &lt;td>複雜資源的優雅管理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.4&lt;/a>&lt;/td>
 &lt;td>插件系統設計&lt;/td>
 &lt;td>建立可擴展的架構&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/integration/" data-link-title="3.5.5 設計模式整合案例" data-link-desc="結合泛型、異常、上下文、插件建立完整系統">3.5&lt;/a>&lt;/td>
 &lt;td>設計模式整合案例&lt;/td>
 &lt;td>綜合應用所有模式&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/trade-offs/" data-link-title="3.5.6 軟體設計的取捨藝術" data-link-desc="從業界經驗學習取捨決策框架：DRY vs 重複、效能 vs 可讀性、Build vs Buy、技術債務管理">3.6&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/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理&lt;/a>&lt;/td>
 &lt;td>config_loader.py&lt;/td>
 &lt;td>Context Manager&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Protocol + 註冊機制&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>異常階層 + ExceptionGroup&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Generic + TypeVar&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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>3.1 泛型進階&lt;/td>
 &lt;td>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3.2 異常設計&lt;/td>
 &lt;td>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3.3 上下文管理&lt;/td>
 &lt;td>入門系列的 &lt;code>with&lt;/code> 語句使用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3.4 插件系統&lt;/td>
 &lt;td>本進階系列 &lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&lt;/a>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3.5 整合案例&lt;/td>
 &lt;td>本模組 3.1-3.4&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3.6 取捨藝術&lt;/td>
 &lt;td>本模組 3.1-3.5（應用全部設計模式的決策思維）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="學習路徑">學習路徑&lt;/h2>
&lt;p>本模組適合以下學習路徑：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>路徑 A（Web/API 開發）&lt;/strong>：asyncio → &lt;strong>設計模式&lt;/strong> → 打包&lt;/li>
&lt;li>&lt;strong>路徑 B（框架開發）&lt;/strong>：元編程 → &lt;strong>設計模式&lt;/strong> → CPython → 打包&lt;/li>
&lt;/ul>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>每章節約 30-45 分鐘，全模組約 3-4 小時&lt;/p>
&lt;hr>
&lt;p>&lt;em>上一模組：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&lt;/a>&lt;/em>
&lt;em>下一模組：&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;/em>&lt;/p></description><content:encoded><![CDATA[<p>本模組將元編程的「機制理解」轉化為「架構設計能力」，學習如何建立型別安全、錯誤處理完善、可擴展的 Python 系統。</p>
<h2 id="為什麼需要這個模組">為什麼需要這個模組？</h2>
<p>在入門系列中，我們學習了：</p>
<ul>
<li>型別提示的基礎用法</li>
<li>異常處理的基本策略</li>
<li><code>with</code> 語句的使用</li>
<li>單例與快取模式</li>
</ul>
<p>這些知識足以應付日常開發，但當你要設計：</p>
<ul>
<li><strong>可重用的 Repository 層</strong>：需要泛型進階技巧</li>
<li><strong>大型專案的錯誤處理架構</strong>：需要異常層級設計</li>
<li><strong>資源管理複雜的系統</strong>：需要進階上下文管理</li>
<li><strong>支援擴展的框架</strong>：需要插件系統設計</li>
</ul>
<p>你就需要本模組的進階設計模式。</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">模組二 元編程 → 模組 3.5 設計模式 → 模組三 CPython
</span></span><span class="line"><span class="ln">2</span><span class="cl">   (機制)           (應用)            (底層)</span></span></code></pre></div><p>元編程教你「如何建立抽象機制」，本模組教你「如何應用這些機制設計系統」。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1</a></td>
          <td>泛型進階</td>
          <td>建立型別安全的抽象層</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2</a></td>
          <td>異常設計架構</td>
          <td>設計大型專案的錯誤處理</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3</a></td>
          <td>進階上下文管理</td>
          <td>複雜資源的優雅管理</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.4</a></td>
          <td>插件系統設計</td>
          <td>建立可擴展的架構</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/integration/" data-link-title="3.5.5 設計模式整合案例" data-link-desc="結合泛型、異常、上下文、插件建立完整系統">3.5</a></td>
          <td>設計模式整合案例</td>
          <td>綜合應用所有模式</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/trade-offs/" data-link-title="3.5.6 軟體設計的取捨藝術" data-link-desc="從業界經驗學習取捨決策框架：DRY vs 重複、效能 vs 可讀性、Build vs Buy、技術債務管理">3.6</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/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></td>
          <td>config_loader.py</td>
          <td>Context Manager</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></td>
          <td>hook_validator.py</td>
          <td>Protocol + 註冊機制</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></td>
          <td>hook_io.py</td>
          <td>異常階層 + ExceptionGroup</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></td>
          <td>hook_validator.py</td>
          <td>Generic + TypeVar</td>
      </tr>
  </tbody>
</table>
<h2 id="先備知識">先備知識</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>需要先讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>3.1 泛型進階</td>
          <td>入門系列 <a href="/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型</a></td>
      </tr>
      <tr>
          <td>3.2 異常設計</td>
          <td>入門系列 <a href="/blog/python/05-error-testing/exception/" data-link-title="5.1 異常處理策略" data-link-desc="何時捕獲、何時拋出">5.1 異常處理策略</a></td>
      </tr>
      <tr>
          <td>3.3 上下文管理</td>
          <td>入門系列的 <code>with</code> 語句使用</td>
      </tr>
      <tr>
          <td>3.4 插件系統</td>
          <td>本進階系列 <a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></td>
      </tr>
      <tr>
          <td>3.5 整合案例</td>
          <td>本模組 3.1-3.4</td>
      </tr>
      <tr>
          <td>3.6 取捨藝術</td>
          <td>本模組 3.1-3.5（應用全部設計模式的決策思維）</td>
      </tr>
  </tbody>
</table>
<h2 id="學習路徑">學習路徑</h2>
<p>本模組適合以下學習路徑：</p>
<ul>
<li><strong>路徑 A（Web/API 開發）</strong>：asyncio → <strong>設計模式</strong> → 打包</li>
<li><strong>路徑 B（框架開發）</strong>：元編程 → <strong>設計模式</strong> → CPython → 打包</li>
</ul>
<h2 id="學習時間">學習時間</h2>
<p>每章節約 30-45 分鐘，全模組約 3-4 小時</p>
<hr>
<p><em>上一模組：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</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/03-design-patterns/case-studies/generic-validator/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_validator.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">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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&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"> 6&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Single validation issue&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">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"> 9&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">10&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">11&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">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="nd">@dataclass&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">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">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validation result for a single hook&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">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">17&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">18&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">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="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">21&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Calculate is_compliant status&amp;#34;&amp;#34;&amp;#34;&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 compliance validator - specific to Path type&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;Validate a single hook file&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="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">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&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">34&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">35&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">36&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 class="n">ValidationIssue&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">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">38&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 file not found: &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">39&lt;/span>&lt;span class="cl"> &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="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"># ... more validation logic ...&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">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>&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">def&lt;/span> &lt;span class="nf">_resolve_path&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">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">Path&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="s2">&amp;#34;&amp;#34;&amp;#34;Resolve path to absolute path&amp;#34;&amp;#34;&amp;#34;&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">p&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">path&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="k">return&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_absolute&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cwd&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">p&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>：專為驗證 Hook 檔案設計，邏輯清晰&lt;/li>
&lt;li>&lt;strong>型別明確&lt;/strong>：輸入是 &lt;code>str&lt;/code>，輸出是 &lt;code>ValidationResult&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要驗證其他類型時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>需要複製大量相似程式碼&lt;/strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator&lt;/li>
&lt;li>&lt;strong>驗證邏輯無法重用&lt;/strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享&lt;/li>
&lt;li>&lt;strong>型別檢查不夠通用&lt;/strong>：&lt;code>ValidationResult&lt;/code> 綁定了 &lt;code>hook_path: str&lt;/code>，無法用於其他場景&lt;/li>
&lt;/ul>
&lt;h2 id="進階解決方案">進階解決方案&lt;/h2>
&lt;h3 id="設計目標">設計目標&lt;/h3>
&lt;ol>
&lt;li>&lt;strong>建立通用的驗證器介面&lt;/strong>：定義 &lt;code>Validator[T]&lt;/code> 協議&lt;/li>
&lt;li>&lt;strong>支援任意輸入類型&lt;/strong>：可以驗證 &lt;code>Path&lt;/code>、&lt;code>str&lt;/code>、&lt;code>dict&lt;/code>、自訂類別&lt;/li>
&lt;li>&lt;strong>保持型別安全&lt;/strong>：靜態型別檢查器能捕捉型別錯誤&lt;/li>
&lt;li>&lt;strong>支援驗證器組合&lt;/strong>：用 &lt;code>And&lt;/code>、&lt;code>Or&lt;/code>、&lt;code>Not&lt;/code> 組合基本驗證器&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議&lt;/h4>
&lt;p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&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">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"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">abstractmethod&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"># Define a type variable for the input type&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">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># Type variable with contravariance for Protocol&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">T_contra&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T_contra&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contravariant&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">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="nd">@dataclass&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">class&lt;/span> &lt;span class="nc">ValidationResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&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;
&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"> Generic validation result
&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">
&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"> Type parameter T represents the validated value type.
&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"> This allows type-safe access to the validated value.
&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;&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&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">is_valid&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">21&lt;/span>&lt;span class="cl"> &lt;span class="n">errors&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">=&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">22&lt;/span>&lt;span class="cl"> &lt;span class="n">warnings&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">=&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">23&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">add_error&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">message&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="s2">&amp;#34;ValidationResult[T]&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="s2">&amp;#34;&amp;#34;&amp;#34;Add an error and mark as invalid&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&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="n">message&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_valid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&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">return&lt;/span> &lt;span class="bp">self&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="k">def&lt;/span> &lt;span class="nf">add_warning&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">message&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="s2">&amp;#34;ValidationResult[T]&amp;#34;&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="s2">&amp;#34;&amp;#34;&amp;#34;Add a warning (does not affect validity)&amp;#34;&amp;#34;&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warnings&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">message&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="k">return&lt;/span> &lt;span class="bp">self&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="k">class&lt;/span> &lt;span class="nc">Validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T_contra&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="s2">&amp;#34;&amp;#34;&amp;#34;
&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"> Generic validator protocol
&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">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="s2"> Any class that implements validate(value: T) -&amp;gt; ValidationResult[T]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> is considered a Validator[T].
&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">
&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"> Using contravariant type variable because validators consume values.
&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"> A Validator[Animal] can validate Dog (subtype), so it&amp;#39;s contravariant.
&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"> &amp;#34;&amp;#34;&amp;#34;&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">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T_contra&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">47&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate the given value and return the result&amp;#34;&amp;#34;&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="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>關鍵設計決策&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Generic 和 TypeVar 建立型別安全的通用驗證器。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/02-type-system/optional-union/" data-link-title="2.2 Optional、Union、泛型" data-link-desc="處理可能為 None 的值和複合型別">2.2 Optional、Union、泛型</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.5.1 泛型進階</a></li>
</ul>
<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="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></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">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Single validation issue&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</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">10</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">11</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">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">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</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">17</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">18</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">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">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Calculate is_compliant status&#34;&#34;&#34;</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 compliance validator - specific to Path type&#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;Validate a single hook file&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</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">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</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">34</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">35</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">36</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">37</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">38</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Hook file not found: </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">39</span><span class="cl">                <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"># ... more validation logic ...</span>
</span></span><span class="line"><span class="ln">43</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></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">_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">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Resolve path to absolute path&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</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">48</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="n">Path</span><span class="o">.</span><span class="n">cwd</span><span class="p">()</span> <span class="o">/</span> <span class="n">p</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>針對具體需求設計</strong>：專為驗證 Hook 檔案設計，邏輯清晰</li>
<li><strong>型別明確</strong>：輸入是 <code>str</code>，輸出是 <code>ValidationResult</code></li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要驗證其他類型時：</p>
<ul>
<li><strong>需要複製大量相似程式碼</strong>：如果要驗證 API 回應、設定檔、表單輸入，需要寫多個類似的 Validator</li>
<li><strong>驗證邏輯無法重用</strong>：「檢查是否為空」「檢查格式」這些通用邏輯無法跨 Validator 共享</li>
<li><strong>型別檢查不夠通用</strong>：<code>ValidationResult</code> 綁定了 <code>hook_path: str</code>，無法用於其他場景</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>建立通用的驗證器介面</strong>：定義 <code>Validator[T]</code> 協議</li>
<li><strong>支援任意輸入類型</strong>：可以驗證 <code>Path</code>、<code>str</code>、<code>dict</code>、自訂類別</li>
<li><strong>保持型別安全</strong>：靜態型別檢查器能捕捉型別錯誤</li>
<li><strong>支援驗證器組合</strong>：用 <code>And</code>、<code>Or</code>、<code>Not</code> 組合基本驗證器</li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1定義泛型-validator-協議">步驟 1：定義泛型 Validator 協議</h4>
<p>首先，我們需要定義「什麼是驗證器」。使用 Protocol 和 TypeVar 來建立泛型介面：</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">typing</span> <span class="kn">import</span> <span class="n">Protocol</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"> 2</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"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</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"># Define a type variable for the input type</span>
</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="s2">&#34;T&#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"># Type variable with contravariance for Protocol</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</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">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">    Generic validation result
</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">    Type parameter T represents the validated value type.
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    This allows type-safe access to the validated value.
</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 class="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln">20</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">21</span><span class="cl">    <span class="n">errors</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">=</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">22</span><span class="cl">    <span class="n">warnings</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">=</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">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">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</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="kc">False</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidationResult[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</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">    Generic validator protocol
</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">    Any class that implements validate(value: T) -&gt; ValidationResult[T]
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">    is considered a Validator[T].
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="s2">    Using contravariant type variable because validators consume values.
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">    A Validator[Animal] can validate Dog (subtype), so it&#39;s contravariant.
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</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">47</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value and return the result&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><p><strong>關鍵設計決策</strong>：</p>
<ul>
<li><code>T_contra</code> 使用逆變（contravariant），因為驗證器是「消費者」</li>
<li><code>ValidationResult[T]</code> 是泛型，讓結果可以攜帶原始值的型別資訊</li>
<li>Protocol 而非 ABC，支援結構型子型別（不需要顯式繼承）</li>
</ul>
<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="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 class="kn">import</span> <span class="nn">re</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">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#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="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</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="o">-&gt;</span> <span class="n">ValidationResult</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="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</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="n">result</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">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="n">must_be_dir</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</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">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</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">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</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="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</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">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </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">25</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </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">27</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</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">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </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">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">return</span> <span class="n">result</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">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string matches a regex pattern&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">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="n">pattern</span><span class="p">)</span>
</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">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Value must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</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">validate</span><span class="p">(</span><span class="bp">self</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="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</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="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</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="n">result</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">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a number is within a range&#34;&#34;&#34;</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></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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 class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</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">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</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="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is below minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</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="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</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">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> is above maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#34;</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="k">return</span> <span class="n">result</span></span></span></code></pre></div><h4 id="步驟-3驗證器組合andornot">步驟 3：驗證器組合（And、Or、Not）</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Sequence</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">AndValidator</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"> 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">    Combine multiple validators with AND logic
</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">    All validators must pass for the result to be valid.
</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</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">validators</span> <span class="o">=</span> <span class="n">validators</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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</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">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</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">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</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="n">result</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="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">return</span> <span class="n">result</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">class</span> <span class="nc">OrValidator</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">25</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    Combine multiple validators with OR logic
</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">    At least one validator must pass for the result to be valid.
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="n">validators</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="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">all_errors</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">=</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">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">                <span class="c1"># At least one passed, return success</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</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="n">result</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</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="c1"># All failed</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</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;None of the validators passed. Errors: </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">all_errors</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</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="k">return</span> <span class="n">result</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">class</span> <span class="nc">NotValidator</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">53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="s2">    Negate a validator
</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">    The result is valid if the inner validator fails.
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</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">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="c1"># If inner failed, outer succeeds</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">        <span class="k">return</span> <span class="n">result</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="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"> 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">ValidatorBuilder</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"> 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">    Fluent builder for composing validators
</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">    Provides a chainable API for building complex validators.
</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="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">11</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</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">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">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator to the chain&#34;&#34;&#34;</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">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</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="bp">self</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">add_if</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="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">condition</span><span class="p">:</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="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</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="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;ValidatorBuilder[T]&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</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="bp">self</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">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build the final AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</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="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</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="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</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="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build with OR logic instead of AND&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</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="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</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="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</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">    Create a type-safe validator builder
</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">    Usage:
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        validator = (
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">            validator_for(str)
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">            .add(NotEmptyValidator())
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">            .add(PatternValidator(r&#34;^[a-z]+$&#34;))
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">            .build()
</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">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</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">Generic Validator System
</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">A type-safe, composable validation framework using Generic and TypeVar.
</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">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 10</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"> 11</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"> 12</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">Callable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="n">Generic</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">Protocol</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="n">Sequence</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">TypeVar</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">runtime_checkable</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="kn">import</span> <span class="nn">re</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"># ===== Type Variables =====</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">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="n">T_contra</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T_contra&#34;</span><span class="p">,</span> <span class="n">contravariant</span><span class="o">=</span><span class="kc">True</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"># ===== Core Types =====</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl"><span class="k">class</span> <span class="nc">ValidationResult</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"> 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="s2">    Generic validation result
</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">    Attributes:
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">        value: The validated value (preserves type information)
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">        is_valid: Whether validation passed
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        errors: List of error messages (cause validation failure)
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">        warnings: List of warning messages (informational only)
</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="n">value</span><span class="p">:</span> <span class="n">T</span>
</span></span><span class="line"><span class="ln"> 41</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"> 42</span><span class="cl">    <span class="n">errors</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">=</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"> 43</span><span class="cl">    <span class="n">warnings</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">=</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"> 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">add_error</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</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 class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add an error and mark as invalid&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 48</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="kc">False</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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">def</span> <span class="nf">add_warning</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</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 class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a warning (does not affect validity)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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="fm">__bool__</span><span class="p">(</span><span class="bp">self</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"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Allow using result in boolean context&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_valid</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="nd">@runtime_checkable</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="k">class</span> <span class="nc">Validator</span><span class="p">(</span><span class="n">Protocol</span><span class="p">[</span><span class="n">T_contra</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">    Generic validator protocol
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 65</span><span class="cl"><span class="s2">    Any class implementing validate(value) -&gt; ValidationResult
</span></span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="s2">    satisfies this protocol.
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T_contra</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"> 70</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate the given value&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="o">...</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"># ===== Basic Validators =====</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">class</span> <span class="nc">NotEmptyValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a string is not empty&#34;&#34;&#34;</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</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="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="s2">&#34;Value cannot be empty&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="k">return</span> <span class="n">result</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">class</span> <span class="nc">PathExistsValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate that a path exists&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="n">must_be_file</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">must_be_dir</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"> 91</span><span class="cl">    <span class="p">):</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">must_be_file</span> <span class="o">=</span> <span class="n">must_be_file</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">must_be_dir</span> <span class="o">=</span> <span class="n">must_be_dir</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Path</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</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="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path does not exist: </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">100</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_file</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a file: </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">102</span><span class="cl">        <span class="k">elif</span> <span class="bp">self</span><span class="o">.</span><span class="n">must_be_dir</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">is_dir</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Path is not a directory: </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">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">return</span> <span class="n">result</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">class</span> <span class="nc">PatternValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string matches a regex pattern&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</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="bp">self</span><span class="o">.</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 class="n">pattern</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">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="sa">f</span><span class="s2">&#34;Must match pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</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">validate</span><span class="p">(</span><span class="bp">self</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="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</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">pattern</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</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">result</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="k">class</span> <span class="nc">RangeValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate number is within range&#34;&#34;&#34;</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="fm">__init__</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">min_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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 class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="o">=</span> <span class="n">min_value</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="o">=</span> <span class="n">max_value</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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">float</span> <span class="o">|</span> <span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_value</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">value</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Value </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_value</span><span class="si">}</span><span class="s2">&#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="n">result</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">class</span> <span class="nc">LengthValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate string length&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</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="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</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="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">151</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">152</span><span class="cl">        <span class="n">length</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</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="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</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_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">length</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Length </span><span class="si">{</span><span class="n">length</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</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="k">return</span> <span class="n">result</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="k">class</span> <span class="nc">TypeValidator</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">162</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate value is of expected type&#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">expected_type</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">type_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">expected_type</span> <span class="o">=</span> <span class="n">expected_type</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">type_name</span> <span class="o">=</span> <span class="n">type_name</span> <span class="ow">or</span> <span class="n">expected_type</span><span class="o">.</span><span class="vm">__name__</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="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">169</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="bp">self</span><span class="o">.</span><span class="n">expected_type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">170</span><span class="cl">            <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">172</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># type: ignore</span>
</span></span><span class="line"><span class="ln">173</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">                <span class="sa">f</span><span class="s2">&#34;Expected </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">type_name</span><span class="si">}</span><span class="s2">, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">            <span class="k">return</span> <span class="n">result</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="c1"># ===== Composite Validators =====</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">
</span></span><span class="line"><span class="ln">180</span><span class="cl"><span class="k">class</span> <span class="nc">AndValidator</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">181</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with AND logic (all must pass)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">
</span></span><span class="line"><span class="ln">183</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">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</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">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</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="n">result</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="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl"><span class="k">class</span> <span class="nc">OrValidator</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">198</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Combine validators with OR logic (at least one must pass)&#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validators</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]]):</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validators</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl">
</span></span><span class="line"><span class="ln">203</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="n">all_errors</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">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">
</span></span><span class="line"><span class="ln">207</span><span class="cl">        <span class="k">for</span> <span class="n">validator</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">validators</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">209</span><span class="cl">            <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">warnings</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</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="n">result</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">            <span class="n">all_errors</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">213</span><span class="cl">
</span></span><span class="line"><span class="ln">214</span><span class="cl">        <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No validator passed: </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">all_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">215</span><span class="cl">        <span class="k">return</span> <span class="n">result</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="k">class</span> <span class="nc">NotValidator</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">218</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Negate a validator (passes if inner validator fails)&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span> <span class="n">error_message</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">222</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_message</span> <span class="o">=</span> <span class="n">error_message</span> <span class="ow">or</span> <span class="s2">&#34;Validation should have failed&#34;</span>
</span></span><span class="line"><span class="ln">223</span><span class="cl">
</span></span><span class="line"><span class="ln">224</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">T</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">        <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">227</span><span class="cl">
</span></span><span class="line"><span class="ln">228</span><span class="cl">        <span class="k">if</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">error_message</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">232</span><span class="cl">
</span></span><span class="line"><span class="ln">233</span><span class="cl"><span class="c1"># ===== Builder =====</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">
</span></span><span class="line"><span class="ln">235</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatorBuilder</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">236</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Fluent builder for composing validators&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Validator</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">240</span><span class="cl">
</span></span><span class="line"><span class="ln">241</span><span class="cl">    <span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">        <span class="k">return</span> <span class="bp">self</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">def</span> <span class="nf">add_if</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">248</span><span class="cl">        <span class="n">condition</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">249</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Conditionally add a validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">252</span><span class="cl">        <span class="k">if</span> <span class="n">condition</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">validator</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">254</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">255</span><span class="cl">
</span></span><span class="line"><span class="ln">256</span><span class="cl">    <span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">257</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build AND-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">258</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">        <span class="k">return</span> <span class="n">AndValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">
</span></span><span class="line"><span class="ln">264</span><span class="cl">    <span class="k">def</span> <span class="nf">build_or</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">265</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Build OR-combined validator&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">267</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;No validators added&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">269</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">        <span class="k">return</span> <span class="n">OrValidator</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_validators</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">271</span><span class="cl">
</span></span><span class="line"><span class="ln">272</span><span class="cl"><span class="k">def</span> <span class="nf">validator_for</span><span class="p">(</span><span class="n">type_hint</span><span class="p">:</span> <span class="nb">type</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">273</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Create a type-safe validator builder&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">274</span><span class="cl">    <span class="k">return</span> <span class="n">ValidatorBuilder</span><span class="p">[</span><span class="n">T</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="c1"># ===== List Validator =====</span>
</span></span><span class="line"><span class="ln">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl"><span class="k">class</span> <span class="nc">ListValidator</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">279</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validate each element in a list&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">282</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">283</span><span class="cl">        <span class="n">element_validator</span><span class="p">:</span> <span class="n">Validator</span><span class="p">[</span><span class="n">T</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">        <span class="n">min_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">286</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span> <span class="o">=</span> <span class="n">element_validator</span>
</span></span><span class="line"><span class="ln">288</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</span>
</span></span><span class="line"><span class="ln">290</span><span class="cl">
</span></span><span class="line"><span class="ln">291</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</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="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">ValidationResult</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="n">T</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln">292</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">293</span><span class="cl">
</span></span><span class="line"><span class="ln">294</span><span class="cl">        <span class="c1"># Check list length</span>
</span></span><span class="line"><span class="ln">295</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">296</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &lt; minimum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">min_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">297</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;List length </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="si">}</span><span class="s2"> &gt; maximum </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</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"># Validate each element</span>
</span></span><span class="line"><span class="ln">301</span><span class="cl">        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">            <span class="n">sub_result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">element_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">303</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_error</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</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">305</span><span class="cl">            <span class="k">for</span> <span class="n">warning</span> <span class="ow">in</span> <span class="n">sub_result</span><span class="o">.</span><span class="n">warnings</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">306</span><span class="cl">                <span class="n">result</span><span class="o">.</span><span class="n">add_warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">warning</span><span class="si">}</span><span class="s2">&#34;</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="k">return</span> <span class="n">result</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"># ===== Demo =====</span>
</span></span><span class="line"><span class="ln">311</span><span class="cl">
</span></span><span class="line"><span class="ln">312</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">313</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;=== Generic Validator Demo ===</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">314</span><span class="cl">
</span></span><span class="line"><span class="ln">315</span><span class="cl">    <span class="c1"># Example 1: Basic validators</span>
</span></span><span class="line"><span class="ln">316</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;1. Basic Validators&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">317</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">40</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="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;&#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">321</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  NotEmpty(&#39;hello&#39;): </span><span class="si">{</span><span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s1">&#39;hello&#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">322</span><span class="cl">
</span></span><span class="line"><span class="ln">323</span><span class="cl">    <span class="c1"># Example 2: Path validator</span>
</span></span><span class="line"><span class="ln">324</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. Path Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">325</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">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">
</span></span><span class="line"><span class="ln">327</span><span class="cl">    <span class="n">path_validator</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">328</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">329</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /etc/hosts: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">330</span><span class="cl">
</span></span><span class="line"><span class="ln">331</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">path_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/nonexistent&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">332</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  /nonexistent: valid=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="si">}</span><span class="s2">, errors=</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">333</span><span class="cl">
</span></span><span class="line"><span class="ln">334</span><span class="cl">    <span class="c1"># Example 3: Composed validators</span>
</span></span><span class="line"><span class="ln">335</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">3. Composed Validators (AND)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">336</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">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">337</span><span class="cl">
</span></span><span class="line"><span class="ln">338</span><span class="cl">    <span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">        <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">        <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">341</span><span class="cl">        <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">,</span> <span class="s2">&#34;Must be lowercase alphanumeric&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">342</span><span class="cl">    <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="n">test_usernames</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;ab&#34;</span><span class="p">,</span> <span class="s2">&#34;valid_user&#34;</span><span class="p">,</span> <span class="s2">&#34;Invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span> <span class="o">*</span> <span class="mi">25</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">345</span><span class="cl">    <span class="k">for</span> <span class="n">username</span> <span class="ow">in</span> <span class="n">test_usernames</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">346</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">347</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">348</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  &#39;</span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">349</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">350</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</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">error</span><span class="si">}</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="c1"># Example 4: Builder pattern</span>
</span></span><span class="line"><span class="ln">354</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">4. Builder Pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">355</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">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">356</span><span class="cl">
</span></span><span class="line"><span class="ln">357</span><span class="cl">    <span class="n">email_validator</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">        <span class="n">validator_for</span><span class="p">(</span><span class="nb">str</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">        <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">361</span><span class="cl">            <span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">362</span><span class="cl">            <span class="s2">&#34;Invalid email format&#34;</span>
</span></span><span class="line"><span class="ln">363</span><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="ln">364</span><span class="cl">        <span class="o">.</span><span class="n">build</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">365</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">366</span><span class="cl">
</span></span><span class="line"><span class="ln">367</span><span class="cl">    <span class="n">test_emails</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;invalid&#34;</span><span class="p">,</span> <span class="s2">&#34;test@example.com&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">368</span><span class="cl">    <span class="k">for</span> <span class="n">email</span> <span class="ow">in</span> <span class="n">test_emails</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">369</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">email</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">370</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</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;  &#39;</span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">372</span><span class="cl">
</span></span><span class="line"><span class="ln">373</span><span class="cl">    <span class="c1"># Example 5: List validator</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="s2">&#34;</span><span class="se">\n</span><span class="s2">5. List Validator&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">375</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">40</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">376</span><span class="cl">
</span></span><span class="line"><span class="ln">377</span><span class="cl">    <span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">378</span><span class="cl">        <span class="n">element_validator</span><span class="o">=</span><span class="n">AndValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">379</span><span class="cl">            <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">380</span><span class="cl">            <span class="n">LengthValidator</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">381</span><span class="cl">        <span class="p">]),</span>
</span></span><span class="line"><span class="ln">382</span><span class="cl">        <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">383</span><span class="cl">        <span class="n">max_length</span><span class="o">=</span><span class="mi">5</span>
</span></span><span class="line"><span class="ln">384</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">385</span><span class="cl">
</span></span><span class="line"><span class="ln">386</span><span class="cl">    <span class="n">test_tags</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">387</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">388</span><span class="cl">        <span class="p">[],</span>
</span></span><span class="line"><span class="ln">389</span><span class="cl">        <span class="p">[</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">390</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">391</span><span class="cl">
</span></span><span class="line"><span class="ln">392</span><span class="cl">    <span class="k">for</span> <span class="n">tags</span> <span class="ow">in</span> <span class="n">test_tags</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">393</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">tags</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">394</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;PASS&#34;</span> <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</span> <span class="k">else</span> <span class="s2">&#34;FAIL&#34;</span>
</span></span><span class="line"><span class="ln">395</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">tags</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">396</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">397</span><span class="cl">            <span class="k">for</span> <span class="n">error</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">398</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">error</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">399</span><span class="cl">
</span></span><span class="line"><span class="ln">400</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">=== Demo Complete ===&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用範例">使用範例</h3>
<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="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="c1"># Create validators</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">not_empty</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">path_exists</span> <span class="o">=</span> <span class="n">PathExistsValidator</span><span class="p">(</span><span class="n">must_be_file</span><span class="o">=</span><span class="kc">True</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"># Validate string</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;hello&#34;</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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</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="n">result</span> <span class="o">=</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;&#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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</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">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Value cannot be empty&#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"># Validate path</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">path_exists</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;/etc/hosts&#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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True (on Unix systems)</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"># Using ValidationResult in boolean context</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">if</span> <span class="n">not_empty</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;test&#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="s2">&#34;Validation passed!&#34;</span><span class="p">)</span></span></span></code></pre></div><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"># Username validator: non-empty, 3-20 chars, lowercase alphanumeric</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">username_validator</span> <span class="o">=</span> <span class="n">AndValidator</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="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]*$&#34;</span><span class="p">),</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="c1"># Test cases</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</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">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</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">result</span> <span class="o">=</span> <span class="n">username_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;ab&#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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</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="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;Length 2 &lt; minimum 3&#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="c1"># OR validation: accept either email or username format</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">login_validator</span> <span class="o">=</span> <span class="n">OrValidator</span><span class="p">[</span><span class="nb">str</span><span class="p">]([</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z][a-z0-9_]{2,19}$&#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="nb">print</span><span class="p">(</span><span class="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</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="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;valid_user&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>        <span class="c1"># True</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="n">login_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;X&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>                  <span class="c1"># False</span></span></span></code></pre></div><h4 id="使用-builder-模式">使用 Builder 模式</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"># Fluent API for building validators</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">password_validator</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">validator_for</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="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">NotEmptyValidator</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">LengthValidator</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">128</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[A-Z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain uppercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[a-z].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain lowercase&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">PatternValidator</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;.*[0-9].*&#34;</span><span class="p">,</span> <span class="s2">&#34;Must contain digit&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="o">.</span><span class="n">build</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">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;weakpass&#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="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># [&#34;Must contain uppercase&#34;, &#34;Must contain digit&#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="n">result</span> <span class="o">=</span> <span class="n">password_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;Strong1Password&#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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</span></span></span></code></pre></div><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"># Validate a list of tags</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">tag_validator</span> <span class="o">=</span> <span class="n">NotEmptyValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">tags_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">tag_validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">max_length</span><span class="o">=</span><span class="mi">10</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="n">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</span><span class="p">,</span> <span class="s2">&#34;generic&#34;</span><span class="p">])</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">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># True</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">result</span> <span class="o">=</span> <span class="n">tags_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">([</span><span class="s2">&#34;valid&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;also-valid&#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="n">result</span><span class="o">.</span><span class="n">is_valid</span><span class="p">)</span>  <span class="c1"># False</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="n">result</span><span class="o">.</span><span class="n">errors</span><span class="p">)</span>    <span class="c1"># [&#34;[1] Value cannot be empty&#34;]</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>具體類型</th>
          <th>泛型設計</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>重用性</td>
          <td>低：每個類型需要獨立實作</td>
          <td>高：一次實作，多處使用</td>
      </tr>
      <tr>
          <td>型別推導</td>
          <td>簡單：型別固定</td>
          <td>需要技巧：需正確標註 TypeVar</td>
      </tr>
      <tr>
          <td>學習曲線</td>
          <td>低：直覺易懂</td>
          <td>中：需理解 Generic、Protocol</td>
      </tr>
      <tr>
          <td>IDE 支援</td>
          <td>完整：型別明確</td>
          <td>需要正確標註才能獲得完整支援</td>
      </tr>
      <tr>
          <td>執行效能</td>
          <td>略佳：無泛型開銷</td>
          <td>略差：有 Protocol 檢查開銷</td>
      </tr>
      <tr>
          <td>錯誤訊息</td>
          <td>清晰：直接指出問題</td>
          <td>可能較模糊：泛型相關錯誤不易讀</td>
      </tr>
  </tbody>
</table>
<h3 id="何時選擇泛型設計">何時選擇泛型設計？</h3>
<p><strong>選擇泛型設計</strong>當：</p>
<ul>
<li>驗證邏輯會用於多種類型</li>
<li>需要組合多個驗證器</li>
<li>正在建立可重用的驗證函式庫</li>
<li>重視編譯時期的型別安全</li>
</ul>
<p><strong>選擇具體類型</strong>當：</p>
<ul>
<li>只驗證單一特定類型</li>
<li>驗證邏輯非常簡單</li>
<li>團隊對泛型不熟悉</li>
<li>效能是關鍵考量</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>需要驗證多種類型的函式庫</li>
<li>驗證邏輯需要組合重用</li>
<li>重視型別安全</li>
<li>API 設計需要表達「這個驗證器接受 T 類型」</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>只驗證單一類型</li>
<li>驗證邏輯很簡單（幾行 if-else 就能搞定）</li>
<li>團隊不熟悉泛型語法</li>
<li>程式碼不會被重用</li>
</ul>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="1-實作-rangevalidatorint-和-lengthvalidatorstr">1. 實作 <code>RangeValidator[int]</code> 和 <code>LengthValidator[str]</code></h4>
<p>參考上面的 <code>RangeValidator</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="n">age_validator</span> <span class="o">=</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">age_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="mi">200</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h4 id="2-實作-emailvalidator">2. 實作 <code>EmailValidator</code></h4>
<p>建立一個 Email 驗證器，組合 <code>NotEmptyValidator</code> 和 <code>PatternValidator</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="n">email_validator</span> <span class="o">=</span> <span class="n">EmailValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">assert</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;user@example.com&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">assert</span> <span class="ow">not</span> <span class="n">email_validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;invalid&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">is_valid</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="1-實作-listvalidatort-驗證列表中的每個元素">1. 實作 <code>ListValidator[T]</code> 驗證列表中的每個元素</h4>
<p>建立一個泛型列表驗證器，可以：</p>
<ul>
<li>驗證列表長度</li>
<li>對每個元素執行子驗證器</li>
<li>收集所有錯誤，標註元素索引</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="n">int_list_validator</span> <span class="o">=</span> <span class="n">ListValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">element_validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</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="n">min_length</span><span class="o">=</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><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">int_list_validator</span><span class="o">.</span><span class="n">validate</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="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># errors: [&#34;[2] Value -3 &lt; minimum 0&#34;]</span></span></span></code></pre></div><h4 id="2-實作-conditionalvalidatort">2. 實作 <code>ConditionalValidator[T]</code></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"># Only validate age if it&#39;s provided (not None)</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">optional_age_validator</span> <span class="o">=</span> <span class="n">ConditionalValidator</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">condition</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</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">4</span><span class="cl">    <span class="n">validator</span><span class="o">=</span><span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="1-實作-schemavalidator-驗證字典結構">1. 實作 <code>SchemaValidator</code> 驗證字典結構</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="n">user_schema</span> <span class="o">=</span> <span class="n">SchemaValidator</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">NotEmptyValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="n">RangeValidator</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">150</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">EmailValidator</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">},</span> <span class="n">required_keys</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">,</span> <span class="s2">&#34;email&#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">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Alice&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;alice@example.com&#34;</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 class="k">assert</span> <span class="n">result</span><span class="o">.</span><span class="n">is_valid</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">result</span> <span class="o">=</span> <span class="n">user_schema</span><span class="o">.</span><span class="n">validate</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="o">-</span><span class="mi">5</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="c1"># errors: [&#34;name: Value cannot be empty&#34;, &#34;age: Value -5 &lt; minimum 0&#34;, &#34;Missing required key: email&#34;]</span></span></span></code></pre></div><p>提示：</p>
<ul>
<li>使用 <code>dict[str, Validator[Any]]</code> 作為 schema 類型</li>
<li>處理可選欄位和必填欄位</li>
<li>考慮巢狀 schema 的支援</li>
</ul>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/typing.html#typing.Generic">Python typing.Generic</a></li>
<li><a href="https://peps.python.org/pep-0484/">PEP 484 - Type Hints</a></li>
<li><a href="https://peps.python.org/pep-0544/">PEP 544 - Protocols: Structural subtyping</a></li>
<li><a href="https://mypy.readthedocs.io/en/stable/generics.html">mypy Generics</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></em>
<em>返回：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組 3.5：進階設計模式</a></em></p>
]]></content:encoded></item><item><title>3.5.4 插件系統設計</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/plugin-system/</guid><description>&lt;p>插件系統讓應用程式可以在不修改核心程式碼的情況下擴展功能。本章介紹三種常見的插件架構模式，以及如何使用 Python 的動態載入機制實現它們。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>本進階系列 &lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&lt;/a>&lt;/li>
&lt;li>特別是 Metaclass 與 &lt;code>__init_subclass__&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="插件架構模式">插件架構模式&lt;/h2>
&lt;h3 id="模式一基於繼承">模式一：基於繼承&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="kn">from&lt;/span> &lt;span class="nn">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ABC&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">abstractmethod&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ClassVar&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">class&lt;/span> &lt;span class="nc">Plugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ABC&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;插件基類&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">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&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"> 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="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">execute&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">context&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">dict&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="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">11&lt;/span>&lt;span class="cl"> &lt;span class="o">...&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="k">class&lt;/span> &lt;span class="nc">LoggingPlugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Plugin&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;logging&amp;#34;&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="nf">execute&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">context&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">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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Processing: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">context&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">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">context&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="k">class&lt;/span> &lt;span class="nc">ValidationPlugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Plugin&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;validation&amp;#34;&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="k">def&lt;/span> &lt;span class="nf">execute&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">context&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">dict&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="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">context&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;valid&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Invalid context&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="k">return&lt;/span> &lt;span class="n">context&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>優點&lt;/strong>：簡單明確，IDE 支援好
&lt;strong>缺點&lt;/strong>：強制耦合，無法使用第三方類別&lt;/p>
&lt;h3 id="模式二基於註冊">模式二：基於註冊&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="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TypeAlias&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="n">PluginFunc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">TypeAlias&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="nb">dict&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"> 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">class&lt;/span> &lt;span class="nc">PluginRegistry&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;插件註冊表&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>&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="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="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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_plugins&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&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">PluginFunc&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">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">def&lt;/span> &lt;span class="nf">register&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">name&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">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">PluginFunc&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">PluginFunc&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="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">13&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">PluginFunc&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">PluginFunc&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="k">if&lt;/span> &lt;span class="n">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">_plugins&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">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Plugin &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; already registered&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_plugins&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">func&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">func&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">return&lt;/span> &lt;span class="n">decorator&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="k">def&lt;/span> &lt;span class="nf">get&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">name&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">PluginFunc&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">21&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">_plugins&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">name&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>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">list_plugins&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">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">24&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">list&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">_plugins&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&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>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">&lt;span class="c1"># 全域註冊表&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">registry&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PluginRegistry&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>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="nd">@registry.register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;uppercase&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="k">def&lt;/span> &lt;span class="nf">uppercase_plugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&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">dict&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="n">context&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&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;text&amp;#34;&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 class="o">.&lt;/span>&lt;span class="n">upper&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="k">return&lt;/span> &lt;span class="n">context&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="nd">@registry.register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;lowercase&amp;#34;&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="k">def&lt;/span> &lt;span class="nf">lowercase_plugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&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">dict&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="n">context&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&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;text&amp;#34;&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 class="o">.&lt;/span>&lt;span class="n">lower&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="n">context&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="c1"># 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="n">plugin&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">registry&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;uppercase&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="k">if&lt;/span> &lt;span class="n">plugin&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="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">plugin&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Hello&amp;#34;&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;strong>缺點&lt;/strong>：需要顯式註冊&lt;/p>
&lt;h3 id="模式三基於發現使用-__init_subclass__">模式三：基於發現（使用 &lt;code>__init_subclass__&lt;/code>）&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="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ClassVar&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">AutoRegisterPlugin&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">_registry&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;AutoRegisterPlugin&amp;#34;&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&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"> 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">__init_subclass__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__init_subclass__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&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"># 跳過沒有定義 name 的中間類別&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="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="bp">cls&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">AutoRegisterPlugin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&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">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Plugin &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; already registered&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">AutoRegisterPlugin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&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="nd">@classmethod&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">def&lt;/span> &lt;span class="nf">get_plugin&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">name&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">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;AutoRegisterPlugin&amp;#34;&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">18&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&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">name&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>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="nd">@classmethod&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">def&lt;/span> &lt;span class="nf">list_plugins&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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">22&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&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>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">execute&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">context&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">dict&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">raise&lt;/span> &lt;span class="ne">NotImplementedError&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">class&lt;/span> &lt;span class="nc">FormatPlugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AutoRegisterPlugin&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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;format&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>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">execute&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">context&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">dict&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="n">context&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;formatted&amp;#34;&lt;/span>&lt;span class="p">]&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">33&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">context&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="k">class&lt;/span> &lt;span class="nc">CompressPlugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AutoRegisterPlugin&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="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;compress&amp;#34;&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="k">def&lt;/span> &lt;span class="nf">execute&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">context&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">dict&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">context&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;compressed&amp;#34;&lt;/span>&lt;span class="p">]&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">40&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">context&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">AutoRegisterPlugin&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list_plugins&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># [&amp;#39;format&amp;#39;, &amp;#39;compress&amp;#39;]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>優點&lt;/strong>：無需顯式註冊，只要繼承就會被發現
&lt;strong>缺點&lt;/strong>：必須繼承基類，Python 專屬&lt;/p>
&lt;h3 id="使用-metaclass-的進階版本">使用 Metaclass 的進階版本&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ClassVar&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">PluginMeta&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">type&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">_registry&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">type&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"> 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">def&lt;/span> &lt;span class="fm">__new__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mcs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&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">bases&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">tuple&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&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">type&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="bp">cls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__new__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mcs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&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 class="k">if&lt;/span> &lt;span class="n">bases&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;plugin_name&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="n">plugin_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">plugin_name&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">plugin_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">mcs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&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="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Plugin &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">plugin_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; already registered&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="n">mcs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">plugin_name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&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">return&lt;/span> &lt;span class="bp">cls&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="nd">@classmethod&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">def&lt;/span> &lt;span class="nf">get_plugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mcs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">name&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">type&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">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">mcs&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&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">name&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>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PluginBase&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">metaclass&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">PluginMeta&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="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">25&lt;/span>&lt;span class="cl"> &lt;span class="n">plugin_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&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">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">run&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="kc">None&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="k">raise&lt;/span> &lt;span class="ne">NotImplementedError&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="k">class&lt;/span> &lt;span class="nc">MyPlugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">PluginBase&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="n">plugin_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;my_plugin&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>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">run&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="kc">None&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;MyPlugin running&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用&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">plugin_cls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PluginMeta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_plugin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;my_plugin&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">if&lt;/span> &lt;span class="n">plugin_cls&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">plugin_cls&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="動態載入模組">動態載入模組&lt;/h2>
&lt;h3 id="importlib-基礎">importlib 基礎&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">import&lt;/span> &lt;span class="nn">importlib&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">types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ModuleType&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">load_module&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module_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">ModuleType&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;動態載入模組&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="k">return&lt;/span> &lt;span class="n">importlib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">import_module&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module_path&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="k">def&lt;/span> &lt;span class="nf">load_class&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module_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="n">class_name&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">type&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="n">module&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">importlib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">import_module&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module_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 class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">class_name&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="c1"># 假設有 myapp/plugins/custom.py 定義了 CustomPlugin&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">plugin_cls&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_class&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;myapp.plugins.custom&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;CustomPlugin&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="n">plugin&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">plugin_cls&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="從檔案路徑載入">從檔案路徑載入&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">import&lt;/span> &lt;span class="nn">importlib.util&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">types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ModuleType&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_module_from_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="n">Path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">ModuleType&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;從檔案路徑載入模組&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="n">spec&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">importlib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">util&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">spec_from_file_location&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">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stem&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 class="n">path&lt;/span> &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="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="k">if&lt;/span> &lt;span class="n">spec&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">spec&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loader&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Cannot load module from &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">path&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">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="n">module&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">importlib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">util&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">module_from_spec&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">spec&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">spec&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loader&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exec_module&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">module&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="k">return&lt;/span> &lt;span class="n">module&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">plugin_module&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">load_module_from_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="s2">&amp;#34;./plugins/custom_plugin.py&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="importlibmetadata-的-entry_points現代做法">importlib.metadata 的 entry_points（現代做法）&lt;/h3>
&lt;p>這是 Python 套件生態系統推薦的插件發現機制：&lt;/p></description><content:encoded><![CDATA[<p>插件系統讓應用程式可以在不修改核心程式碼的情況下擴展功能。本章介紹三種常見的插件架構模式，以及如何使用 Python 的動態載入機制實現它們。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>本進階系列 <a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></li>
<li>特別是 Metaclass 與 <code>__init_subclass__</code></li>
</ul>
<h2 id="插件架構模式">插件架構模式</h2>
<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">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</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">ClassVar</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">Plugin</span><span class="p">(</span><span class="n">ABC</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">name</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">str</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">10</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行插件邏輯&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="o">...</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">class</span> <span class="nc">LoggingPlugin</span><span class="p">(</span><span class="n">Plugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;logging&#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="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">17</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Processing: </span><span class="si">{</span><span class="n">context</span><span class="si">}</span><span class="s2">&#34;</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">context</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">class</span> <span class="nc">ValidationPlugin</span><span class="p">(</span><span class="n">Plugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;validation&#34;</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">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">24</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;valid&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Invalid context&#34;</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">context</span></span></span></code></pre></div><p><strong>優點</strong>：簡單明確，IDE 支援好
<strong>缺點</strong>：強制耦合，無法使用第三方類別</p>
<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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">TypeAlias</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="n">PluginFunc</span><span class="p">:</span> <span class="n">TypeAlias</span> <span class="o">=</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">dict</span><span class="p">],</span> <span class="nb">dict</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">class</span> <span class="nc">PluginRegistry</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></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="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="bp">self</span><span class="o">.</span><span class="n">_plugins</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">PluginFunc</span><span class="p">]</span> <span class="o">=</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">def</span> <span class="nf">register</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">[[</span><span class="n">PluginFunc</span><span class="p">],</span> <span class="n">PluginFunc</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 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">PluginFunc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">PluginFunc</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="n">name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; already registered&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">func</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">return</span> <span class="n">func</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="n">decorator</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">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">PluginFunc</span> <span class="o">|</span> <span class="kc">None</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="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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">def</span> <span class="nf">list_plugins</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="nb">str</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="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="o">.</span><span class="n">keys</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"># 全域註冊表</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">registry</span> <span class="o">=</span> <span class="n">PluginRegistry</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="nd">@registry.register</span><span class="p">(</span><span class="s2">&#34;uppercase&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">def</span> <span class="nf">uppercase_plugin</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">31</span><span class="cl">    <span class="n">context</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">upper</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="n">context</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="nd">@registry.register</span><span class="p">(</span><span class="s2">&#34;lowercase&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">def</span> <span class="nf">lowercase_plugin</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">36</span><span class="cl">    <span class="n">context</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">lower</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="n">context</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">plugin</span> <span class="o">=</span> <span class="n">registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;uppercase&#34;</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">plugin</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">plugin</span><span class="p">({</span><span class="s2">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello&#34;</span><span class="p">})</span></span></span></code></pre></div><p><strong>優點</strong>：靈活，支援函式和類別
<strong>缺點</strong>：需要顯式註冊</p>
<h3 id="模式三基於發現使用-__init_subclass__">模式三：基於發現（使用 <code>__init_subclass__</code>）</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">ClassVar</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">AutoRegisterPlugin</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">_registry</span><span class="p">:</span> <span class="n">ClassVar</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="nb">type</span><span class="p">[</span><span class="s2">&#34;AutoRegisterPlugin&#34;</span><span class="p">]]]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="n">ClassVar</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></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="nf">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init_subclass__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="c1"># 跳過沒有定義 name 的中間類別</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;name&#34;</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">cls</span><span class="o">.</span><span class="n">name</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="k">if</span> <span class="bp">cls</span><span class="o">.</span><span class="n">name</span> <span class="ow">in</span> <span class="n">AutoRegisterPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; already registered&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">AutoRegisterPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="bp">cls</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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">@classmethod</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">get_plugin</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">[</span><span class="s2">&#34;AutoRegisterPlugin&#34;</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">18</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">list_plugins</span><span class="p">(</span><span class="bp">cls</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">22</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">keys</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">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">25</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</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">class</span> <span class="nc">FormatPlugin</span><span class="p">(</span><span class="n">AutoRegisterPlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;format&#34;</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">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">32</span><span class="cl">        <span class="n">context</span><span class="p">[</span><span class="s2">&#34;formatted&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="n">context</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">CompressPlugin</span><span class="p">(</span><span class="n">AutoRegisterPlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;compress&#34;</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">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nb">dict</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">39</span><span class="cl">        <span class="n">context</span><span class="p">[</span><span class="s2">&#34;compressed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="n">context</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="nb">print</span><span class="p">(</span><span class="n">AutoRegisterPlugin</span><span class="o">.</span><span class="n">list_plugins</span><span class="p">())</span>  <span class="c1"># [&#39;format&#39;, &#39;compress&#39;]</span></span></span></code></pre></div><p><strong>優點</strong>：無需顯式註冊，只要繼承就會被發現
<strong>缺點</strong>：必須繼承基類，Python 專屬</p>
<h3 id="使用-metaclass-的進階版本">使用 Metaclass 的進階版本</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">typing</span> <span class="kn">import</span> <span class="n">ClassVar</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">PluginMeta</span><span class="p">(</span><span class="nb">type</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">_registry</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="nb">type</span><span class="p">]</span> <span class="o">=</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">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">bases</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">namespace</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">cls</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">bases</span><span class="p">,</span> <span class="n">namespace</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 class="k">if</span> <span class="n">bases</span> <span class="ow">and</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;plugin_name&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="n">plugin_name</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="n">plugin_name</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="k">if</span> <span class="n">plugin_name</span> <span class="ow">in</span> <span class="n">mcs</span><span class="o">.</span><span class="n">_registry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">plugin_name</span><span class="si">}</span><span class="s2">&#39; already registered&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="n">mcs</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="n">plugin_name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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">return</span> <span class="bp">cls</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">@classmethod</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">get_plugin</span><span class="p">(</span><span class="n">mcs</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</span> <span class="o">|</span> <span class="kc">None</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">mcs</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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">class</span> <span class="nc">PluginBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">PluginMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;插件基類&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">plugin_name</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">str</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">run</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">28</span><span class="cl">        <span class="k">raise</span> <span class="ne">NotImplementedError</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">class</span> <span class="nc">MyPlugin</span><span class="p">(</span><span class="n">PluginBase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="n">plugin_name</span> <span class="o">=</span> <span class="s2">&#34;my_plugin&#34;</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">def</span> <span class="nf">run</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">34</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;MyPlugin running&#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="n">plugin_cls</span> <span class="o">=</span> <span class="n">PluginMeta</span><span class="o">.</span><span class="n">get_plugin</span><span class="p">(</span><span class="s2">&#34;my_plugin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="k">if</span> <span class="n">plugin_cls</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">plugin_cls</span><span class="p">()</span><span class="o">.</span><span class="n">run</span><span class="p">()</span></span></span></code></pre></div><h2 id="動態載入模組">動態載入模組</h2>
<h3 id="importlib-基礎">importlib 基礎</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">importlib</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</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">load_module</span><span class="p">(</span><span class="n">module_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleType</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">importlib</span><span class="o">.</span><span class="n">import_module</span><span class="p">(</span><span class="n">module_path</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">load_class</span><span class="p">(</span><span class="n">module_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">class_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</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="n">module</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">import_module</span><span class="p">(</span><span class="n">module_path</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">getattr</span><span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="n">class_name</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="c1"># 假設有 myapp/plugins/custom.py 定義了 CustomPlugin</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">plugin_cls</span> <span class="o">=</span> <span class="n">load_class</span><span class="p">(</span><span class="s2">&#34;myapp.plugins.custom&#34;</span><span class="p">,</span> <span class="s2">&#34;CustomPlugin&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">plugin</span> <span class="o">=</span> <span class="n">plugin_cls</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="kn">import</span> <span class="nn">importlib.util</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">types</span> <span class="kn">import</span> <span class="n">ModuleType</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_module_from_path</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">ModuleType</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">spec</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">spec_from_file_location</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">path</span><span class="o">.</span><span class="n">stem</span><span class="p">,</span>  <span class="c1"># 模組名稱</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">path</span>        <span class="c1"># 檔案路徑</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 class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">spec</span><span class="o">.</span><span class="n">loader</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cannot load module from </span><span class="si">{</span><span class="n">path</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></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">module</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">module_from_spec</span><span class="p">(</span><span class="n">spec</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">spec</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">exec_module</span><span class="p">(</span><span class="n">module</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">module</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">plugin_module</span> <span class="o">=</span> <span class="n">load_module_from_path</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./plugins/custom_plugin.py&#34;</span><span class="p">))</span></span></span></code></pre></div><h3 id="importlibmetadata-的-entry_points現代做法">importlib.metadata 的 entry_points（現代做法）</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="c1"># 在 pyproject.toml 中定義 entry points</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">[project.entry-points.&#34;myapp.plugins&#34;]
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">format = &#34;myapp_format_plugin:FormatPlugin&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">compress = &#34;myapp_compress_plugin:CompressPlugin&#34;
</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">importlib.metadata</span> <span class="kn">import</span> <span class="n">entry_points</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">discover_plugins</span><span class="p">(</span><span class="n">group</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 class="nb">str</span><span class="p">,</span> <span class="nb">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;發現已安裝套件中的插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">plugins</span> <span class="o">=</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"># Python 3.10+</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">eps</span> <span class="o">=</span> <span class="n">entry_points</span><span class="p">(</span><span class="n">group</span><span class="o">=</span><span class="n">group</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">for</span> <span class="n">ep</span> <span class="ow">in</span> <span class="n">eps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="n">plugin_cls</span> <span class="o">=</span> <span class="n">ep</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="n">plugins</span><span class="p">[</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">plugin_cls</span>
</span></span><span class="line"><span class="ln">21</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">22</span><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load plugin </span><span class="si">{</span><span class="n">ep</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: </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><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">return</span> <span class="n">plugins</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"># 發現所有 myapp.plugins 群組的插件</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">plugins</span> <span class="o">=</span> <span class="n">discover_plugins</span><span class="p">(</span><span class="s2">&#34;myapp.plugins&#34;</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">name</span><span class="p">,</span> <span class="bp">cls</span> <span class="ow">in</span> <span class="n">plugins</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Found plugin: </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> -&gt; </span><span class="si">{</span><span class="bp">cls</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>優點</strong>：</p>
<ul>
<li>標準化的發現機制</li>
<li>支援第三方套件提供插件</li>
<li>pip 安裝即可使用</li>
</ul>
<h2 id="實際範例一hook-系統的插件化改造">實際範例一：Hook 系統的插件化改造</h2>
<p>基於入門系列的 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="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln">  2</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">  3</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">  4</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">  5</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">ClassVar</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="kn">import</span> <span class="nn">importlib.util</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">class</span> <span class="nc">HookEvent</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">PRE_TOOL_USE</span> <span class="o">=</span> <span class="s2">&#34;PreToolUse&#34;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">POST_TOOL_USE</span> <span class="o">=</span> <span class="s2">&#34;PostToolUse&#34;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">SESSION_START</span> <span class="o">=</span> <span class="s2">&#34;SessionStart&#34;</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">SESSION_END</span> <span class="o">=</span> <span class="s2">&#34;SessionEnd&#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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">class</span> <span class="nc">HookContext</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">event</span><span class="p">:</span> <span class="n">HookEvent</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="n">tool_name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</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">input_data</span><span class="p">:</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</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">HookResult</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">success</span><span class="p">:</span> <span class="nb">bool</span>
</span></span><span class="line"><span class="ln"> 25</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"> 26</span><span class="cl">    <span class="n">modified_context</span><span class="p">:</span> <span class="n">HookContext</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">class</span> <span class="nc">HookPlugin</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 插件基類&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">    <span class="n">_registry</span><span class="p">:</span> <span class="n">ClassVar</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="nb">list</span><span class="p">[</span><span class="nb">type</span><span class="p">[</span><span class="s2">&#34;HookPlugin&#34;</span><span class="p">]]]]</span> <span class="o">=</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">name</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="n">events</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="n">HookEvent</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">    <span class="n">priority</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">100</span>  <span class="c1"># 預設優先級</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">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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"> 38</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init_subclass__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;name&#34;</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;events&#34;</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">event</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">events</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">                <span class="n">event_name</span> <span class="o">=</span> <span class="n">event</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="k">if</span> <span class="n">event_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">HookPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">                    <span class="n">HookPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="n">event_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">                <span class="n">HookPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="n">event_name</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">                <span class="c1"># 按優先級排序</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">                <span class="n">HookPlugin</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="n">event_name</span><span class="p">]</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">                    <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="o">.</span><span class="n">priority</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">HookContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">HookResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行 Hook 邏輯&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">        <span class="o">...</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="k">def</span> <span class="nf">get_hooks_for_event</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="n">HookEvent</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">type</span><span class="p">[</span><span class="s2">&#34;HookPlugin&#34;</span><span class="p">]]:</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;取得特定事件的所有 Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">value</span><span class="p">,</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"># 定義具體的 Hook 插件</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="k">class</span> <span class="nc">SecurityCheckHook</span><span class="p">(</span><span class="n">HookPlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="s2">&#34;&#34;&#34;安全檢查 Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;security_check&#34;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="n">events</span> <span class="o">=</span> <span class="p">[</span><span class="n">HookEvent</span><span class="o">.</span><span class="n">PRE_TOOL_USE</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="n">priority</span> <span class="o">=</span> <span class="mi">10</span>  <span class="c1"># 高優先級，先執行</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">DANGEROUS_PATTERNS</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;rm -rf&#34;</span><span class="p">,</span> <span class="s2">&#34;DROP TABLE&#34;</span><span class="p">,</span> <span class="s2">&#34;sudo&#34;</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">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">HookContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">HookResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="k">if</span> <span class="n">context</span><span class="o">.</span><span class="n">input_data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="n">command</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">input_data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;command&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 72</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">DANGEROUS_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">                <span class="k">if</span> <span class="n">pattern</span> <span class="ow">in</span> <span class="n">command</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">                    <span class="k">return</span> <span class="n">HookResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">                        <span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">                        <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Blocked dangerous pattern: </span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="k">return</span> <span class="n">HookResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Security check passed&#34;</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">class</span> <span class="nc">LoggingHook</span><span class="p">(</span><span class="n">HookPlugin</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">name</span> <span class="o">=</span> <span class="s2">&#34;logging&#34;</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">    <span class="n">events</span> <span class="o">=</span> <span class="p">[</span><span class="n">HookEvent</span><span class="o">.</span><span class="n">PRE_TOOL_USE</span><span class="p">,</span> <span class="n">HookEvent</span><span class="o">.</span><span class="n">POST_TOOL_USE</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">    <span class="n">priority</span> <span class="o">=</span> <span class="mi">1000</span>  <span class="c1"># 低優先級，最後執行</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="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">HookContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">HookResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 87</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">context</span><span class="o">.</span><span class="n">event</span><span class="o">.</span><span class="n">value</span><span class="si">}</span><span class="s2">] Tool: </span><span class="si">{</span><span class="n">context</span><span class="o">.</span><span class="n">tool_name</span><span class="si">}</span><span class="s2">&#34;</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="n">HookResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Logged&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">
</span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="c1"># Hook 執行器</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="k">class</span> <span class="nc">HookRunner</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">    <span class="s2">&#34;&#34;&#34;執行 Hook 的管理器&#34;&#34;&#34;</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="k">def</span> <span class="nf">run_hooks</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="n">HookEvent</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">HookContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">HookResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行特定事件的所有 Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 96</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"> 97</span><span class="cl">        <span class="n">hook_classes</span> <span class="o">=</span> <span class="n">HookPlugin</span><span class="o">.</span><span class="n">get_hooks_for_event</span><span class="p">(</span><span class="n">event</span><span class="p">)</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="k">for</span> <span class="n">hook_cls</span> <span class="ow">in</span> <span class="n">hook_classes</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="n">hook</span> <span class="o">=</span> <span class="n">hook_cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">hook</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">103</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">104</span><span class="cl">                <span class="c1"># 如果 Hook 失敗且事件是 PRE，中斷執行</span>
</span></span><span class="line"><span class="ln">105</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">success</span> <span class="ow">and</span> <span class="n">event</span> <span class="o">==</span> <span class="n">HookEvent</span><span class="o">.</span><span class="n">PRE_TOOL_USE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">                    <span class="k">break</span>
</span></span><span class="line"><span class="ln">107</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">108</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">HookResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">                    <span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">110</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_cls</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> failed: </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">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="k">return</span> <span class="n">results</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="c1"># 使用</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl"><span class="n">runner</span> <span class="o">=</span> <span class="n">HookRunner</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl"><span class="n">context</span> <span class="o">=</span> <span class="n">HookContext</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="n">event</span><span class="o">=</span><span class="n">HookEvent</span><span class="o">.</span><span class="n">PRE_TOOL_USE</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">    <span class="n">tool_name</span><span class="o">=</span><span class="s2">&#34;Bash&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="n">input_data</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;ls -la&#34;</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="n">results</span> <span class="o">=</span> <span class="n">runner</span><span class="o">.</span><span class="n">run_hooks</span><span class="p">(</span><span class="n">HookEvent</span><span class="o">.</span><span class="n">PRE_TOOL_USE</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span></span></span></code></pre></div><h2 id="實際範例二通用插件框架設計">實際範例二：通用插件框架設計</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">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln">  2</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">  3</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 class="p">,</span> <span class="n">auto</span>
</span></span><span class="line"><span class="ln">  4</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">  5</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></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="kn">import</span> <span class="nn">importlib.util</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">class</span> <span class="nc">PluginState</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="n">UNLOADED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="n">LOADED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">    <span class="n">ACTIVE</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">    <span class="n">ERROR</span> <span class="o">=</span> <span class="n">auto</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl"><span class="k">class</span> <span class="nc">PluginInfo</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="n">name</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">version</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">description</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">author</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"> 21</span><span class="cl">    <span class="n">dependencies</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">=</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"> 22</span><span class="cl">
</span></span><span class="line"><span class="ln"> 23</span><span class="cl"><span class="k">class</span> <span class="nc">BasePlugin</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;插件基類&#34;&#34;&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="k">def</span> <span class="nf">info</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">PluginInfo</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="o">...</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">on_load</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="s2">&#34;&#34;&#34;插件載入時呼叫&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">pass</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">on_unload</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"> 37</span><span class="cl">        <span class="s2">&#34;&#34;&#34;插件卸載時呼叫&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="k">pass</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">on_activate</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"> 41</span><span class="cl">        <span class="s2">&#34;&#34;&#34;插件啟用時呼叫&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="k">pass</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="k">def</span> <span class="nf">on_deactivate</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"> 45</span><span class="cl">        <span class="s2">&#34;&#34;&#34;插件停用時呼叫&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">        <span class="k">pass</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="k">class</span> <span class="nc">LoadedPlugin</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="s2">&#34;&#34;&#34;已載入的插件包裝&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="n">plugin</span><span class="p">:</span> <span class="n">BasePlugin</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">state</span><span class="p">:</span> <span class="n">PluginState</span> <span class="o">=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">UNLOADED</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">class</span> <span class="nc">PluginManager</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="s2">&#34;&#34;&#34;插件管理器&#34;&#34;&#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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">plugin_dir</span><span class="p">:</span> <span class="n">Path</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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"> 59</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</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">LoadedPlugin</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="bp">self</span><span class="o">.</span><span class="n">_plugin_dir</span> <span class="o">=</span> <span class="n">plugin_dir</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="k">def</span> <span class="nf">load_plugin</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="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="s2">&#34;&#34;&#34;從檔案載入插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">            <span class="c1"># 動態載入模組</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">spec</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">spec_from_file_location</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">                <span class="n">path</span><span class="o">.</span><span class="n">stem</span><span class="p">,</span> <span class="n">path</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">            <span class="k">if</span> <span class="n">spec</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="n">spec</span><span class="o">.</span><span class="n">loader</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Cannot load </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#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="n">module</span> <span class="o">=</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">module_from_spec</span><span class="p">(</span><span class="n">spec</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">            <span class="n">spec</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">exec_module</span><span class="p">(</span><span class="n">module</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="c1"># 尋找 BasePlugin 的子類別</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">            <span class="n">plugin_cls</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 77</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="n">module</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">                <span class="n">attr</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">attr</span><span class="p">,</span> <span class="nb">type</span><span class="p">)</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">                    <span class="nb">issubclass</span><span class="p">(</span><span class="n">attr</span><span class="p">,</span> <span class="n">BasePlugin</span><span class="p">)</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">                    <span class="n">attr</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">BasePlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">                    <span class="n">plugin_cls</span> <span class="o">=</span> <span class="n">attr</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                    <span class="k">break</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="k">if</span> <span class="n">plugin_cls</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;No plugin class found in </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span><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="c1"># 實例化插件</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="n">plugin</span> <span class="o">=</span> <span class="n">plugin_cls</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="n">plugin_name</span> <span class="o">=</span> <span class="n">plugin</span><span class="o">.</span><span class="n">info</span><span class="o">.</span><span class="n">name</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="c1"># 檢查依賴</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">            <span class="k">for</span> <span class="n">dep</span> <span class="ow">in</span> <span class="n">plugin</span><span class="o">.</span><span class="n">info</span><span class="o">.</span><span class="n">dependencies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">                <span class="k">if</span> <span class="n">dep</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">                    <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Missing dependency: </span><span class="si">{</span><span class="n">dep</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="c1"># 呼叫生命週期方法</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="n">plugin</span><span class="o">.</span><span class="n">on_load</span><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="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">plugin_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">LoadedPlugin</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">                <span class="n">plugin</span><span class="o">=</span><span class="n">plugin</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">                <span class="n">state</span><span class="o">=</span><span class="n">PluginState</span><span class="o">.</span><span class="n">LOADED</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="k">return</span> <span class="n">plugin_name</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">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">108</span><span class="cl">            <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Failed to load plugin from </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">: </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><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">unload_plugin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">111</span><span class="cl">        <span class="s2">&#34;&#34;&#34;卸載插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">            <span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</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="n">loaded</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">
</span></span><span class="line"><span class="ln">117</span><span class="cl">        <span class="c1"># 先停用</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">        <span class="k">if</span> <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">deactivate_plugin</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">
</span></span><span class="line"><span class="ln">121</span><span class="cl">        <span class="c1"># 呼叫生命週期方法</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="n">loaded</span><span class="o">.</span><span class="n">plugin</span><span class="o">.</span><span class="n">on_unload</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">
</span></span><span class="line"><span class="ln">124</span><span class="cl">        <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">
</span></span><span class="line"><span class="ln">126</span><span class="cl">    <span class="k">def</span> <span class="nf">activate_plugin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">127</span><span class="cl">        <span class="s2">&#34;&#34;&#34;啟用插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">            <span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><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="n">loaded</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="k">if</span> <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">LOADED</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; is not in LOADED state&#34;</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">loaded</span><span class="o">.</span><span class="n">plugin</span><span class="o">.</span><span class="n">on_activate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">            <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">ACTIVE</span>
</span></span><span class="line"><span class="ln">138</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">139</span><span class="cl">            <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">ERROR</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="n">loaded</span><span class="o">.</span><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">141</span><span class="cl">            <span class="k">raise</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">deactivate_plugin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">144</span><span class="cl">        <span class="s2">&#34;&#34;&#34;停用插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="k">if</span> <span class="n">name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">            <span class="k">raise</span> <span class="ne">KeyError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Plugin &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; not found&#34;</span><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="n">loaded</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">        <span class="k">if</span> <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">            <span class="k">return</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="n">loaded</span><span class="o">.</span><span class="n">plugin</span><span class="o">.</span><span class="n">on_deactivate</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">loaded</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">PluginState</span><span class="o">.</span><span class="n">LOADED</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">def</span> <span class="nf">discover_plugins</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">Path</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">156</span><span class="cl">        <span class="s2">&#34;&#34;&#34;發現插件目錄中的所有插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugin_dir</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">            <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">159</span><span class="cl">
</span></span><span class="line"><span class="ln">160</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_plugin_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">161</span><span class="cl">
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="k">def</span> <span class="nf">load_all_plugins</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 class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">163</span><span class="cl">        <span class="s2">&#34;&#34;&#34;載入所有發現的插件&#34;&#34;&#34;</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="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</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 class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">discover_plugins</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">167</span><span class="cl">                <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">load_plugin</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">168</span><span class="cl">                <span class="n">results</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">169</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">170</span><span class="cl">                <span class="n">results</span><span class="p">[</span><span class="n">path</span><span class="o">.</span><span class="n">stem</span><span class="p">]</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">171</span><span class="cl">        <span class="k">return</span> <span class="n">results</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="k">def</span> <span class="nf">get_plugin</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">BasePlugin</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">174</span><span class="cl">        <span class="s2">&#34;&#34;&#34;取得插件實例&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">175</span><span class="cl">        <span class="n">loaded</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">176</span><span class="cl">        <span class="k">return</span> <span class="n">loaded</span><span class="o">.</span><span class="n">plugin</span> <span class="k">if</span> <span class="n">loaded</span> <span class="k">else</span> <span class="kc">None</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="k">def</span> <span class="nf">list_plugins</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">PluginInfo</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">179</span><span class="cl">        <span class="s2">&#34;&#34;&#34;列出所有已載入的插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="n">lp</span><span class="o">.</span><span class="n">plugin</span><span class="o">.</span><span class="n">info</span> <span class="k">for</span> <span class="n">lp</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_plugins</span><span class="o">.</span><span class="n">values</span><span class="p">()]</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="c1"># 範例插件（放在獨立檔案中）</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl"><span class="k">class</span> <span class="nc">GreeterPlugin</span><span class="p">(</span><span class="n">BasePlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">184</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡單的問候插件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">
</span></span><span class="line"><span class="ln">186</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">    <span class="k">def</span> <span class="nf">info</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">PluginInfo</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">        <span class="k">return</span> <span class="n">PluginInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">            <span class="n">name</span><span class="o">=</span><span class="s2">&#34;greeter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">            <span class="n">version</span><span class="o">=</span><span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">191</span><span class="cl">            <span class="n">description</span><span class="o">=</span><span class="s2">&#34;A simple greeting plugin&#34;</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></span><span class="line"><span class="ln">194</span><span class="cl">    <span class="k">def</span> <span class="nf">on_activate</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">195</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Greeter plugin activated!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">196</span><span class="cl">
</span></span><span class="line"><span class="ln">197</span><span class="cl">    <span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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">198</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">!&#34;</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 class="n">manager</span> <span class="o">=</span> <span class="n">PluginManager</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="s2">&#34;./plugins&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">202</span><span class="cl"><span class="n">manager</span><span class="o">.</span><span class="n">load_all_plugins</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl"><span class="n">manager</span><span class="o">.</span><span class="n">activate_plugin</span><span class="p">(</span><span class="s2">&#34;greeter&#34;</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="n">greeter</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">get_plugin</span><span class="p">(</span><span class="s2">&#34;greeter&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl"><span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">greeter</span><span class="p">,</span> <span class="n">GreeterPlugin</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">greeter</span><span class="o">.</span><span class="n">greet</span><span class="p">(</span><span class="s2">&#34;World&#34;</span><span class="p">))</span></span></span></code></pre></div><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="c1"># 限制插件可以存取的模組</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 class="kn">from</span> <span class="nn">types</span> <span class="kn">import</span> <span class="n">ModuleType</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">class</span> <span class="nc">SandboxedImporter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;限制插件可以 import 的模組&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">ALLOWED_MODULES</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;json&#34;</span><span class="p">,</span> <span class="s2">&#34;re&#34;</span><span class="p">,</span> <span class="s2">&#34;datetime&#34;</span><span class="p">,</span> <span class="s2">&#34;typing&#34;</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="k">def</span> <span class="nf">find_module</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kc">None</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">name</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 class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">ALLOWED_MODULES</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="bp">self</span>  <span class="c1"># 攔截</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</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="k">def</span> <span class="nf">load_module</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ModuleType</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Module &#39;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#39; is not allowed in plugins&#34;</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="c1"># 使用時插入到 sys.meta_path</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># sys.meta_path.insert(0, SandboxedImporter())</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">packaging</span> <span class="kn">import</span> <span class="n">version</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_compatibility</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">plugin_info</span><span class="p">:</span> <span class="n">PluginInfo</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">app_version</span><span class="p">:</span> <span class="nb">str</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="nb">bool</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="c1"># 插件可以定義 min_app_version 屬性</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">min_version</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">plugin_info</span><span class="p">,</span> <span class="s2">&#34;min_app_version&#34;</span><span class="p">,</span> <span class="s2">&#34;0.0.0&#34;</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">version</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">app_version</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">version</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">min_version</span><span class="p">)</span></span></span></code></pre></div><h2 id="小結">小結</h2>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th>適用場景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>基於繼承</td>
          <td>簡單場景，需要型別安全</td>
      </tr>
      <tr>
          <td>基於註冊</td>
          <td>需要靈活性，支援函式插件</td>
      </tr>
      <tr>
          <td>基於發現</td>
          <td>自動發現，減少樣板程式碼</td>
      </tr>
      <tr>
          <td>entry_points</td>
          <td>套件生態系統，第三方插件</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li>三種插件模式各有什麼優缺點？</li>
<li>如何實現插件之間的依賴管理？</li>
<li>如何確保插件的安全性？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.5.3 進階上下文管理</a></em>
<em>下一章：<a href="/blog/python-advanced/03-design-patterns/integration/" data-link-title="3.5.5 設計模式整合案例" data-link-desc="結合泛型、異常、上下文、插件建立完整系統">3.5.5 設計模式整合案例</a></em></p>
]]></content:encoded></item><item><title>3.5.5 設計模式整合案例</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/integration/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/integration/</guid><description>&lt;p>本章透過兩個完整案例，展示如何將前四章的設計模式結合應用。每個案例都會說明各模式的協作關係。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>本模組 3.5.1-3.5.4 所有章節&lt;/li>
&lt;/ul>
&lt;h2 id="案例一迷你-orm-框架">案例一：迷你 ORM 框架&lt;/h2>
&lt;p>建立一個簡化的 ORM（Object-Relational Mapping）框架，展示各模式的整合。&lt;/p>
&lt;h3 id="設計概覽">設計概覽&lt;/h3>





&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">│ MiniORM Framework │
&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">│ 泛型 │ Repository[T] - 型別安全的資料存取 │
&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">│ 上下文 │ Transaction - 交易管理 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">│ 插件 │ FieldType - 可擴展的欄位型別 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">└─────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="異常層級設計">異常層級設計&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="c1"># miniorm/exceptions.py&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">ORMError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="ne">Exception&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;ORM 框架的基礎異常&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>&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="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">message&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">code&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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="kc">None&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">message&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">code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">code&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">class&lt;/span> &lt;span class="nc">ConnectionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORMError&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="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">13&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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="k">class&lt;/span> &lt;span class="nc">QueryError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORMError&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;查詢執行錯誤&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="k">pass&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">class&lt;/span> &lt;span class="nc">EntityNotFoundError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORMError&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>&lt;/span>&lt;span class="line">&lt;span class="ln">22&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">model&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">pk&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">|&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="kc">None&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">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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">model&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> with pk=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">pk&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> not found&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;NOT_FOUND&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pk&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pk&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">class&lt;/span> &lt;span class="nc">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORMError&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="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">field&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">message&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="kc">None&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="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Validation failed for &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">field&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39;: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&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="n">code&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;VALIDATION&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">field&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TransactionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ORMError&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="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">36&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="插件系統可擴展的欄位型別">插件系統：可擴展的欄位型別&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="c1"># miniorm/fields.py&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="kn">from&lt;/span> &lt;span class="nn">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ABC&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">abstractmethod&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ClassVar&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&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">class&lt;/span> &lt;span class="nc">FieldType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ABC&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">_registry&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;FieldType&amp;#34;&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"> 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="c1"># 子類別必須定義&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">type_name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&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"> 13&lt;/span>&lt;span class="cl"> &lt;span class="n">python_type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">type&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="k">def&lt;/span> &lt;span class="nf">__init_subclass__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&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"> 16&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__init_subclass__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">kwargs&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">if&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;type_name&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="n">FieldType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">type_name&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&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="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">to_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&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;&amp;#34;&amp;#34;Python 值轉換為資料庫值&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="o">...&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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 26&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">from_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Any&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;資料庫值轉換為 Python 值&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 class="o">...&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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證值，失敗時拋出 ValidationError&amp;#34;&amp;#34;&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="o">...&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="nd">@classmethod&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">def&lt;/span> &lt;span class="nf">get_type&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">name&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">type&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;FieldType&amp;#34;&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"> 37&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_registry&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">name&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl">&lt;span class="c1"># 內建欄位型別&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">class&lt;/span> &lt;span class="nc">IntegerField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FieldType&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="n">type_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;integer&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="n">python_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 44&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">to_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="k">return&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&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>&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">from_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&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="k">return&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 51&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">isinstance&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">int&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">raise&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Expected int, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__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"> 53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">StringField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FieldType&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">type_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;string&amp;#34;&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">python_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&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="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">max_length&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">255&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"> 59&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_length&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max_length&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">def&lt;/span> &lt;span class="nf">to_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 62&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 64&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">from_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 65&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&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="k">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 68&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&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"> 69&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">isinstance&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 70&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Expected str, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__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"> 71&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">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_length&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 class="k">raise&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Max length is &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_length&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"> 73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">DateTimeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FieldType&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 75&lt;/span>&lt;span class="cl"> &lt;span class="n">type_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;datetime&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">python_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 77&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">to_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 79&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 80&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isoformat&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 82&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">from_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">datetime&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 class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 85&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 86&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fromisoformat&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 87&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 89&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">datetime&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="k">raise&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Expected datetime, got &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__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"> 91&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl">&lt;span class="c1"># 使用者可以擴展新的欄位型別&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">JSONField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">FieldType&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="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"> 95&lt;/span>&lt;span class="cl"> &lt;span class="n">type_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl"> &lt;span class="n">python_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">to_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 99&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dumps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">101&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">from_db&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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">103&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">json&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">if&lt;/span> &lt;span class="nb">isinstance&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">dict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&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>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&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">value&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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">109&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">,&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">110&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">ValidationError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;value&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Expected dict or list&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="泛型-repository">泛型 Repository&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="c1"># miniorm/repository.py&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="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Generic&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Protocol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ClassVar&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">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">abstractmethod&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">class&lt;/span> &lt;span class="nc">HasId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&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;具有 id 屬性的協議&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="nb">id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&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">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TypeVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;T&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bound&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">HasId&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="k">class&lt;/span> &lt;span class="nc">Repository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Generic&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&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;泛型 Repository 基類&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>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">model_class&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">ClassVar&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">type&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>&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="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">connection&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Connection&amp;#34;&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">18&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_connection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">connection&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="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">get&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">pk&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">-&amp;gt;&lt;/span> &lt;span class="n">T&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">22&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">23&lt;/span>&lt;span class="cl"> &lt;span class="o">...&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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">save&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">entity&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">T&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;儲存實體&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 class="o">...&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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">delete&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">pk&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">-&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">32&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">33&lt;/span>&lt;span class="cl"> &lt;span class="o">...&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="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">find_all&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">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&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="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">38&lt;/span>&lt;span class="cl"> &lt;span class="o">...&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">get_or_raise&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">pk&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">-&amp;gt;&lt;/span> &lt;span class="n">T&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;取得實體，不存在時拋出異常&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="n">entity&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">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pk&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">if&lt;/span> &lt;span class="n">entity&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">44&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">EntityNotFoundError&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">model_class&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pk&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="k">return&lt;/span> &lt;span class="n">entity&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">class&lt;/span> &lt;span class="nc">InMemoryRepository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Repository&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&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;記憶體實作的 Repository&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>&lt;/span>&lt;span class="line">&lt;span class="ln">50&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">connection&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Connection&amp;#34;&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">51&lt;/span>&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">connection&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_storage&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&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">53&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_next_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&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">def&lt;/span> &lt;span class="nf">get&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">pk&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">-&amp;gt;&lt;/span> &lt;span class="n">T&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">56&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">_storage&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">pk&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>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">save&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">entity&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">T&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="c1"># 驗證欄位&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_validate_entity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">entity&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>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">entity&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&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">63&lt;/span>&lt;span class="cl"> &lt;span class="n">entity&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&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">_next_id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_next_id&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_storage&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">entity&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">entity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">66&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">entity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">delete&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">pk&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">-&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">69&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">pk&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">_storage&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="k">del&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_storage&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">pk&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="k">return&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&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">73&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">find_all&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">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">75&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">list&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">_storage&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">values&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>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">_validate_entity&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">entity&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">T&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">78&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">79&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 這裡可以整合 FieldType 的驗證邏輯&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">80&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="上下文管理交易">上下文管理：交易&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="c1"># miniorm/transaction.py&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="kn">from&lt;/span> &lt;span class="nn">contextlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">contextmanager&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">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Iterator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">enum&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Enum&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">auto&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">class&lt;/span> &lt;span class="nc">TransactionState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Enum&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">ACTIVE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">auto&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">COMMITTED&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">auto&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">ROLLED_BACK&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">auto&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="nd">@dataclass&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">class&lt;/span> &lt;span class="nc">Transaction&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;&amp;#34;&amp;#34;交易物件&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="nb">id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&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">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">TransactionState&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ACTIVE&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">_operations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&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">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="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">21&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_operations&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">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="k">def&lt;/span> &lt;span class="nf">add_operation&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">op&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">24&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">state&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ACTIVE&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">raise&lt;/span> &lt;span class="n">TransactionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Transaction is not active&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_operations&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">op&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>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">commit&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="kc">None&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="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ACTIVE&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="k">raise&lt;/span> &lt;span class="n">TransactionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Transaction is not active&amp;#34;&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="c1"># 實際應用中這裡會提交到資料庫&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">COMMITTED&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">rollback&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="kc">None&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="k">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">COMMITTED&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">raise&lt;/span> &lt;span class="n">TransactionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Cannot rollback committed transaction&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="c1"># 實際應用中這裡會回滾操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_operations&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clear&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ROLLED_BACK&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">class&lt;/span> &lt;span class="nc">Connection&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="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">43&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&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">url&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="kc">None&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">url&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_tx_counter&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">47&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_current_tx&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Transaction&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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">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="nd">@contextmanager&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">def&lt;/span> &lt;span class="nf">transaction&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="n">Iterator&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Transaction&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="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">52&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">_current_tx&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&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">53&lt;/span>&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="n">TransactionError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Nested transactions not supported&amp;#34;&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_tx_counter&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&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">tx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Transaction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">id&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">_tx_counter&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_current_tx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tx&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&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">60&lt;/span>&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="n">tx&lt;/span>
&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="n">tx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ACTIVE&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">tx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">commit&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="k">except&lt;/span> &lt;span class="ne">Exception&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="k">if&lt;/span> &lt;span class="n">tx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">state&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">TransactionState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ACTIVE&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">tx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">rollback&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="k">raise&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="k">finally&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_current_tx&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">69&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&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">71&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">in_transaction&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">bool&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 class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_current_tx&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="整合使用範例">整合使用範例&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="c1"># 定義 Model&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">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"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">datetime&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="nd">@dataclass&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">class&lt;/span> &lt;span class="nc">User&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="nb">id&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">0&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">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&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">email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&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">created_at&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">datetime&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="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&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"># 定義具體的 Repository&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">class&lt;/span> &lt;span class="nc">UserRepository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">InMemoryRepository&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">User&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">model_class&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">User&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="k">def&lt;/span> &lt;span class="nf">main&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="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">conn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Connection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;memory://&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>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 建立 Repository&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">users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">UserRepository&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">conn&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>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 使用交易&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">with&lt;/span> &lt;span class="n">conn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transaction&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">tx&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="c1"># 建立使用者&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">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;alice@example.com&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">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&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"># 更多操作...&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">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Alice Smith&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="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user&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>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 交易外的操作&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">try&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="n">found&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_or_raise&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">999&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">except&lt;/span> &lt;span class="n">EntityNotFoundError&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">38&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;Error: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">message&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">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="c1"># 列出所有使用者&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">for&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">users&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">find_all&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;User: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> (&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">user&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">email&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;h2 id="案例二任務排程器">案例二：任務排程器&lt;/h2>
&lt;p>建立一個支援並行執行的任務排程器。&lt;/p></description><content:encoded><![CDATA[<p>本章透過兩個完整案例，展示如何將前四章的設計模式結合應用。每個案例都會說明各模式的協作關係。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>本模組 3.5.1-3.5.4 所有章節</li>
</ul>
<h2 id="案例一迷你-orm-框架">案例一：迷你 ORM 框架</h2>
<p>建立一個簡化的 ORM（Object-Relational Mapping）框架，展示各模式的整合。</p>
<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">│                     MiniORM Framework                    │
</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">│  泛型        │ Repository[T] - 型別安全的資料存取       │
</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">│  上下文      │ Transaction - 交易管理                   │
</span></span><span class="line"><span class="ln">7</span><span class="cl">│  插件        │ FieldType - 可擴展的欄位型別             │
</span></span><span class="line"><span class="ln">8</span><span class="cl">└─────────────────────────────────────────────────────────┘</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="c1"># miniorm/exceptions.py</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">ORMError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;ORM 框架的基礎異常&#34;&#34;&#34;</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">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">code</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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"> 7</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">message</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">message</span> <span class="o">=</span> <span class="n">message</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">code</span> <span class="o">=</span> <span class="n">code</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">class</span> <span class="nc">ConnectionError</span><span class="p">(</span><span class="n">ORMError</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 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="k">class</span> <span class="nc">QueryError</span><span class="p">(</span><span class="n">ORMError</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="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="k">class</span> <span class="nc">EntityNotFoundError</span><span class="p">(</span><span class="n">ORMError</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></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">model</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</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">23</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">model</span><span class="si">}</span><span class="s2"> with pk=</span><span class="si">{</span><span class="n">pk</span><span class="si">}</span><span class="s2"> not found&#34;</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="s2">&#34;NOT_FOUND&#34;</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">model</span> <span class="o">=</span> <span class="n">model</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">pk</span> <span class="o">=</span> <span class="n">pk</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">class</span> <span class="nc">ValidationError</span><span class="p">(</span><span class="n">ORMError</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">message</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">31</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation failed for &#39;</span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">&#39;: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">code</span><span class="o">=</span><span class="s2">&#34;VALIDATION&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">field</span> <span class="o">=</span> <span class="n">field</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">class</span> <span class="nc">TransactionError</span><span class="p">(</span><span class="n">ORMError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="s2">&#34;&#34;&#34;交易錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</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="c1"># miniorm/fields.py</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">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln">  4</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">ClassVar</span>
</span></span><span class="line"><span class="ln">  5</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">  6</span><span class="cl">
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="k">class</span> <span class="nc">FieldType</span><span class="p">(</span><span class="n">ABC</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">_registry</span><span class="p">:</span> <span class="n">ClassVar</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="nb">type</span><span class="p">[</span><span class="s2">&#34;FieldType&#34;</span><span class="p">]]]</span> <span class="o">=</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">type_name</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">    <span class="n">python_type</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">type</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">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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"> 16</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init_subclass__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;type_name&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">            <span class="n">FieldType</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="bp">cls</span><span class="o">.</span><span class="n">type_name</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">    <span class="k">def</span> <span class="nf">to_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Python 值轉換為資料庫值&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="k">def</span> <span class="nf">from_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="s2">&#34;&#34;&#34;資料庫值轉換為 Python 值&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證值，失敗時拋出 ValidationError&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="o">...</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="k">def</span> <span class="nf">get_type</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">type</span><span class="p">[</span><span class="s2">&#34;FieldType&#34;</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"> 37</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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">class</span> <span class="nc">IntegerField</span><span class="p">(</span><span class="n">FieldType</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">type_name</span> <span class="o">=</span> <span class="s2">&#34;integer&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="n">python_type</span> <span class="o">=</span> <span class="nb">int</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="k">def</span> <span class="nf">to_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 45</span><span class="cl">        <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">value</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">from_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 48</span><span class="cl">        <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <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="k">else</span> <span class="mi">0</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="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 51</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="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">int</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">            <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Expected int, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</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></span><span class="line"><span class="ln"> 54</span><span class="cl"><span class="k">class</span> <span class="nc">StringField</span><span class="p">(</span><span class="n">FieldType</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="n">type_name</span> <span class="o">=</span> <span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="n">python_type</span> <span class="o">=</span> <span class="nb">str</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_length</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">255</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"> 59</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</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">def</span> <span class="nf">to_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 62</span><span class="cl">        <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">value</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="k">def</span> <span class="nf">from_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 65</span><span class="cl">        <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <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="k">else</span> <span class="s2">&#34;&#34;</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 68</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"> 69</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</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"> 70</span><span class="cl">                <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Expected str, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">            <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">                <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Max length is </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">max_length</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</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">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">FieldType</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="n">type_name</span> <span class="o">=</span> <span class="s2">&#34;datetime&#34;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">    <span class="n">python_type</span> <span class="o">=</span> <span class="n">datetime</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">def</span> <span class="nf">to_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 79</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="n">datetime</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">            <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">value</span><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="k">def</span> <span class="nf">from_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">datetime</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 84</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="n">datetime</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">value</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">fromisoformat</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">value</span><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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 89</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="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">datetime</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">            <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Expected datetime, got </span><span class="si">{</span><span class="nb">type</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><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="c1"># 使用者可以擴展新的欄位型別</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="k">class</span> <span class="nc">JSONField</span><span class="p">(</span><span class="n">FieldType</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">    <span class="s2">&#34;&#34;&#34;自訂欄位型別範例&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">    <span class="n">type_name</span> <span class="o">=</span> <span class="s2">&#34;json&#34;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="n">python_type</span> <span class="o">=</span> <span class="nb">dict</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">def</span> <span class="nf">to_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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"> 99</span><span class="cl">        <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">value</span><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="k">def</span> <span class="nf">from_db</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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">103</span><span class="cl">        <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">104</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">dict</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">            <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">value</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">
</span></span><span class="line"><span class="ln">108</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="n">Any</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">109</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="ow">and</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="p">(</span><span class="nb">dict</span><span class="p">,</span> <span class="nb">list</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">            <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="s2">&#34;value&#34;</span><span class="p">,</span> <span class="s2">&#34;Expected dict or list&#34;</span><span class="p">)</span></span></span></code></pre></div><h3 id="泛型-repository">泛型 Repository</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"># miniorm/repository.py</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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Protocol</span><span class="p">,</span> <span class="n">ClassVar</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</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">HasId</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;具有 id 屬性的協議&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</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">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">HasId</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">class</span> <span class="nc">Repository</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">13</span><span class="cl">    <span class="s2">&#34;&#34;&#34;泛型 Repository 基類&#34;&#34;&#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="n">model_class</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">type</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">connection</span><span class="p">:</span> <span class="s2">&#34;Connection&#34;</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">18</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_connection</span> <span class="o">=</span> <span class="n">connection</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="nd">@abstractmethod</span>
</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">pk</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;&#34;&#34;根據主鍵取得實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</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">27</span><span class="cl">        <span class="s2">&#34;&#34;&#34;儲存實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="nb">int</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">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;刪除實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">def</span> <span class="nf">find_all</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">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="s2">&#34;&#34;&#34;取得所有實體&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="o">...</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">get_or_raise</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="nb">int</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">41</span><span class="cl">        <span class="s2">&#34;&#34;&#34;取得實體，不存在時拋出異常&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">entity</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">pk</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">entity</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">            <span class="k">raise</span> <span class="n">EntityNotFoundError</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">pk</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="n">entity</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">class</span> <span class="nc">InMemoryRepository</span><span class="p">(</span><span class="n">Repository</span><span class="p">[</span><span class="n">T</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="s2">&#34;&#34;&#34;記憶體實作的 Repository&#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">connection</span><span class="p">:</span> <span class="s2">&#34;Connection&#34;</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">51</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">connection</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">int</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">53</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_next_id</span> <span class="o">=</span> <span class="mi">1</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">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</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="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</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">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</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">59</span><span class="cl">        <span class="c1"># 驗證欄位</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_validate_entity</span><span class="p">(</span><span class="n">entity</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">if</span> <span class="n">entity</span><span class="o">.</span><span class="n">id</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="n">entity</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_next_id</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_next_id</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">[</span><span class="n">entity</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">entity</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">        <span class="k">return</span> <span class="n">entity</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="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="nb">int</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">69</span><span class="cl">        <span class="k">if</span> <span class="n">pk</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">            <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="k">return</span> <span class="kc">False</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">find_all</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">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">        <span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_storage</span><span class="o">.</span><span class="n">values</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">def</span> <span class="nf">_validate_entity</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entity</span><span class="p">:</span> <span class="n">T</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">78</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證實體的所有欄位&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="c1"># 這裡可以整合 FieldType 的驗證邏輯</span>
</span></span><span class="line"><span class="ln">80</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="c1"># miniorm/transaction.py</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Iterator</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 class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span><span class="p">,</span> <span class="n">auto</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">class</span> <span class="nc">TransactionState</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">ACTIVE</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">COMMITTED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">ROLLED_BACK</span> <span class="o">=</span> <span class="n">auto</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">Transaction</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="nb">id</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">state</span><span class="p">:</span> <span class="n">TransactionState</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">_operations</span><span class="p">:</span> <span class="nb">list</span> <span class="o">=</span> <span class="kc">None</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">__post_init__</span><span class="p">(</span><span class="bp">self</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">_operations</span> <span class="o">=</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">def</span> <span class="nf">add_operation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">op</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">24</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">raise</span> <span class="n">TransactionError</span><span class="p">(</span><span class="s2">&#34;Transaction is not active&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_operations</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">op</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">commit</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">29</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">!=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">raise</span> <span class="n">TransactionError</span><span class="p">(</span><span class="s2">&#34;Transaction is not active&#34;</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="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">COMMITTED</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="nf">rollback</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">35</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">COMMITTED</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="k">raise</span> <span class="n">TransactionError</span><span class="p">(</span><span class="s2">&#34;Cannot rollback committed transaction&#34;</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="bp">self</span><span class="o">.</span><span class="n">_operations</span><span class="o">.</span><span class="n">clear</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">state</span> <span class="o">=</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ROLLED_BACK</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">class</span> <span class="nc">Connection</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="s2">&#34;&#34;&#34;資料庫連接&#34;&#34;&#34;</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="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url</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">45</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="n">url</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_current_tx</span><span class="p">:</span> <span class="n">Transaction</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="k">def</span> <span class="nf">transaction</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">Transaction</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="s2">&#34;&#34;&#34;交易上下文管理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_current_tx</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">53</span><span class="cl">            <span class="k">raise</span> <span class="n">TransactionError</span><span class="p">(</span><span class="s2">&#34;Nested transactions not supported&#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="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">tx</span> <span class="o">=</span> <span class="n">Transaction</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">_tx_counter</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_current_tx</span> <span class="o">=</span> <span class="n">tx</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">            <span class="k">yield</span> <span class="n">tx</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="k">if</span> <span class="n">tx</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                <span class="n">tx</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="k">if</span> <span class="n">tx</span><span class="o">.</span><span class="n">state</span> <span class="o">==</span> <span class="n">TransactionState</span><span class="o">.</span><span class="n">ACTIVE</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="n">tx</span><span class="o">.</span><span class="n">rollback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="k">raise</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_current_tx</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">    <span class="k">def</span> <span class="nf">in_transaction</span><span class="p">(</span><span class="bp">self</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">72</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_current_tx</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</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="c1"># 定義 Model</span>
</span></span><span class="line"><span class="ln"> 2</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"> 3</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"> 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">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="nb">id</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"> 8</span><span class="cl">    <span class="n">name</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"> 9</span><span class="cl">    <span class="n">email</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">10</span><span class="cl">    <span class="n">created_at</span><span class="p">:</span> <span class="n">datetime</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="n">datetime</span><span class="o">.</span><span class="n">now</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"># 定義具體的 Repository</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">UserRepository</span><span class="p">(</span><span class="n">InMemoryRepository</span><span class="p">[</span><span class="n">User</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">model_class</span> <span class="o">=</span> <span class="n">User</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="k">def</span> <span class="nf">main</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">conn</span> <span class="o">=</span> <span class="n">Connection</span><span class="p">(</span><span class="s2">&#34;memory://&#34;</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"># 建立 Repository</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">users</span> <span class="o">=</span> <span class="n">UserRepository</span><span class="p">(</span><span class="n">conn</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="c1"># 使用交易</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">with</span> <span class="n">conn</span><span class="o">.</span><span class="n">transaction</span><span class="p">()</span> <span class="k">as</span> <span class="n">tx</span><span class="p">:</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">user</span> <span class="o">=</span> <span class="n">User</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;Alice&#34;</span><span class="p">,</span> <span class="n">email</span><span class="o">=</span><span class="s2">&#34;alice@example.com&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="n">users</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">user</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"># 更多操作...</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">user</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Alice Smith&#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">users</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">user</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"># 交易外的操作</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">found</span> <span class="o">=</span> <span class="n">users</span><span class="o">.</span><span class="n">get_or_raise</span><span class="p">(</span><span class="mi">999</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="k">except</span> <span class="n">EntityNotFoundError</span> <span class="k">as</span> <span class="n">e</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;Error: </span><span class="si">{</span><span class="n">e</span><span class="o">.</span><span class="n">message</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="c1"># 列出所有使用者</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="k">for</span> <span class="n">user</span> <span class="ow">in</span> <span class="n">users</span><span class="o">.</span><span class="n">find_all</span><span class="p">():</span>
</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;User: </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">email</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="案例二任務排程器">案例二：任務排程器</h2>
<p>建立一個支援並行執行的任務排程器。</p>
<h3 id="設計概覽-1">設計概覽</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">│                    Task Scheduler                        │
</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">│  泛型        │ Task[T] - 型別安全的任務定義             │
</span></span><span class="line"><span class="ln">5</span><span class="cl">│  異常        │ ExceptionGroup - 並行錯誤處理            │
</span></span><span class="line"><span class="ln">6</span><span class="cl">│  上下文      │ TaskContext - 資源生命週期               │
</span></span><span class="line"><span class="ln">7</span><span class="cl">│  插件        │ TaskHandler - 可擴展的處理器             │
</span></span><span class="line"><span class="ln">8</span><span class="cl">└─────────────────────────────────────────────────────────┘</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="c1"># scheduler/exceptions.py</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">SchedulerError</span><span class="p">(</span><span class="ne">Exception</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">pass</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">class</span> <span class="nc">TaskError</span><span class="p">(</span><span class="n">SchedulerError</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></span><span class="line"><span class="ln">10</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">task_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">message</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">cause</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">11</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task &#39;</span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2">&#39; failed: </span><span class="si">{</span><span class="n">message</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="bp">self</span><span class="o">.</span><span class="n">task_id</span> <span class="o">=</span> <span class="n">task_id</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="n">cause</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">__cause__</span> <span class="o">=</span> <span class="n">cause</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">class</span> <span class="nc">TimeoutError</span><span class="p">(</span><span class="n">TaskError</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="k">pass</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">class</span> <span class="nc">DependencyError</span><span class="p">(</span><span class="n">TaskError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;&#34;&#34;依賴任務失敗&#34;&#34;&#34;</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">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">task_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">failed_deps</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="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">task_id</span><span class="p">,</span> <span class="sa">f</span><span class="s2">&#34;Dependencies failed: </span><span class="si">{</span><span class="n">failed_deps</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="bp">self</span><span class="o">.</span><span class="n">failed_deps</span> <span class="o">=</span> <span class="n">failed_deps</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="c1"># scheduler/task.py</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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Any</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 class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 5</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 class="p">,</span> <span class="n">auto</span>
</span></span><span class="line"><span class="ln"> 6</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"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#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="k">class</span> <span class="nc">TaskStatus</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">PENDING</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">RUNNING</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">COMPLETED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">CANCELLED</span> <span class="o">=</span> <span class="n">auto</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">class</span> <span class="nc">TaskResult</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">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="n">value</span><span class="p">:</span> <span class="n">T</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="ne">Exception</span> <span class="o">|</span> <span class="kc">None</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">started_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</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">completed_at</span><span class="p">:</span> <span class="n">datetime</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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="nd">@property</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">def</span> <span class="nf">success</span><span class="p">(</span><span class="bp">self</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">27</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">error</span> <span class="ow">is</span> <span class="kc">None</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="nd">@property</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="k">def</span> <span class="nf">duration</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">started_at</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">completed_at</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 class="bp">self</span><span class="o">.</span><span class="n">completed_at</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">started_at</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">33</span><span class="cl">        <span class="k">return</span> <span class="kc">None</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">class</span> <span class="nc">Task</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">37</span><span class="cl">    <span class="s2">&#34;&#34;&#34;泛型任務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">handler</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="s2">&#34;TaskContext&#34;</span><span class="p">],</span> <span class="n">T</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="n">dependencies</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">=</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">timeout</span><span class="p">:</span> <span class="nb">float</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">status</span><span class="p">:</span> <span class="n">TaskStatus</span> <span class="o">=</span> <span class="n">TaskStatus</span><span class="o">.</span><span class="n">PENDING</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">result</span><span class="p">:</span> <span class="n">TaskResult</span><span class="p">[</span><span class="n">T</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</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">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">TaskResult</span><span class="p">[</span><span class="n">T</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="s2">&#34;&#34;&#34;執行任務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">TaskResult</span><span class="p">[</span><span class="n">T</span><span class="p">](</span><span class="o">/</span><span class="n">python</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="mi">03</span><span class="o">-</span><span class="n">design</span><span class="o">-</span><span class="n">patterns</span><span class="o">/</span><span class="n">integration</span><span class="o">/</span><span class="n">started_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">48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">TaskStatus</span><span class="o">.</span><span class="n">RUNNING</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</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">handler</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">result</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">53</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">TaskStatus</span><span class="o">.</span><span class="n">COMPLETED</span>
</span></span><span class="line"><span class="ln">54</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">55</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">error</span> <span class="o">=</span> <span class="n">e</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">status</span> <span class="o">=</span> <span class="n">TaskStatus</span><span class="o">.</span><span class="n">FAILED</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">completed_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">59</span><span class="cl">
</span></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">result</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="k">return</span> <span class="n">result</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="c1"># scheduler/handlers.py</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">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">ClassVar</span><span class="p">,</span> <span class="n">Protocol</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">abstractmethod</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">TaskHandlerProtocol</span><span class="p">(</span><span class="n">Protocol</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></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">def</span> <span class="nf">can_handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">task_type</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="o">...</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">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="o">...</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">class</span> <span class="nc">TaskHandler</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="n">_registry</span><span class="p">:</span> <span class="n">ClassVar</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="s2">&#34;TaskHandler&#34;</span><span class="p">]]</span> <span class="o">=</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="n">task_type</span><span class="p">:</span> <span class="n">ClassVar</span><span class="p">[</span><span class="nb">str</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="k">def</span> <span class="nf">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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">22</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init_subclass__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</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="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s2">&#34;task_type&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">TaskHandler</span><span class="o">.</span><span class="n">_registry</span><span class="p">[</span><span class="bp">cls</span><span class="o">.</span><span class="n">task_type</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</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 class="o">...</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">get_handler</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">task_type</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;TaskHandler | None&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_registry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">task_type</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="c1"># 具體的處理器</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="k">class</span> <span class="nc">HttpRequestHandler</span><span class="p">(</span><span class="n">TaskHandler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="s2">&#34;&#34;&#34;HTTP 請求處理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">task_type</span> <span class="o">=</span> <span class="s2">&#34;http_request&#34;</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">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</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">41</span><span class="cl">        <span class="kn">import</span> <span class="nn">urllib.request</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">        <span class="n">url</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s2">&#34;url&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="n">method</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;method&#34;</span><span class="p">,</span> <span class="s2">&#34;GET&#34;</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="c1"># 簡化的實作</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</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="s2">&#34;status&#34;</span><span class="p">:</span> <span class="n">response</span><span class="o">.</span><span class="n">status</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">                <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">response</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><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="k">class</span> <span class="nc">ShellCommandHandler</span><span class="p">(</span><span class="n">TaskHandler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Shell 命令處理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">    <span class="n">task_type</span> <span class="o">=</span> <span class="s2">&#34;shell&#34;</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">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</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">57</span><span class="cl">        <span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="n">command</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s2">&#34;command&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="n">timeout</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;timeout&#34;</span><span class="p">,</span> <span class="mi">30</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="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="n">command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="n">shell</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">            <span class="n">capture_output</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">            <span class="n">text</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="n">timeout</span><span class="o">=</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <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">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">            <span class="s2">&#34;returncode&#34;</span><span class="p">:</span> <span class="n">result</span><span class="o">.</span><span class="n">returncode</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">            <span class="s2">&#34;stdout&#34;</span><span class="p">:</span> <span class="n">result</span><span class="o">.</span><span class="n">stdout</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">            <span class="s2">&#34;stderr&#34;</span><span class="p">:</span> <span class="n">result</span><span class="o">.</span><span class="n">stderr</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">class</span> <span class="nc">DataProcessHandler</span><span class="p">(</span><span class="n">TaskHandler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">    <span class="s2">&#34;&#34;&#34;資料處理器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="n">task_type</span> <span class="o">=</span> <span class="s2">&#34;data_process&#34;</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">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">:</span> <span class="s2">&#34;TaskContext&#34;</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
</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 class="n">data</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">get_resource</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">        <span class="n">operation</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;operation&#34;</span><span class="p">,</span> <span class="s2">&#34;identity&#34;</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="n">operation</span> <span class="o">==</span> <span class="s2">&#34;transform&#34;</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="p">[</span><span class="n">item</span> <span class="o">*</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">86</span><span class="cl">        <span class="k">elif</span> <span class="n">operation</span> <span class="o">==</span> <span class="s2">&#34;filter&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">            <span class="k">return</span> <span class="p">[</span><span class="n">item</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="n">item</span> <span class="o">&gt;</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="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">            <span class="k">return</span> <span class="n">data</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="c1"># scheduler/context.py</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">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span><span class="p">,</span> <span class="n">ExitStack</span>
</span></span><span class="line"><span class="ln"> 4</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">Iterator</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 class="p">,</span> <span class="n">field</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">TaskContext</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="n">task_id</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">_resources</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">Any</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">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">_cleanup_callbacks</span><span class="p">:</span> <span class="nb">list</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">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">set_resource</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</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="n">Any</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">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="bp">self</span><span class="o">.</span><span class="n">_resources</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</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">get_resource</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span> <span class="o">|</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;取得共享資源&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_resources</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</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">add_cleanup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">callback</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">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">_cleanup_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">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">cleanup</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="s2">&#34;&#34;&#34;執行所有清理回調&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">28</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">29</span><span class="cl">        <span class="k">for</span> <span class="n">callback</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_cleanup_callbacks</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">                <span class="n">callback</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</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">33</span><span class="cl">                <span class="n">errors</span><span class="o">.</span><span class="n">append</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></span><span class="line"><span class="ln">35</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">36</span><span class="cl">            <span class="k">raise</span> <span class="n">ExceptionGroup</span><span class="p">(</span><span class="s2">&#34;Cleanup failed&#34;</span><span class="p">,</span> <span class="n">errors</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="nd">@contextmanager</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">def</span> <span class="nf">task_context</span><span class="p">(</span><span class="n">task_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Iterator</span><span class="p">[</span><span class="n">TaskContext</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="n">ctx</span> <span class="o">=</span> <span class="n">TaskContext</span><span class="p">(</span><span class="n">task_id</span><span class="o">=</span><span class="n">task_id</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="k">yield</span> <span class="n">ctx</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="k">finally</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="n">ctx</span><span class="o">.</span><span class="n">cleanup</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="c1"># scheduler/scheduler.py</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">asyncio</span>
</span></span><span class="line"><span class="ln"> 4</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></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 class="p">,</span> <span class="n">field</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="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">Scheduler</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="n">_tasks</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">Task</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">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">_results</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">TaskResult</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">dict</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">add_task</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">task</span><span class="p">:</span> <span class="n">Task</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">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;新增任務&#34;&#34;&#34;</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">_tasks</span><span class="p">[</span><span class="n">task</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">task</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">_get_execution_order</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="nb">list</span><span class="p">[</span><span class="nb">str</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="c1"># 簡化實作：按依賴深度分層</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">layers</span><span class="p">:</span> <span class="nb">list</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">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">remaining</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tasks</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">completed</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="nb">set</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">while</span> <span class="n">remaining</span><span class="p">:</span>
</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">ready</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">tid</span> <span class="k">for</span> <span class="n">tid</span> <span class="ow">in</span> <span class="n">remaining</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">dep</span> <span class="ow">in</span> <span class="n">completed</span> <span class="k">for</span> <span class="n">dep</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_tasks</span><span class="p">[</span><span class="n">tid</span><span class="p">]</span><span class="o">.</span><span class="n">dependencies</span><span class="p">)</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="k">if</span> <span class="ow">not</span> <span class="n">ready</span><span class="p">:</span>
</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="k">raise</span> <span class="n">SchedulerError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Circular dependency detected: </span><span class="si">{</span><span class="n">remaining</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="n">layers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ready</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="k">for</span> <span class="n">tid</span> <span class="ow">in</span> <span class="n">ready</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">                <span class="n">remaining</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">tid</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="n">completed</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">tid</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="k">return</span> <span class="n">layers</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">async</span> <span class="k">def</span> <span class="nf">run_async</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 class="nb">str</span><span class="p">,</span> <span class="n">TaskResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="s2">&#34;&#34;&#34;非同步執行所有任務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">layers</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_get_execution_order</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">layer</span> <span class="ow">in</span> <span class="n">layers</span><span class="p">:</span>
</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="n">tasks_to_run</span> <span class="o">=</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="k">for</span> <span class="n">task_id</span> <span class="ow">in</span> <span class="n">layer</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">                <span class="n">task</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_tasks</span><span class="p">[</span><span class="n">task_id</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 class="n">failed_deps</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">dep</span> <span class="k">for</span> <span class="n">dep</span> <span class="ow">in</span> <span class="n">task</span><span class="o">.</span><span class="n">dependencies</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">                    <span class="k">if</span> <span class="n">dep</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_results</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">[</span><span class="n">dep</span><span class="p">]</span><span class="o">.</span><span class="n">success</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="k">if</span> <span class="n">failed_deps</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">                    <span class="c1"># 依賴失敗，跳過此任務</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">TaskResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">                        <span class="n">error</span><span class="o">=</span><span class="n">DependencyError</span><span class="p">(</span><span class="n">task_id</span><span class="p">,</span> <span class="n">failed_deps</span><span class="p">)</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="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">[</span><span class="n">task_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                    <span class="k">continue</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">tasks_to_run</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">_run_task_async</span><span class="p">(</span><span class="n">task</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="c1"># 並行執行，收集所有錯誤</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">            <span class="k">if</span> <span class="n">tasks_to_run</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">                    <span class="k">async</span> <span class="k">with</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">TaskGroup</span><span class="p">()</span> <span class="k">as</span> <span class="n">tg</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">                        <span class="k">for</span> <span class="n">coro</span> <span class="ow">in</span> <span class="n">tasks_to_run</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">                            <span class="n">tg</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="n">coro</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">                <span class="k">except</span><span class="o">*</span> <span class="n">TaskError</span> <span class="k">as</span> <span class="n">eg</span><span class="p">:</span>
</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="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">eg</span><span class="o">.</span><span class="n">exceptions</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">                        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Task failed: </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><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">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_results</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">async</span> <span class="k">def</span> <span class="nf">_run_task_async</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">task</span><span class="p">:</span> <span class="n">Task</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;執行單個任務&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">        <span class="k">with</span> <span class="n">task_context</span><span class="p">(</span><span class="n">task</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="k">as</span> <span class="n">ctx</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">            <span class="c1"># 設定共享資源（從依賴任務的結果）</span>
</span></span><span class="line"><span class="ln">86</span><span class="cl">            <span class="k">for</span> <span class="n">dep_id</span> <span class="ow">in</span> <span class="n">task</span><span class="o">.</span><span class="n">dependencies</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">                <span class="k">if</span> <span class="n">dep_id</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">                    <span class="n">ctx</span><span class="o">.</span><span class="n">set_resource</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;result_</span><span class="si">{</span><span class="n">dep_id</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">_results</span><span class="p">[</span><span class="n">dep_id</span><span class="p">]</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">
</span></span><span class="line"><span class="ln">90</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</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">_results</span><span class="p">[</span><span class="n">task</span><span class="o">.</span><span class="n">id</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">92</span><span class="cl">
</span></span><span class="line"><span class="ln">93</span><span class="cl">            <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">error</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">94</span><span class="cl">                <span class="k">raise</span> <span class="n">TaskError</span><span class="p">(</span><span class="n">task</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">error</span><span class="p">),</span> <span class="n">result</span><span class="o">.</span><span class="n">error</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">run</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 class="nb">str</span><span class="p">,</span> <span class="n">TaskResult</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">97</span><span class="cl">        <span class="s2">&#34;&#34;&#34;同步執行&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">98</span><span class="cl">        <span class="k">return</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">run_async</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="c1"># 建立排程器</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">scheduler</span> <span class="o">=</span> <span class="n">Scheduler</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="k">def</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">TaskContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</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="k">return</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="mi">4</span><span class="p">,</span> <span class="mi">5</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="k">def</span> <span class="nf">process_data</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">TaskContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;處理資料&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">get_resource</span><span class="p">(</span><span class="s2">&#34;result_fetch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">x</span> <span class="o">*</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">data</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">save_result</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="n">TaskContext</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">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">data</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">get_resource</span><span class="p">(</span><span class="s2">&#34;result_process&#34;</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="sa">f</span><span class="s2">&#34;Saved </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="si">}</span><span class="s2"> items&#34;</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">scheduler</span><span class="o">.</span><span class="n">add_task</span><span class="p">(</span><span class="n">Task</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;fetch&#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">fetch_data</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="n">scheduler</span><span class="o">.</span><span class="n">add_task</span><span class="p">(</span><span class="n">Task</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;process&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">handler</span><span class="o">=</span><span class="n">process_data</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">dependencies</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;fetch&#34;</span><span class="p">]</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="n">scheduler</span><span class="o">.</span><span class="n">add_task</span><span class="p">(</span><span class="n">Task</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="nb">id</span><span class="o">=</span><span class="s2">&#34;save&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">handler</span><span class="o">=</span><span class="n">save_result</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">dependencies</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;process&#34;</span><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="c1"># 執行</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="n">results</span> <span class="o">=</span> <span class="n">scheduler</span><span class="o">.</span><span class="n">run</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="k">for</span> <span class="n">task_id</span><span class="p">,</span> <span class="n">result</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">if</span> <span class="n">result</span><span class="o">.</span><span class="n">success</span><span class="p">:</span>
</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;[OK] </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">value</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">result</span><span class="o">.</span><span class="n">duration</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">44</span><span class="cl">    <span class="k">else</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;[FAIL] </span><span class="si">{</span><span class="n">task_id</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">result</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></code></pre></div><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">├─────────────────────────────────────────────────────────────────┤
</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">│   │ Repository  │     │ 層級設計    │     │ Transaction │       │
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   │ Task[T]     │     │ 異常鏈      │     │ TaskContext │       │
</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">│          │                   │                   │               │
</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">│   │  FieldType  │  TaskHandler  │  HookPlugin          │       │
</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></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">1. 泛型確保型別安全，在編譯期捕獲錯誤
</span></span><span class="line"><span class="ln">22</span><span class="cl">2. 異常提供精確的錯誤處理，支援錯誤傳播和轉換
</span></span><span class="line"><span class="ln">23</span><span class="cl">3. 上下文管理資源生命週期，確保正確清理
</span></span><span class="line"><span class="ln">24</span><span class="cl">4. 插件系統提供擴展點，允許自訂行為</span></span></code></pre></div><h2 id="設計原則總結">設計原則總結</h2>
<table>
  <thead>
      <tr>
          <th>模式</th>
          <th>解決的問題</th>
          <th>使用時機</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>泛型</td>
          <td>型別安全的重用</td>
          <td>容器、Repository、服務介面</td>
      </tr>
      <tr>
          <td>異常層級</td>
          <td>精確的錯誤處理</td>
          <td>大型專案、API 設計</td>
      </tr>
      <tr>
          <td>上下文管理</td>
          <td>資源生命週期</td>
          <td>連接、交易、臨時資源</td>
      </tr>
      <tr>
          <td>插件系統</td>
          <td>可擴展性</td>
          <td>框架設計、開放式架構</td>
      </tr>
  </tbody>
</table>
<h2 id="思考題">思考題</h2>
<ol>
<li>如何在這些案例中加入日誌記錄？</li>
<li>如果要支援分散式執行，需要修改哪些部分？</li>
<li>如何為這些框架加入效能監控？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">3.5.4 插件系統設計</a></em>
<em>回到模組目錄：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組 3.5：進階設計模式</a></em></p>
]]></content:encoded></item><item><title>3.5.6 軟體設計的取捨藝術</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/trade-offs/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/trade-offs/</guid><description>&lt;p>前五章介紹了泛型、異常設計、上下文管理、插件系統等進階設計模式。但在真實專案中，最困難的往往不是「如何實作」，而是「該不該這樣做」。本章從英文技術社群的經驗中提煉出實用的取捨決策框架，幫助你在面對兩難時做出更好的判斷。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>本模組 3.5.1-3.5.5 所有章節&lt;/li>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/04-oop/" data-link-title="模組四：物件導向設計" data-link-desc="Python 的物件導向設計與設計模式">模組四：物件導向設計&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="為什麼取捨不可避免">為什麼取捨不可避免？&lt;/h2>
&lt;p>Stack Overflow 的技術部落格曾發表一篇文章 &lt;a href="https://stackoverflow.blog/2022/01/17/plan-for-tradeoffs-you-cant-optimize-all-software-quality-attributes/">Plan for Tradeoffs&lt;/a>，核心論點是：&lt;strong>你無法同時最佳化所有軟體品質屬性&lt;/strong>。&lt;/p>
&lt;p>該文列出了 17 項核心品質屬性，包含可用性、效能、安全性、可維護性、可移植性等。它們之間存在天然的衝突：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>安全性 vs 易用性&lt;/strong>：多因素驗證提高了安全性，但增加了使用步驟&lt;/li>
&lt;li>&lt;strong>可重用性 vs 效率&lt;/strong>：泛用型元件的效能不如針對特定場景最佳化的程式碼&lt;/li>
&lt;li>&lt;strong>效能 vs 可移植性&lt;/strong>：平台特定的最佳化降低了跨平台能力&lt;/li>
&lt;/ul>
&lt;p>取捨是軟體開發的數學本質，跟工程師能力無關。每個設計決策都在一個多維空間中選擇一個點，無法讓每個維度都處於最佳值。&lt;/p>
&lt;h3 id="python-的哲學立場">Python 的哲學立場&lt;/h3>
&lt;p>Python 的設計者 Tim Peters 在 &lt;a href="https://peps.python.org/pep-0020/">PEP 20 &amp;ndash; The Zen of Python&lt;/a> 中早已預見了這個問題，其中幾條格言直接反映了取捨思維：&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">Simple is better than complex.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">Complex is better than complicated.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">Special cases aren&amp;#39;t special enough to break the rules.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">Although practicality beats purity.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>「Although practicality beats purity」（務實勝於純粹）是整個 Python 設計哲學的關鍵。它承認完美方案不存在，鼓勵工程師在原則和現實之間找到平衡。&lt;/p>
&lt;hr>
&lt;h2 id="六個核心取捨維度">六個核心取捨維度&lt;/h2>
&lt;p>從業界經驗中，我們可以歸納出六個最常見的取捨維度。&lt;/p>
&lt;h3 id="維度一重複-vs-錯誤抽象">維度一：重複 vs 錯誤抽象&lt;/h3>
&lt;p>這是軟體設計中最經典的取捨之一，近年來因為 &lt;a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">Sandi Metz 的 &amp;ldquo;The Wrong Abstraction&amp;rdquo;&lt;/a> 和 &lt;a href="https://kentcdodds.com/blog/aha-programming">Kent C. Dodds 的 AHA Programming&lt;/a> 而被重新審視。&lt;/p>
&lt;h4 id="傳統觀點--drydont-repeat-yourself">傳統觀點 &amp;ndash; DRY（Don&amp;rsquo;t Repeat Yourself）&lt;/h4>
&lt;blockquote>
&lt;p>&amp;ldquo;Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.&amp;rdquo;&lt;/p>&lt;/blockquote>
&lt;p>DRY 原則要求消除所有重複。這在很多情況下是正確的，但當它成為教條時，工程師會為了消除表面上的重複而建立過度複雜的抽象。&lt;/p>
&lt;h4 id="反思觀點--ahaavoid-hasty-abstractions">反思觀點 &amp;ndash; AHA（Avoid Hasty Abstractions）&lt;/h4>
&lt;p>Kent C. Dodds 提出了 AHA 程式設計的概念：&lt;strong>不要急於抽象，等到模式自然浮現&lt;/strong>。核心洞察是：&lt;/p>
&lt;ul>
&lt;li>重複的程式碼容易在之後重構&lt;/li>
&lt;li>但錯誤的抽象拆除起來痛苦得多&lt;/li>
&lt;/ul>
&lt;p>Sandi Metz 的名言精準地總結了這個觀點：&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Duplication is far cheaper than the wrong abstraction.&amp;rdquo;&lt;/p>
&lt;p>（重複的成本遠低於錯誤抽象的成本。）&lt;/p>&lt;/blockquote>
&lt;h4 id="錯誤抽象的演化過程">錯誤抽象的演化過程&lt;/h4>
&lt;p>Metz 描述了一個在真實專案中反覆出現的模式：&lt;/p>
&lt;ol>
&lt;li>工程師發現兩段重複程式碼，提取出抽象&lt;/li>
&lt;li>新需求出現，幾乎但不完全符合現有抽象&lt;/li>
&lt;li>後繼者加入參數和條件分支來適應新需求&lt;/li>
&lt;li>更多變體出現，更多條件被加入&lt;/li>
&lt;li>抽象變得難以理解，但沒人敢重寫（沉沒成本謬誤）&lt;/li>
&lt;li>所有人都害怕這段程式碼&lt;/li>
&lt;/ol>
&lt;h4 id="python-實際案例">Python 實際案例&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：發現重複，提取函式&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">process_user_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">dict&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="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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">validated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate_fields&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">normalize_strings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validated&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">return&lt;/span> &lt;span class="n">save_to_db&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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="c1"># 階段 2：「訂單資料也差不多嘛」，加入參數&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">def&lt;/span> &lt;span class="nf">process_data&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data_type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&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">10&lt;/span>&lt;span class="cl"> &lt;span class="n">validated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate_fields&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&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">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">normalize_strings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validated&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">if&lt;/span> &lt;span class="n">data_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;order&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="n">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">calculate_totals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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="k">return&lt;/span> &lt;span class="n">save_to_db&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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"># 階段 3：更多類型，更多分支&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">def&lt;/span> &lt;span class="nf">process_data&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="n">data&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">19&lt;/span>&lt;span class="cl"> &lt;span class="n">data_type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;user&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="n">skip_validation&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">False&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">custom_normalizer&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="kc">None&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">22&lt;/span>&lt;span class="cl"> &lt;span class="n">dry_run&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">False&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 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">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">skip_validation&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">validated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validate_fields&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">strict&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;payment&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="k">else&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">validated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&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">if&lt;/span> &lt;span class="n">custom_normalizer&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">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">custom_normalizer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validated&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="k">else&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="n">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">normalize_strings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">validated&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>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">data_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;order&amp;#34;&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="n">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">calculate_totals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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">elif&lt;/span> &lt;span class="n">data_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;payment&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="n">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encrypt_sensitive&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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">elif&lt;/span> &lt;span class="n">data_type&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;inventory&amp;#34;&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">normalized&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">check_stock_levels&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&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="n">dry_run&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">normalized&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">return&lt;/span> &lt;span class="n">save_to_db&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normalized&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">table&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">TABLE_MAP&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">data_type&lt;/span>&lt;span class="p">])&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>到了階段 3，這個函式已經違反了單一職責原則，而且任何修改都可能影響所有資料類型的處理。&lt;/p></description><content:encoded><![CDATA[<p>前五章介紹了泛型、異常設計、上下文管理、插件系統等進階設計模式。但在真實專案中，最困難的往往不是「如何實作」，而是「該不該這樣做」。本章從英文技術社群的經驗中提煉出實用的取捨決策框架，幫助你在面對兩難時做出更好的判斷。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>本模組 3.5.1-3.5.5 所有章節</li>
<li>入門系列 <a href="/blog/python/04-oop/" data-link-title="模組四：物件導向設計" data-link-desc="Python 的物件導向設計與設計模式">模組四：物件導向設計</a></li>
</ul>
<hr>
<h2 id="為什麼取捨不可避免">為什麼取捨不可避免？</h2>
<p>Stack Overflow 的技術部落格曾發表一篇文章 <a href="https://stackoverflow.blog/2022/01/17/plan-for-tradeoffs-you-cant-optimize-all-software-quality-attributes/">Plan for Tradeoffs</a>，核心論點是：<strong>你無法同時最佳化所有軟體品質屬性</strong>。</p>
<p>該文列出了 17 項核心品質屬性，包含可用性、效能、安全性、可維護性、可移植性等。它們之間存在天然的衝突：</p>
<ul>
<li><strong>安全性 vs 易用性</strong>：多因素驗證提高了安全性，但增加了使用步驟</li>
<li><strong>可重用性 vs 效率</strong>：泛用型元件的效能不如針對特定場景最佳化的程式碼</li>
<li><strong>效能 vs 可移植性</strong>：平台特定的最佳化降低了跨平台能力</li>
</ul>
<p>取捨是軟體開發的數學本質，跟工程師能力無關。每個設計決策都在一個多維空間中選擇一個點，無法讓每個維度都處於最佳值。</p>
<h3 id="python-的哲學立場">Python 的哲學立場</h3>
<p>Python 的設計者 Tim Peters 在 <a href="https://peps.python.org/pep-0020/">PEP 20 &ndash; The Zen of Python</a> 中早已預見了這個問題，其中幾條格言直接反映了取捨思維：</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">Simple is better than complex.
</span></span><span class="line"><span class="ln">2</span><span class="cl">Complex is better than complicated.
</span></span><span class="line"><span class="ln">3</span><span class="cl">Special cases aren&#39;t special enough to break the rules.
</span></span><span class="line"><span class="ln">4</span><span class="cl">Although practicality beats purity.</span></span></code></pre></div><p>「Although practicality beats purity」（務實勝於純粹）是整個 Python 設計哲學的關鍵。它承認完美方案不存在，鼓勵工程師在原則和現實之間找到平衡。</p>
<hr>
<h2 id="六個核心取捨維度">六個核心取捨維度</h2>
<p>從業界經驗中，我們可以歸納出六個最常見的取捨維度。</p>
<h3 id="維度一重複-vs-錯誤抽象">維度一：重複 vs 錯誤抽象</h3>
<p>這是軟體設計中最經典的取捨之一，近年來因為 <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">Sandi Metz 的 &ldquo;The Wrong Abstraction&rdquo;</a> 和 <a href="https://kentcdodds.com/blog/aha-programming">Kent C. Dodds 的 AHA Programming</a> 而被重新審視。</p>
<h4 id="傳統觀點--drydont-repeat-yourself">傳統觀點 &ndash; DRY（Don&rsquo;t Repeat Yourself）</h4>
<blockquote>
<p>&ldquo;Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.&rdquo;</p></blockquote>
<p>DRY 原則要求消除所有重複。這在很多情況下是正確的，但當它成為教條時，工程師會為了消除表面上的重複而建立過度複雜的抽象。</p>
<h4 id="反思觀點--ahaavoid-hasty-abstractions">反思觀點 &ndash; AHA（Avoid Hasty Abstractions）</h4>
<p>Kent C. Dodds 提出了 AHA 程式設計的概念：<strong>不要急於抽象，等到模式自然浮現</strong>。核心洞察是：</p>
<ul>
<li>重複的程式碼容易在之後重構</li>
<li>但錯誤的抽象拆除起來痛苦得多</li>
</ul>
<p>Sandi Metz 的名言精準地總結了這個觀點：</p>
<blockquote>
<p>&ldquo;Duplication is far cheaper than the wrong abstraction.&rdquo;</p>
<p>（重複的成本遠低於錯誤抽象的成本。）</p></blockquote>
<h4 id="錯誤抽象的演化過程">錯誤抽象的演化過程</h4>
<p>Metz 描述了一個在真實專案中反覆出現的模式：</p>
<ol>
<li>工程師發現兩段重複程式碼，提取出抽象</li>
<li>新需求出現，幾乎但不完全符合現有抽象</li>
<li>後繼者加入參數和條件分支來適應新需求</li>
<li>更多變體出現，更多條件被加入</li>
<li>抽象變得難以理解，但沒人敢重寫（沉沒成本謬誤）</li>
<li>所有人都害怕這段程式碼</li>
</ol>
<h4 id="python-實際案例">Python 實際案例</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：發現重複，提取函式</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">process_user_data</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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"> 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="n">validated</span> <span class="o">=</span> <span class="n">validate_fields</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">normalized</span> <span class="o">=</span> <span class="n">normalize_strings</span><span class="p">(</span><span class="n">validated</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">save_to_db</span><span class="p">(</span><span class="n">normalized</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"># 階段 2：「訂單資料也差不多嘛」，加入參數</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">process_data</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">data_type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;user&#34;</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">10</span><span class="cl">    <span class="n">validated</span> <span class="o">=</span> <span class="n">validate_fields</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">normalized</span> <span class="o">=</span> <span class="n">normalize_strings</span><span class="p">(</span><span class="n">validated</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">if</span> <span class="n">data_type</span> <span class="o">==</span> <span class="s2">&#34;order&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">calculate_totals</span><span class="p">(</span><span class="n">normalized</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">save_to_db</span><span class="p">(</span><span class="n">normalized</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"># 階段 3：更多類型，更多分支</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">def</span> <span class="nf">process_data</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">data_type</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">skip_validation</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">custom_normalizer</span><span class="p">:</span> <span class="n">Callable</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</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">24</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">skip_validation</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">validated</span> <span class="o">=</span> <span class="n">validate_fields</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">strict</span><span class="o">=</span><span class="p">(</span><span class="n">data_type</span> <span class="o">==</span> <span class="s2">&#34;payment&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">validated</span> <span class="o">=</span> <span class="n">data</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">if</span> <span class="n">custom_normalizer</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">custom_normalizer</span><span class="p">(</span><span class="n">validated</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">normalized</span> <span class="o">=</span> <span class="n">normalize_strings</span><span class="p">(</span><span class="n">validated</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="k">if</span> <span class="n">data_type</span> <span class="o">==</span> <span class="s2">&#34;order&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">calculate_totals</span><span class="p">(</span><span class="n">normalized</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">elif</span> <span class="n">data_type</span> <span class="o">==</span> <span class="s2">&#34;payment&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">encrypt_sensitive</span><span class="p">(</span><span class="n">normalized</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">elif</span> <span class="n">data_type</span> <span class="o">==</span> <span class="s2">&#34;inventory&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">check_stock_levels</span><span class="p">(</span><span class="n">normalized</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="n">dry_run</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">normalized</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="k">return</span> <span class="n">save_to_db</span><span class="p">(</span><span class="n">normalized</span><span class="p">,</span> <span class="n">table</span><span class="o">=</span><span class="n">TABLE_MAP</span><span class="p">[</span><span class="n">data_type</span><span class="p">])</span></span></span></code></pre></div><p>到了階段 3，這個函式已經違反了單一職責原則，而且任何修改都可能影響所有資料類型的處理。</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="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</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">DataProcessor</span><span class="p">(</span><span class="n">ABC</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></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="o">...</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="o">...</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">@abstractmethod</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span> <span class="o">...</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">process</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">16</span><span class="cl">        <span class="n">validated</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">normalize</span><span class="p">(</span><span class="n">validated</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="bp">self</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">normalized</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="k">class</span> <span class="nc">UserDataProcessor</span><span class="p">(</span><span class="n">DataProcessor</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">22</span><span class="cl">        <span class="k">return</span> <span class="n">validate_fields</span><span class="p">(</span><span class="n">data</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">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">25</span><span class="cl">        <span class="k">return</span> <span class="n">normalize_strings</span><span class="p">(</span><span class="n">data</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">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">28</span><span class="cl">        <span class="k">return</span> <span class="n">save_to_db</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">table</span><span class="o">=</span><span class="s2">&#34;users&#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="k">class</span> <span class="nc">PaymentDataProcessor</span><span class="p">(</span><span class="n">DataProcessor</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">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">32</span><span class="cl">        <span class="k">return</span> <span class="n">validate_fields</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">strict</span><span class="o">=</span><span class="kc">True</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="k">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">35</span><span class="cl">        <span class="n">normalized</span> <span class="o">=</span> <span class="n">normalize_strings</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="n">encrypt_sensitive</span><span class="p">(</span><span class="n">normalized</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">def</span> <span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</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">39</span><span class="cl">        <span class="k">return</span> <span class="n">save_to_db</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">table</span><span class="o">=</span><span class="s2">&#34;payments&#34;</span><span class="p">)</span></span></span></code></pre></div><p>注意：<code>UserDataProcessor</code> 和 <code>PaymentDataProcessor</code> 的 <code>validate</code> 方法有些重複（都呼叫 <code>validate_fields</code>），但這是<strong>可接受的重複</strong>。每個處理器獨立演化，不會因為支付系統的新需求而影響用戶資料的處理。</p>
<h4 id="決策指引">決策指引</h4>
<table>
  <thead>
      <tr>
          <th>情境</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>兩段程式碼目前看起來一樣</td>
          <td>先保持重複，觀察是否真的有共同的演化方向</td>
      </tr>
      <tr>
          <td>三處以上相同且穩定不變</td>
          <td>提取抽象，但保持介面簡單</td>
      </tr>
      <tr>
          <td>現有抽象開始出現 <code>if type == ...</code></td>
          <td>考慮拆回獨立實作</td>
      </tr>
      <tr>
          <td>修改一處總是需要同時修改抽象的其他使用者</td>
          <td>抽象方向錯了，回到重複再重新評估</td>
      </tr>
  </tbody>
</table>
<h3 id="維度二效能-vs-可讀性">維度二：效能 vs 可讀性</h3>
<p>Python 社群有一句話經常被引用：</p>
<blockquote>
<p>&ldquo;Premature optimization is the root of all evil.&rdquo; &ndash; Donald Knuth</p></blockquote>
<p>但完整的引用其實是：</p>
<blockquote>
<p>&ldquo;We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. <strong>Yet we should not pass up our opportunities in that critical 3%.</strong>&rdquo;</p></blockquote>
<p>這段話包含了兩個同樣重要的訊息：<strong>97% 的時候不要優化</strong>，但<strong>那關鍵的 3% 不能錯過</strong>。</p>
<h4 id="python-的定位">Python 的定位</h4>
<p>Python 的設計選擇（動態型別、簡潔語法、豐富的標準庫）大幅降低了開發時間。Dev.to 上的一篇分析指出，效能不僅用 CPU 週期衡量，也用 <a href="https://dev.to/grenishrai/why-developers-still-choose-python-even-if-its-slow-2hlc">time-to-solution</a> 衡量。程式碼可能在 0.5 秒內執行完畢，但如果需要三天來撰寫和除錯，你損失的生產力可能超過執行速度帶來的收益。</p>
<h4 id="python-中效能與可讀性的典型衝突">Python 中效能與可讀性的典型衝突</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="k">def</span> <span class="nf">find_active_premium_users</span><span class="p">(</span><span class="n">users</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</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="n">active_users</span> <span class="o">=</span> <span class="p">[</span><span class="n">u</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">users</span> <span class="k">if</span> <span class="n">u</span><span class="o">.</span><span class="n">is_active</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">premium_users</span> <span class="o">=</span> <span class="p">[</span><span class="n">u</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">active_users</span> <span class="k">if</span> <span class="n">u</span><span class="o">.</span><span class="n">plan</span> <span class="o">==</span> <span class="s2">&#34;premium&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">recent_users</span> <span class="o">=</span> <span class="p">[</span><span class="n">u</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">premium_users</span> <span class="k">if</span> <span class="n">u</span><span class="o">.</span><span class="n">last_login</span> <span class="o">&gt;</span> <span class="n">cutoff_date</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">recent_users</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="k">def</span> <span class="nf">find_active_premium_users</span><span class="p">(</span><span class="n">users</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</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="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">u</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">users</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">if</span> <span class="n">u</span><span class="o">.</span><span class="n">is_active</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="ow">and</span> <span class="n">u</span><span class="o">.</span><span class="n">plan</span> <span class="o">==</span> <span class="s2">&#34;premium&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="ow">and</span> <span class="n">u</span><span class="o">.</span><span class="n">last_login</span> <span class="o">&gt;</span> <span class="n">cutoff_date</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"># 極致效能版本：犧牲可讀性</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">def</span> <span class="nf">find_active_premium_users</span><span class="p">(</span><span class="n">users</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">_is</span> <span class="o">=</span> <span class="kc">True</span><span class="o">.</span><span class="fm">__eq__</span>  <span class="c1"># 避免屬性查找</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="n">_pr</span> <span class="o">=</span> <span class="s2">&#34;premium&#34;</span><span class="o">.</span><span class="fm">__eq__</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">_dt</span> <span class="o">=</span> <span class="n">cutoff_date</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="n">u</span> <span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">users</span> <span class="k">if</span> <span class="n">_is</span><span class="p">(</span><span class="n">u</span><span class="o">.</span><span class="n">is_active</span><span class="p">)</span> <span class="ow">and</span> <span class="n">_pr</span><span class="p">(</span><span class="n">u</span><span class="o">.</span><span class="n">plan</span><span class="p">)</span> <span class="ow">and</span> <span class="n">u</span><span class="o">.</span><span class="n">last_login</span> <span class="o">&gt;</span> <span class="n">_dt</span><span class="p">]</span></span></span></code></pre></div><p>在這個例子中，第一個版本建立了三個中間列表，但最清楚；第二個版本是合理的平衡；第三個版本的微優化在 99% 的場景中毫無意義，卻犧牲了所有可讀性。</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"># 決策流程（虛擬碼）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">should_optimize</span><span class="p">(</span><span class="n">code_section</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"> 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">if</span> <span class="ow">not</span> <span class="n">is_performance_bottleneck</span><span class="p">(</span><span class="n">code_section</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="s2">&#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"># 步驟 2：瓶頸在這裡嗎？</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">profiling_result</span> <span class="o">=</span> <span class="n">profile</span><span class="p">(</span><span class="n">code_section</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">if</span> <span class="n">profiling_result</span><span class="o">.</span><span class="n">time_percentage</span> <span class="o">&lt;</span> <span class="mi">5</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="s2">&#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="c1"># 步驟 3：有不犧牲可讀性的優化方案嗎？</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">if</span> <span class="n">can_use_better_algorithm</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="s2">&#34;換演算法（通常不影響可讀性）&#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="k">if</span> <span class="n">can_use_better_data_structure</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="s2">&#34;換資料結構（通常不影響可讀性）&#34;</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"># 步驟 4：真的需要犧牲可讀性</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="s2">&#34;優化，但加上詳細註解說明為什麼&#34;</span></span></span></code></pre></div><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"># 場景：頻繁的成員檢查</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"># list -- O(n) 查找，但保留順序、允許重複</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">users_list</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">=</span> <span class="p">[</span><span class="s2">&#34;alice&#34;</span><span class="p">,</span> <span class="s2">&#34;bob&#34;</span><span class="p">,</span> <span class="s2">&#34;charlie&#34;</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="s2">&#34;alice&#34;</span> <span class="ow">in</span> <span class="n">users_list</span><span class="p">:</span>  <span class="c1"># 線性掃描</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">pass</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"># set -- O(1) 查找，但不保留順序、不允許重複</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">users_set</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="p">{</span><span class="s2">&#34;alice&#34;</span><span class="p">,</span> <span class="s2">&#34;bob&#34;</span><span class="p">,</span> <span class="s2">&#34;charlie&#34;</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="s2">&#34;alice&#34;</span> <span class="ow">in</span> <span class="n">users_set</span><span class="p">:</span>  <span class="c1"># 雜湊查找</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">pass</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"># - 集合 &lt; 100 個元素：差異可忽略，選擇語義更清楚的</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># - 集合 &gt; 10000 個元素且頻繁查找：set 是明確的選擇</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># - 需要保留順序 + 快速查找：dict.fromkeys() 或 OrderedDict</span></span></span></code></pre></div><h3 id="維度三build-vs-buy">維度三：Build vs Buy</h3>
<p>這是影響範圍最大的取捨之一。Antoine Sauvinet 在 <a href="https://oinant.com/en/posts/2026-01-05-build-vs-buy-2026/">Build vs Buy in 2026</a> 中指出，67% 的軟體專案失敗源於錯誤的 build vs buy 決策（引用 Forrester 研究）。</p>
<h4 id="核心判斷原則這是你的競爭優勢嗎">核心判斷原則：這是你的競爭優勢嗎？</h4>
<blockquote>
<p>如果你正在建造的東西能在市場上區隔你和競爭者，那值得自建。否則，不要重新發明輪子。</p></blockquote>
<p>這個原則被稱為 NIH 症候群（Not Invented Here Syndrome）的解藥。一篇分析 <a href="https://news.ycombinator.com/item?id=34163624">Hacker News 上的 build vs buy 失敗經驗</a> 後發現，最常見的錯誤是：</p>
<ul>
<li>低估了維護自建方案的長期成本</li>
<li>高估了「我們的需求很特殊」的程度</li>
<li>忽略了開源社群數百位貢獻者的累積優勢</li>
</ul>
<h4 id="python-生態系統的案例">Python 生態系統的案例</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"># 反面案例：自建 HTTP 請求函式庫</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># 「我們只需要 GET 和 POST，寫一個很簡單」</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">socket</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">simple_get</span><span class="p">(</span><span class="n">url</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="c1"># 解析 URL、建立 socket、處理 SSL、</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="c1"># 三個月後，你重新實作了 requests 的 30%</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="c1"># 但缺少了 cookie 管理、連接池、代理支援...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">pass</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"># 正確做法：用 requests 或 httpx</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">import</span> <span class="nn">httpx</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">response</span> <span class="o">=</span> <span class="n">httpx</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;https://api.example.com/data&#34;</span><span class="p">)</span></span></span></code></pre></div>




<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="c1"># 這是你的競爭優勢，沒有通用方案能完全符合</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">PricingEngine</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></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">calculate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">product</span><span class="p">:</span> <span class="n">Product</span><span class="p">,</span> <span class="n">customer</span><span class="p">:</span> <span class="n">Customer</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Price</span><span class="p">:</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="bp">self</span><span class="o">.</span><span class="n">_base_price</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">adjusted</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_apply_customer_tier</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">customer</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">seasonal</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_seasonal_adjustment</span><span class="p">(</span><span class="n">adjusted</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="bp">self</span><span class="o">.</span><span class="n">_apply_regulatory_constraints</span><span class="p">(</span><span class="n">seasonal</span><span class="p">)</span></span></span></code></pre></div><h4 id="決策矩陣">決策矩陣</h4>
<table>
  <thead>
      <tr>
          <th>因素</th>
          <th>傾向自建</th>
          <th>傾向採用</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>市面方案無法滿足 &gt; 70% 需求</td>
          <td>市面方案滿足 &gt; 80% 需求</td>
      </tr>
      <tr>
          <td>時間壓力</td>
          <td>可以投入 3+ 個月</td>
          <td>需要在數週內交付</td>
      </tr>
  </tbody>
</table>
<h3 id="維度四快速失敗-vs-預先驗證">維度四：快速失敗 vs 預先驗證</h3>
<h4 id="fail-fast-原則">Fail Fast 原則</h4>
<p>DZone 上的文章 <a href="https://dzone.com/articles/fail-fast-principle-in-software-development">The Fail-Fast Principle in Software Development</a> 指出：fail-fast 系統在遇到非預期狀態時立即停止，而不是嘗試繼續執行可能產生不正確結果的操作。</p>
<p>Enterprise Craftsmanship 的部落格進一步說明：因為 fail-fast 程式碼在第一時間就失敗了，回報的錯誤或例外通常非常接近實際的根因，大幅減少了除錯時間。</p>
<h4 id="python-中的-fail-fast">Python 中的 Fail Fast</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"># Fail Fast 風格：立即驗證，立即失敗</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">def</span> <span class="nf">transfer_money</span><span class="p">(</span><span class="n">from_account</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">to_account</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">amount</span><span class="p">:</span> <span class="n">Decimal</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">3</span><span class="cl">    <span class="k">if</span> <span class="n">amount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;轉帳金額必須為正數，收到: </span><span class="si">{</span><span class="n">amount</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 class="k">if</span> <span class="n">from_account</span> <span class="o">==</span> <span class="n">to_account</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">        <span class="k">raise</span> <span class="ne">ValueError</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 class="c1"># ... 執行轉帳</span></span></span></code></pre></div>




<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">transfer_money</span><span class="p">(</span><span class="n">from_account</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">to_account</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">amount</span><span class="p">:</span> <span class="n">Decimal</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"> 3</span><span class="cl">    <span class="n">errors</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">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">if</span> <span class="n">amount</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">errors</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">amount</span><span class="si">}</span><span class="s2">&#34;</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">from_account</span> <span class="o">==</span> <span class="n">to_account</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</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;不能轉帳給自己&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">account_exists</span><span class="p">(</span><span class="n">from_account</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">errors</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">from_account</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="k">if</span> <span class="ow">not</span> <span class="n">account_exists</span><span class="p">(</span><span class="n">to_account</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">errors</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">to_account</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></span><span class="line"><span class="ln">13</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">14</span><span class="cl">        <span class="k">raise</span> <span class="n">ValidationError</span><span class="p">(</span><span class="n">errors</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><h4 id="何時用哪種">何時用哪種？</h4>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>建議策略</th>
          <th>理由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式內部邏輯</td>
          <td>Fail Fast</td>
          <td>不應該出現的狀態要立即暴露</td>
      </tr>
      <tr>
          <td>API 請求驗證</td>
          <td>預先驗證</td>
          <td>使用者需要一次看到所有錯誤</td>
      </tr>
      <tr>
          <td>資料管線</td>
          <td>Fail Fast + 重試</td>
          <td>單筆失敗不應阻塞整個管線</td>
      </tr>
      <tr>
          <td>表單提交</td>
          <td>預先驗證</td>
          <td>UX 考量：使用者不想重複提交</td>
      </tr>
      <tr>
          <td>系統啟動</td>
          <td>Fail Fast</td>
          <td>配置錯誤要在啟動時就發現</td>
      </tr>
  </tbody>
</table>
<h4 id="python-的-assert-是-fail-fast-的工具但有限制">Python 的 assert 是 Fail Fast 的工具（但有限制）</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">process_batch</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="n">Item</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">Result</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># assert 適合標記「這裡不應該發生」的情況</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">assert</span> <span class="n">items</span><span class="p">,</span> <span class="s2">&#34;process_batch 不應該收到空列表&#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="c1"># 但注意：python -O 會移除所有 assert</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="c1"># 所以不要用 assert 做業務驗證</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">assert</span> <span class="n">user</span><span class="o">.</span><span class="n">is_authenticated</span><span class="p">,</span> <span class="s2">&#34;使用者未登入&#34;</span>  <span class="c1"># 不要這樣做</span></span></span></code></pre></div><h3 id="維度五嚴格型別-vs-靈活鴨子型別">維度五：嚴格型別 vs 靈活鴨子型別</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="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</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"># 方案 A：嚴格的型別定義</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">Serializable</span><span class="p">(</span><span class="n">Protocol</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">to_dict</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 class="o">...</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">from_dict</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="s2">&#34;Serializable&#34;</span><span class="p">:</span> <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="k">def</span> <span class="nf">save_strict</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span> <span class="n">Serializable</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="n">data</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span>
</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"># 方案 B：鴨子型別，依賴約定</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">save_flexible</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span> <span class="nb">object</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">14</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s2">&#34;to_dict&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</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="si">}</span><span class="s2"> 缺少 to_dict 方法&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span>  <span class="c1"># type: ignore</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="c1"># 方案 C：Protocol 的平衡點（推薦）</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># Protocol 提供靜態檢查，但不要求繼承</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">Persistable</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">def</span> <span class="nf">to_dict</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 class="o">...</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">save_balanced</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span> <span class="n">Persistable</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">25</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</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></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># 任何有 to_dict 方法的類別都自動滿足 Persistable</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">class</span> <span class="nc">User</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">to_dict</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">32</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</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">save_balanced</span><span class="p">(</span><span class="n">User</span><span class="p">())</span>  <span class="c1"># mypy 檢查通過，無需繼承</span></span></span></code></pre></div><h4 id="決策指引-1">決策指引</h4>
<table>
  <thead>
      <tr>
          <th>專案特性</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>團隊 &gt; 5 人</td>
          <td>傾向嚴格型別，降低溝通成本</td>
      </tr>
      <tr>
          <td>快速原型</td>
          <td>鴨子型別，快速迭代</td>
      </tr>
      <tr>
          <td>長期維護的框架</td>
          <td>Protocol（平衡點）</td>
      </tr>
      <tr>
          <td>資料管線/ETL</td>
          <td>嚴格型別，錯誤代價高</td>
      </tr>
      <tr>
          <td>個人腳本</td>
          <td>鴨子型別，效率優先</td>
      </tr>
  </tbody>
</table>
<h3 id="維度六技術債務的策略性管理">維度六：技術債務的策略性管理</h3>
<p>Oskar Dudycz 在 Architecture Weekly 上發表了一篇引人深思的文章 <a href="https://www.architecture-weekly.com/p/tech-debt-doesnt-exist-but-trade">Tech Debt Doesn&rsquo;t Exist, But Trade-offs Do</a>。他認為「技術債務」這個標籤讓我們可以承認問題的存在而不去解決它。他主張用「取捨」取代「債務」的思維：</p>
<blockquote>
<p>說「我們有技術債」是一種藉口。真正的問題是：「我們當時做了什麼取捨，現在的代價是什麼？」</p></blockquote>
<h4 id="策略性技術債務-vs-魯莽的技術債務">策略性技術債務 vs 魯莽的技術債務</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="c1"># 場景：MVP 需要在兩週內上線驗證市場</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"># 目前的實作：直接用 JSON 檔案儲存</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 取捨決策：放棄資料庫，節省 3 天開發時間</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 償還計畫：驗證成功後第二個 sprint 遷移到 PostgreSQL</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln"> 8</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"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># TRADE-OFF: 使用 JSON 檔案而非資料庫</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 原因: MVP 階段，使用者 &lt; 100 人，讀寫頻率低</span>
</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="c1"># 償還條件: 使用者 &gt; 50 或資料 &gt; 10MB 時遷移</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">DATA_FILE</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&#34;data/users.json&#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">def</span> <span class="nf">save_user</span><span class="p">(</span><span class="n">user</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">17</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">DATA_FILE</span><span class="o">.</span><span class="n">read_text</span><span class="p">())</span> <span class="k">if</span> <span class="n">DATA_FILE</span><span class="o">.</span><span class="n">exists</span><span class="p">()</span> <span class="k">else</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">data</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">DATA_FILE</span><span class="o">.</span><span class="n">write_text</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">))</span></span></span></code></pre></div>




<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="c1"># 場景：「先讓它動起來再說」</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">handle_request</span><span class="p">(</span><span class="n">req</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1"># 100 行沒有型別提示、沒有錯誤處理、沒有測試的程式碼</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">req</span><span class="p">[</span><span class="s2">&#34;data&#34;</span><span class="p">]</span>  <span class="c1"># 可能是 None</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="n">process</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>  <span class="c1"># process 可能拋出任何異常</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">db</span><span class="o">.</span><span class="n">save</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"> 9</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">except</span><span class="p">:</span>  <span class="c1"># 裸 except：吞掉所有錯誤</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;ok&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span></span></span></code></pre></div><p><strong>關鍵區別</strong>：策略性債務是有意識的取捨，附帶償還計畫和觸發條件。魯莽的債務是無意識的品質下降。</p>
<hr>
<h2 id="決策框架面對取捨的系統性方法">決策框架：面對取捨的系統性方法</h2>
<p>綜合上述六個維度的經驗，以下是一個通用的取捨決策框架。</p>
<h3 id="三步決策法">三步決策法</h3>
<h4 id="步驟一辨識取捨的存在">步驟一：辨識取捨的存在</h4>
<p>很多時候，工程師沒有意識到自己正在做取捨。以下信號表明你正面對一個取捨決策：</p>
<ul>
<li>「兩種做法各有優缺點」</li>
<li>「如果我們選 A，就會失去 B」</li>
<li>團隊成員對同一個問題有不同偏好</li>
<li>解決方案中出現了 &ldquo;it depends&rdquo;</li>
</ul>
<h4 id="步驟二量化代價">步驟二：量化代價</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">class</span> <span class="nc">TradeoffEvaluation</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></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">decision</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="bp">self</span><span class="o">.</span><span class="n">decision</span> <span class="o">=</span> <span class="n">decision</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">options</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">=</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="k">def</span> <span class="nf">add_option</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="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">benefits</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">13</span><span class="cl">        <span class="n">costs</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">14</span><span class="cl">        <span class="n">risks</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">15</span><span class="cl">        <span class="n">reversibility</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>  <span class="c1"># &#34;easy&#34; | &#34;moderate&#34; | &#34;difficult&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <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">17</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">            <span class="s2">&#34;benefits&#34;</span><span class="p">:</span> <span class="n">benefits</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">            <span class="s2">&#34;costs&#34;</span><span class="p">:</span> <span class="n">costs</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">            <span class="s2">&#34;risks&#34;</span><span class="p">:</span> <span class="n">risks</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="s2">&#34;reversibility&#34;</span><span class="p">:</span> <span class="n">reversibility</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">evaluate</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">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">lines</span> <span class="o">=</span> <span class="p">[</span><span class="sa">f</span><span class="s2">&#34;## 決策: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">decision</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">28</span><span class="cl">        <span class="k">for</span> <span class="n">opt</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</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="s2">&#34;### 方案: </span><span class="si">{</span><span class="n">opt</span><span class="p">[</span><span class="s1">&#39;name&#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">30</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="s2">&#34;- 可逆性: </span><span class="si">{</span><span class="n">opt</span><span class="p">[</span><span class="s1">&#39;reversibility&#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">31</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="s2">&#34;- 優點: </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">opt</span><span class="p">[</span><span class="s1">&#39;benefits&#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">32</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="s2">&#34;- 代價: </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">opt</span><span class="p">[</span><span class="s1">&#39;costs&#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">33</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="s2">&#34;- 風險: </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">opt</span><span class="p">[</span><span class="s1">&#39;risks&#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">34</span><span class="cl">            <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;&#34;</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="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</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></code></pre></div><h4 id="步驟三偏好可逆的決策">步驟三：偏好可逆的決策</h4>
<p>Amazon 的 Jeff Bezos 將決策分為兩類：</p>
<ul>
<li><strong>Type 1 決策</strong>（單向門）：不可逆，需要深思熟慮。例如選擇程式語言、資料庫架構。</li>
<li><strong>Type 2 決策</strong>（雙向門）：可逆，應該快速做出。例如 API 命名、函式拆分方式。</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="c1"># Type 2 決策：函式簽名可以之後改</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="k">def</span> <span class="nf">send_notification</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">message</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="s2">&#34;&#34;&#34;現在只支援 email，之後可以擴展&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">return</span> <span class="n">send_email</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">message</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">send_notification</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</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 class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">channel</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;email&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><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;支援多種通知管道&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">sender</span> <span class="o">=</span> <span class="n">CHANNEL_MAP</span><span class="p">[</span><span class="n">channel</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="n">sender</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span></span></span></code></pre></div>




<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"># Type 1 決策：資料庫 schema 設計</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></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 決策記錄（Architecture Decision Record）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># ADR-007: 使用者地址儲存方式</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"># 狀態: 已採納</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 日期: 2026-03-01</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 class="c1"># 背景:</span>
</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="c1">#</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 class="c1">#   A. JSON 欄位：靈活但難以查詢和索引</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">#   B. 獨立 address 表：標準化但查詢需要 JOIN</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1">#   C. 嵌入式欄位（home_addr, work_addr）：簡單但不可擴展</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="c1"># 決策: 方案 B（獨立表）</span>
</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><h3 id="可逆性評估表">可逆性評估表</h3>
<table>
  <thead>
      <tr>
          <th>決策類型</th>
          <th>可逆性</th>
          <th>建議決策速度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>變數/函式命名</td>
          <td>高（全域替換）</td>
          <td>秒級</td>
      </tr>
      <tr>
          <td>模組拆分方式</td>
          <td>中（需要重構）</td>
          <td>分鐘級</td>
      </tr>
      <tr>
          <td>API 介面設計</td>
          <td>低（外部依賴）</td>
          <td>小時級</td>
      </tr>
      <tr>
          <td>資料庫 schema</td>
          <td>很低（資料遷移）</td>
          <td>天級</td>
      </tr>
      <tr>
          <td>程式語言選擇</td>
          <td>極低（全部重寫）</td>
          <td>週級</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="經典案例研究">經典案例研究</h2>
<h3 id="案例一food-tech-新創的技術債務危機">案例一：Food-Tech 新創的技術債務危機</h3>
<p>一家 Food-Tech 新創公司在六個月內推出了 MVP，快速獲得了數千名用戶和可觀的投資。但開發團隊為了搶市場，犧牲了文件、測試和可擴展的架構。</p>
<p>一次特別糟糕的產品發布導致了嚴重的停機和大量客戶投訴。CTO 終於意識到，短期搶快的收益已經被長期的不穩定和效率低下所抵消。</p>
<p><strong>教訓</strong>：這是一個策略性債務失控的例子。初始的取捨（速度優先）是合理的，但缺少了「償還計畫」和「觸發條件」。如果團隊在 MVP 驗證成功後立即投入技術債務償還，結果會完全不同。</p>
<p>來源：<a href="https://medium.com/@helal.hamed/technical-debt-vs-innovation-how-to-manage-trade-offs-in-startups-and-scale-ups-d00abd8add4a">Medium - Technical Debt vs. Innovation</a></p>
<h3 id="案例二可觀測性的成本爆炸">案例二：可觀測性的成本爆炸</h3>
<p><a href="https://www.honeycomb.io/blog/cost-crisis-observability-tooling">Honeycomb 的工程部落格</a>描述了可觀測性工具面臨的成本危機：微服務架構產生的日誌、指標和追蹤資料量呈指數成長，但大部分資料從未被查看。</p>
<p>典型的取捨是取樣率：10% 的取樣可以大幅降低成本，但可能錯過關鍵的請求。</p>
<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="c1"># 概念示意：基於重要性的取樣策略</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></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">SAMPLING_RULES</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s2">&#34;health_check&#34;</span><span class="p">:</span> <span class="mf">0.001</span><span class="p">,</span>    <span class="c1"># 0.1% -- 幾乎不需要</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;static_asset&#34;</span><span class="p">:</span> <span class="mf">0.01</span><span class="p">,</span>     <span class="c1"># 1% -- 很少出問題</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;api_read&#34;</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>          <span class="c1"># 10% -- 標準取樣</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;api_write&#34;</span><span class="p">:</span> <span class="mf">0.5</span><span class="p">,</span>         <span class="c1"># 50% -- 寫入操作更重要</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;payment&#34;</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>           <span class="c1"># 100% -- 永遠完整記錄</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="s2">&#34;error&#34;</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span>             <span class="c1"># 100% -- 錯誤永遠記錄</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="k">def</span> <span class="nf">should_sample</span><span class="p">(</span><span class="n">request_type</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">15</span><span class="cl">    <span class="n">rate</span> <span class="o">=</span> <span class="n">SAMPLING_RULES</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">request_type</span><span class="p">,</span> <span class="mf">0.1</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">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">rate</span></span></span></code></pre></div><p>來源：<a href="https://www.honeycomb.io/blog/cost-crisis-observability-tooling">Honeycomb - The Cost Crisis in Observability Tooling</a></p>
<h3 id="案例三b2b-企業的-4-億美元技術債">案例三：B2B 企業的 4 億美元技術債</h3>
<p>McKinsey 報導了一家大型 B2B 企業的案例：他們識別出了數十個現代化計畫，可以帶來 20 億美元的利潤提升，但其中 70% 的計畫依賴的技術需要 4 億美元的投入來償還多年累積的技術債務。</p>
<p><strong>教訓</strong>：技術債務的成本不是線性的。每一次「之後再處理」都會增加下一次修改的難度。當債務累積到一定程度，甚至連修改的機會成本都變成天文數字。</p>
<p>來源：<a href="https://mckinsey.com/capabilities/mckinsey-digital/our-insights/breaking-technical-debts-vicious-cycle-to-modernize-your-business">McKinsey - Breaking Technical Debt&rsquo;s Vicious Cycle</a></p>
<hr>
<h2 id="python-特有的取捨考量">Python 特有的取捨考量</h2>
<h3 id="there-should-be-one-obvious-way-to-do-it-vs-現實">&ldquo;There should be one obvious way to do it&rdquo; vs 現實</h3>
<p>The Zen of Python 說：</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">There should be one-- and preferably only one --obvious way to do it.</span></span></code></pre></div><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="c1"># 格式化字串：三種方式</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;World&#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"># % 格式化（最老，但 logging 模組仍在用）</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">message</span> <span class="o">=</span> <span class="s2">&#34;Hello, </span><span class="si">%s</span><span class="s2">&#34;</span> <span class="o">%</span> <span class="n">name</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"># str.format()（Python 2.6+，某些場景更靈活）</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">message</span> <span class="o">=</span> <span class="s2">&#34;Hello, </span><span class="si">{}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">format</span><span class="p">(</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="c1"># f-string（Python 3.6+，通常是最佳選擇）</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span></span></span></code></pre></div>




<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">dict_a</span> <span class="o">=</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></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">dict_b</span> <span class="o">=</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"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 方式 1：update（原地修改）</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">merged</span> <span class="o">=</span> <span class="n">dict_a</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">merged</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">dict_b</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：解包（Python 3.5+，建立新字典）</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">merged</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">dict_a</span><span class="p">,</span> <span class="o">**</span><span class="n">dict_b</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：聯集運算子（Python 3.9+，最 Pythonic）</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">merged</span> <span class="o">=</span> <span class="n">dict_a</span> <span class="o">|</span> <span class="n">dict_b</span></span></span></code></pre></div><p><strong>如何選擇？</strong> 優先考慮：</p>
<ol>
<li><strong>團隊共識</strong> &ndash; 統一比「最佳」更重要</li>
<li><strong>Python 版本相容性</strong> &ndash; 如果要支援 3.8，就不能用 <code>|</code></li>
<li><strong>語境適合度</strong> &ndash; logging 中用 <code>%</code> 是慣例，不需要改成 f-string</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="c1"># 階段 1：不加型別提示（快速原型）</span>
</span></span><span class="line"><span class="ln"> 2</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></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">item</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="n">item</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;active&#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="c1"># 階段 2：關鍵介面加型別提示（公開 API）</span>
</span></span><span class="line"><span class="ln"> 6</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="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</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"> 7</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">item</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">]</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="n">item</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;active&#34;</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"># 階段 3：完整型別定義（長期維護）</span>
</span></span><span class="line"><span class="ln">10</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">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">Item</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">active</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">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">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="n">Item</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">18</span><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">item</span><span class="o">.</span><span class="n">name</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="n">item</span><span class="o">.</span><span class="n">active</span><span class="p">]</span></span></span></code></pre></div><p>每個階段都是合理的，關鍵在於根據專案的生命週期選擇正確的階段。個人腳本停在階段 1 完全合理；團隊共同維護的服務應該至少在階段 2；核心業務邏輯應該達到階段 3。</p>
<h3 id="eafp-vs-lbyl">EAFP vs LBYL</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="c1"># LBYL (Look Before You Leap) -- 先檢查再行動</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">get_value_lbyl</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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="o">|</span> <span class="kc">None</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">key</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">key</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="nb">isinstance</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"> 6</span><span class="cl">            <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">return</span> <span class="kc">None</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"># EAFP (Easier to Ask Forgiveness than Permission) -- 先嘗試再處理異常</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">get_value_eafp</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">dict</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="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</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">14</span><span class="cl">            <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">return</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</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="kc">None</span></span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>因素</th>
          <th>LBYL</th>
          <th>EAFP</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Race condition</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>Python 慣例</td>
          <td>較少使用</td>
          <td>更 Pythonic</td>
      </tr>
  </tbody>
</table>
<p><strong>實用建議</strong>：如果異常是真正的「例外情況」（發生率 &lt; 5%），EAFP 通常更好。如果「異常」其實是常見情況（例如字典中 30% 的 key 不存在），LBYL 的效能更好。</p>
<hr>
<h2 id="取捨決策清單">取捨決策清單</h2>
<p>面對需要做取捨的設計決策時，依序確認以下項目：</p>
<h3 id="決策前">決策前</h3>
<ul>
<li><input disabled="" type="checkbox"> 我是否辨識出了取捨的存在？（不是只有一個「正確答案」）</li>
<li><input disabled="" type="checkbox"> 我列出了至少兩個可行方案嗎？</li>
<li><input disabled="" type="checkbox"> 每個方案的優點、代價和風險都有記錄嗎？</li>
<li><input disabled="" type="checkbox"> 這個決策的可逆性如何？（Type 1 還是 Type 2？）</li>
<li><input disabled="" type="checkbox"> 有沒有可以參考的業界經驗或先例？</li>
</ul>
<h3 id="評估中">評估中</h3>
<ul>
<li><input disabled="" type="checkbox"> 我是否量化了代價，而不只是用直覺判斷？</li>
<li><input disabled="" type="checkbox"> 我有沒有考慮到<strong>長期</strong>維護成本？</li>
<li><input disabled="" type="checkbox"> 團隊其他成員的意見是什麼？</li>
<li><input disabled="" type="checkbox"> 最壞的情況是什麼？我們能承受嗎？</li>
<li><input disabled="" type="checkbox"> 有沒有第三個方案是我忽略的？</li>
</ul>
<h3 id="決策後">決策後</h3>
<ul>
<li><input disabled="" type="checkbox"> 決策理由有記錄嗎？（ADR 或程式碼註解）</li>
<li><input disabled="" type="checkbox"> 取捨的代價有告知相關人員嗎？</li>
<li><input disabled="" type="checkbox"> 如果是策略性技術債務，償還計畫和觸發條件是什麼？</li>
<li><input disabled="" type="checkbox"> 什麼條件下需要重新評估這個決策？</li>
</ul>
<hr>
<h2 id="本章重點整理">本章重點整理</h2>
<h3 id="核心觀念">核心觀念</h3>
<ol>
<li>取捨是軟體工程的本質，不是能力不足的表現</li>
<li>「務實勝於純粹」&ndash; Python 的設計哲學本身就是取捨的產物</li>
<li>錯誤的抽象比重複更昂貴（Sandi Metz）</li>
<li>不要急於抽象，等模式自然浮現（AHA Programming）</li>
<li>優先做可逆的決策，謹慎對待不可逆的決策</li>
</ol>
<h3 id="實用原則">實用原則</h3>
<ol>
<li>效能優化前先量測，97% 的時候可讀性優先</li>
<li>只有核心競爭力才值得自建</li>
<li>策略性技術債務需要「償還計畫」和「觸發條件」</li>
<li>型別提示的嚴格程度應匹配專案的生命週期</li>
<li>決策理由比決策本身更值得記錄</li>
</ol>
<hr>
<h2 id="延伸閱讀">延伸閱讀</h2>
<h3 id="必讀文章">必讀文章</h3>
<ul>
<li><a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">The Wrong Abstraction &ndash; Sandi Metz</a> &ndash; 為什麼錯誤的抽象比重複更昂貴</li>
<li><a href="https://kentcdodds.com/blog/aha-programming">AHA Programming &ndash; Kent C. Dodds</a> &ndash; 不要急於抽象的實踐指南</li>
<li><a href="https://stackoverflow.blog/2022/01/17/plan-for-tradeoffs-you-cant-optimize-all-software-quality-attributes/">Plan for Tradeoffs &ndash; Stack Overflow Blog</a> &ndash; 軟體品質屬性的取捨框架</li>
<li><a href="https://www.architecture-weekly.com/p/tech-debt-doesnt-exist-but-trade">Tech Debt Doesn&rsquo;t Exist, But Trade-offs Do &ndash; Oskar Dudycz</a> &ndash; 重新理解技術債務</li>
</ul>
<h3 id="深入探討">深入探討</h3>
<ul>
<li><a href="https://oinant.com/en/posts/2026-01-05-build-vs-buy-2026/">Build vs Buy in 2026 &ndash; Antoine Sauvinet</a> &ndash; AI 時代的 build vs buy 決策</li>
<li><a href="https://www.honeycomb.io/blog/cost-crisis-observability-tooling">The Cost Crisis in Observability Tooling &ndash; Honeycomb</a> &ndash; 可觀測性的成本取捨</li>
<li><a href="https://dzone.com/articles/fail-fast-principle-in-software-development">The Fail-Fast Principle &ndash; DZone</a> &ndash; 快速失敗原則的全面介紹</li>
<li><a href="https://enterprisecraftsmanship.com/posts/fail-fast-principle/">Fail Fast Principle &ndash; Enterprise Craftsmanship</a> &ndash; 快速失敗的實務應用</li>
</ul>
<h3 id="python-相關">Python 相關</h3>
<ul>
<li><a href="https://peps.python.org/pep-0020/">PEP 20 &ndash; The Zen of Python</a> &ndash; Python 設計哲學</li>
<li><a href="https://peps.python.org/pep-0008/">PEP 8 &ndash; Style Guide for Python Code</a> &ndash; Python 風格指南</li>
<li><a href="https://dev.to/grenishrai/why-developers-still-choose-python-even-if-its-slow-2hlc">Why Developers Still Choose Python, Even If It&rsquo;s &ldquo;Slow&rdquo;</a> &ndash; Python 速度取捨的分析</li>
<li><a href="https://docs.python-guide.org/writing/style/">The Hitchhiker&rsquo;s Guide to Python: Code Style</a> &ndash; Pythonic 風格指南</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/03-design-patterns/integration/" data-link-title="3.5.5 設計模式整合案例" data-link-desc="結合泛型、異常、上下文、插件建立完整系統">3.5.5 設計模式整合案例</a></em>
<em>回到模組首頁：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組 3.5：進階設計模式</a></em></p>
]]></content:encoded></item><item><title>案例研究：設計模式實戰</title><link>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/</guid><description>&lt;p>本系列案例基於 &lt;code>.claude/lib&lt;/code> 的實際程式碼，展示如何用進階設計模式解決實際問題。&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;th>難度&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理&lt;/a>&lt;/td>
 &lt;td>config_loader.py&lt;/td>
 &lt;td>Context Manager&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Protocol + 註冊機制&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>異常階層 + ExceptionGroup&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Generic + TypeVar&lt;/td>
 &lt;td>高階&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">異常設計架構（基礎）
&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">泛型驗證器（整合）&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/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1 泛型進階&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2 異常設計架構&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組三：進階設計模式&lt;/a>&lt;/p></description><content:encoded><![CDATA[<p>本系列案例基於 <code>.claude/lib</code> 的實際程式碼，展示如何用進階設計模式解決實際問題。</p>
<h2 id="案例列表">案例列表</h2>
<table>
  <thead>
      <tr>
          <th>案例</th>
          <th>素材</th>
          <th>進階技術</th>
          <th>難度</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/cache-lifecycle/" data-link-title="案例：快取生命週期管理" data-link-desc="用 Context Manager 控制快取的生命週期，解決全域狀態問題">快取生命週期管理</a></td>
          <td>config_loader.py</td>
          <td>Context Manager</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/plugin-architecture/" data-link-title="案例：插件架構設計" data-link-desc="用 Protocol 和註冊機制實現可擴展的插件系統">插件架構設計</a></td>
          <td>hook_validator.py</td>
          <td>Protocol + 註冊機制</td>
          <td>高階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/exception-hierarchy/" data-link-title="案例：異常設計架構" data-link-desc="設計清晰的異常階層，並用 ExceptionGroup 處理多重錯誤">異常設計架構</a></td>
          <td>hook_io.py</td>
          <td>異常階層 + ExceptionGroup</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a></td>
          <td>hook_validator.py</td>
          <td>Generic + TypeVar</td>
          <td>高階</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">異常設計架構（基礎）
</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">泛型驗證器（整合）</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/generics/" data-link-title="3.5.1 泛型進階" data-link-desc="TypeVar 進階用法、Generic 類別、Protocol 與結構化子型別">3.1 泛型進階</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/exception-design/" data-link-title="3.5.2 異常設計架構" data-link-desc="異常層級設計、異常鏈、ExceptionGroup、異常 vs 返回值">3.2 異常設計架構</a></li>
<li><a href="/blog/python-advanced/03-design-patterns/context-managers/" data-link-title="3.5.3 進階上下文管理" data-link-desc="上下文管理器協議、contextlib 工具、嵌套與組合、async with">3.3 進階上下文管理</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/03-design-patterns/" data-link-title="模組三：進階設計模式" data-link-desc="將元編程知識應用於實際架構設計，建立型別安全、可擴展的系統">模組三：進階設計模式</a></p>
]]></content:encoded></item></channel></rss>