<?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>Metaprogramming on Tarragon</title><link>https://tarrragon.github.io/blog/tags/metaprogramming/</link><description>Recent content in Metaprogramming on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 21 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/metaprogramming/index.xml" rel="self" type="application/rss+xml"/><item><title>案例：宣告式驗證</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Descriptor Protocol 實現宣告式驗證。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南&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="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">VALID_NAME_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/declarative-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &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">HOOK_IO_PATTERNS&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;from\s+lib\.hook_io\s+import&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &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">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">15&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">16&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">17&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">18&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">19&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">20&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">21&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">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">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">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">filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">hook_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&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">valid_name&lt;/span> &lt;span class="o">=&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">27&lt;/span>&lt;span class="cl"> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">filename&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">for&lt;/span> &lt;span class="n">pattern&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">VALID_NAME_PATTERNS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &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">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">valid_name&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">return&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">32&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;warning&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;檔案名稱不符合規範: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">filename&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">34&lt;/span>&lt;span class="cl"> &lt;span class="n">suggestion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;建議使用 snake-case 或 kebab-case 命名&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="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">return&lt;/span> &lt;span class="p">[]&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;code>check_*&lt;/code> 方法負責一項檢查&lt;/li>
&lt;li>&lt;strong>彈性高&lt;/strong>：容易新增或修改檢查邏輯&lt;/li>
&lt;li>&lt;strong>調試方便&lt;/strong>：可以單獨執行任一檢查方法&lt;/li>
&lt;/ol>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要&lt;strong>設定類別&lt;/strong>時（例如 Hook 配置），命令式驗證會有問題：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">HookConfig&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">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="n">event&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">command&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="c1"># 驗證邏輯散落在 __init__ 中&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="ow">not&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&amp;#34;&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"> 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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;無效的 Hook 名稱: &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 class="k">if&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&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;無效的事件類型: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">event&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">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">command&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">raise&lt;/span> &lt;span class="ne">ValueError&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">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="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">13&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&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">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：&lt;/p>
&lt;ul>
&lt;li>驗證邏輯在 &lt;code>__init__&lt;/code> 中，不容易重用&lt;/li>
&lt;li>修改屬性時不會重新驗證&lt;/li>
&lt;li>無法在類別定義中看到驗證規則&lt;/li>
&lt;/ul>
&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;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1建立基礎-descriptor">步驟 1：建立基礎 Descriptor&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">re&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">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Callable&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"> 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">ValidatedField&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;
&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"> 驗證欄位 Descriptor
&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>&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">__init__&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">validator&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">],&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">15&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&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">16&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2"> 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"> validator: 驗證函式，接受值，返回 bool
&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"> error_msg: 驗證失敗時的錯誤訊息
&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&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">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="c1"># __set_name__ 會設定這些&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">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">26&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_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">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">__set_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="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">29&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">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> Python 3.6+ 自動呼叫，取得屬性名稱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&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"> owner: 擁有此 Descriptor 的類別
&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"> name: 屬性名稱
&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;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">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">37&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&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">&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>&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="fm">__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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&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 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">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"> 讀取屬性時呼叫
&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"> obj: 實例（如果透過實例存取）
&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"> objtype: 類別
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">47&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl">&lt;span class="s2"> 屬性值，或透過類別存取時返回 Descriptor 本身
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&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">51&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span> &lt;span class="c1"># 透過類別存取，返回 Descriptor&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="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&lt;/span>&lt;span class="p">,&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>&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="fm">__set__&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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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">55&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">56&lt;/span>&lt;span class="cl">&lt;span class="s2"> 設定屬性時呼叫，執行驗證
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&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">59&lt;/span>&lt;span class="cl">&lt;span class="s2"> obj: 實例
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">60&lt;/span>&lt;span class="cl">&lt;span class="s2"> value: 要設定的值
&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">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">62&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">63&lt;/span>&lt;span class="cl">&lt;span class="s2"> ValueError: 驗證失敗時
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">64&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">65&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&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">66&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;&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">: &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">error_msg&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">67&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&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;/code>&lt;/pre>&lt;/div>&lt;h4 id="步驟-2建立特化的驗證-descriptor">步驟 2：建立特化的驗證 Descriptor&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">PatternField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2"> 正則表達式驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2"> 簡化常見的模式匹配驗證。
&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="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">pattern&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_msg&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"> 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 class="s2"> Args:
&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"> pattern: 正則表達式模式
&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"> error_msg: 驗證失敗時的錯誤訊息
&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&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">16&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&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">pattern&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&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">v&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">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&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="k">class&lt;/span> &lt;span class="nc">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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"> 限制值必須在指定選項中。
&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">choices&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">error_msg&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">28&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">&lt;span class="s2"> choices: 允許的選項
&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"> error_msg: 驗證失敗時的錯誤訊息
&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">choices&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">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">35&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&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">choices&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">error_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">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須是: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">)&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">37&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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"> 非空驗證欄位
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> 確保值不為空。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &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="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">error_msg&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">47&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">48&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&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="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">50&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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="s2">&amp;#34;&amp;#34;&amp;#34;
&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"> 範圍驗證欄位
&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">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">56&lt;/span>&lt;span class="cl">&lt;span class="s2"> 確保數值在指定範圍內。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&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">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">def&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">60&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">61&lt;/span>&lt;span class="cl"> &lt;span class="n">min_val&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">float&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">62&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&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">float&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">63&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&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">64&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&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 class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl">&lt;span class="s2"> min_val: 最小值（None 表示無下限）
&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"> max_val: 最大值（None 表示無上限）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&lt;/span>&lt;span class="cl">&lt;span class="s2"> error_msg: 驗證失敗時的錯誤訊息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&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">71&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">min_val&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">max_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max_val&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">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&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">if&lt;/span> &lt;span class="n">min_val&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="n">v&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">min_val&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="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&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="n">v&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">max_val&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="k">return&lt;/span> &lt;span class="kc">False&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">return&lt;/span> &lt;span class="kc">True&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="n">range_desc&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">82&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&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">83&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">min_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">84&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&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">85&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">max_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">86&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">87&lt;/span>&lt;span class="cl"> &lt;span class="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">88&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">89&lt;/span>&lt;span class="cl"> &lt;span class="n">error_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">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39; 且 &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">range_desc&lt;/span>&lt;span class="p">)&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">90&lt;/span>&lt;span class="cl"> &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">HookConfig&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"> 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"> 驗證規則直接在類別定義中可見。
&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="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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PatternField&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&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="s2">&amp;#34;Hook 名稱必須是小寫字母、數字、連字號或底線，且不能以連字號開頭或結尾&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="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">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChoiceField&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="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionEnd&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;無效的事件類型&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="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="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NonEmptyField&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">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="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RangeField&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">min_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">300&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">error_msg&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">25&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="k">def&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">28&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">29&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="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">event&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">31&lt;/span>&lt;span class="cl"> &lt;span class="n">command&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">32&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">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> 初始化 Hook 配置
&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"> Args:
&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"> name: Hook 名稱
&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"> event: 事件類型
&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"> command: 執行的命令
&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"> timeout: 超時時間（秒）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證會在賦值時自動執行。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="s2"> &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 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 class="c1"># 自動驗證名稱格式&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">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="c1"># 自動驗證事件類型&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">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span> &lt;span class="c1"># 自動驗證非空&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">timeout&lt;/span> &lt;span class="c1"># 自動驗證範圍&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">__repr__&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">51&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">52&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;HookConfig(name=&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">!r}&lt;/span>&lt;span class="s2">, event=&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">event&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">53&lt;/span>&lt;span class="cl"> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;command=&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">command&lt;/span>&lt;span class="si">!r}&lt;/span>&lt;span class="s2">, timeout=&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">timeout&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">54&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">展示如何用 Descriptor Protocol 實現宣告式驗證。
&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">import&lt;/span> &lt;span class="nn">re&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">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">Callable&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"> 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"># ===== Descriptor 定義 =====&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">ValidatedField&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證欄位 Descriptor 基類&amp;#34;&amp;#34;&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="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="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">validator&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">],&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"> 19&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&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"> 20&lt;/span>&lt;span class="cl"> &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">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&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">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&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">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"> 24&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_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"> 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">__set_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="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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"> 27&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"> 28&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&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">&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">__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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&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 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"> 31&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&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"> 32&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"> 33&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">obj&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">private_name&lt;/span>&lt;span class="p">,&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>&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="fm">__set__&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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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"> 36&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&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"> 37&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;&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">: &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">error_msg&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"> 38&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&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"> 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">class&lt;/span> &lt;span class="nc">PatternField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 43&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">pattern&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_msg&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"> 44&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pattern&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pattern&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 45&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"> 46&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&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">pattern&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="k">match&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">v&lt;/span>&lt;span class="p">))),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 49&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 50&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ChoiceField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&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">choices&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">error_msg&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"> 54&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">choices&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&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"> 56&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&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">choices&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="n">error_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">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須是: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">c&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">choices&lt;/span>&lt;span class="p">)&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"> 58&lt;/span>&lt;span class="cl"> &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">class&lt;/span> &lt;span class="nc">NonEmptyField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 63&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">error_msg&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"> 64&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"> 65&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">lambda&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">error_msg&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 69&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ValidatedField&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="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"> 71&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 72&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>&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="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 74&lt;/span>&lt;span class="cl"> &lt;span class="n">min_val&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">float&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"> 75&lt;/span>&lt;span class="cl"> &lt;span class="n">max_val&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">float&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"> 76&lt;/span>&lt;span class="cl"> &lt;span class="n">error_msg&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"> 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 class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">min_val&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_val&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max_val&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">def&lt;/span> &lt;span class="nf">validator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&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">if&lt;/span> &lt;span class="n">min_val&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="n">v&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">min_val&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 83&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&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="n">max_val&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="n">v&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">max_val&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="kc">False&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="kc">True&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="n">range_desc&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"> 89&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">min_val&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"> 90&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">min_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"> 91&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">max_val&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"> 92&lt;/span>&lt;span class="cl"> &lt;span class="n">range_desc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;lt;= &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">max_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"> 93&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&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"> 95&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">validator&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="n">error_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">error_msg&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">，必須 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="s1">&amp;#39; 且 &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">range_desc&lt;/span>&lt;span class="p">)&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"> 97&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 98&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 99&lt;/span>&lt;span class="cl">&lt;span class="c1"># ===== 使用範例 =====&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">class&lt;/span> &lt;span class="nc">HookConfig&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="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">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">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PatternField&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="sa">r&lt;/span>&lt;span class="s2">&amp;#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">106&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;Hook 名稱格式無效&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">107&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChoiceField&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="p">(&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;PostToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Stop&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionStart&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;SessionEnd&amp;#34;&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="s2">&amp;#34;無效的事件類型&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="n">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NonEmptyField&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">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="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RangeField&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">min_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_val&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">300&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_msg&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">117&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&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="n">event&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">command&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">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">30&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&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">120&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&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">command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">command&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&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">123&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">124&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__repr__&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">125&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;HookConfig(&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">!r}&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">event&lt;/span>&lt;span class="si">!r}&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">command&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">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="c1"># ===== 測試 =====&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">128&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">129&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">130&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 正確的配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">131&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">132&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;check-format&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">133&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&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">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&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="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="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">config&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">137&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">138&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 修改屬性也會驗證&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">139&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">140&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;修改 timeout: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&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">141&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">142&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 驗證失敗的例子&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">143&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">144&lt;/span>&lt;span class="cl"> &lt;span class="n">bad_config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">145&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;Check-Format&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">146&lt;/span>&lt;span class="cl"> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&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="n">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">148&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">149&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&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">150&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">151&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">152&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">153&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;InvalidEvent&amp;#34;&lt;/span> &lt;span class="c1"># 錯誤：無效的事件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">154&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&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">155&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">156&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">157&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">158&lt;/span>&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">500&lt;/span> &lt;span class="c1"># 錯誤：超出範圍&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">159&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&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">160&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;/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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HookConfig&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="o">...&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;check-format&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="o">...&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PreToolUse&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="o">...&lt;/span> &lt;span class="n">command&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;python check.py&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="o">...&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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&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">HookConfig&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;check-format&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;PreToolUse&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;python check.py&amp;#39;&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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&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">13&lt;/span>&lt;span class="cl">&lt;span class="mi">60&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 驗證失敗&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&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;Invalid Name&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="ne">ValueError&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">Hook&lt;/span> &lt;span class="n">名稱格式無效&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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">event&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;BadEvent&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="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">無效的事件類型&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="n">必須是&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">PreToolUse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">PostToolUse&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Stop&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SessionStart&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SessionEnd&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="o">&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">timeout&lt;/span> &lt;span class="o">=&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">23&lt;/span>&lt;span class="cl">&lt;span class="ne">ValueError&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">超時時間無效&lt;/span>&lt;span class="err">，&lt;/span>&lt;span class="n">必須&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="n">且&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">300&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>宣告式驗證（Descriptor）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &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>Descriptor 可在多個類別重用&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>需要理解 Descriptor Protocol&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>調試&lt;/td>
 &lt;td>容易追蹤&lt;/td>
 &lt;td>需要了解 &lt;code>__get__&lt;/code>/&lt;code>__set__&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>彈性&lt;/td>
 &lt;td>高&lt;/td>
 &lt;td>中等（需要遵循 Descriptor 協議）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="什麼時候該用宣告式驗證">什麼時候該用宣告式驗證？&lt;/h2>
&lt;p>&lt;strong>適合使用&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Descriptor Protocol 實現宣告式驗證。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南</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="c1"># 驗證模式定義為類別常數</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">VALID_NAME_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/declarative-validation/[a-z0-9\-_]*[a-z0-9])?\.py$&#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></span><span class="line"><span class="ln"> 9</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">10</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">11</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">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">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">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證單個 Hook 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</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">17</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">18</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">19</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">20</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">21</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">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">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">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">filename</span> <span class="o">=</span> <span class="n">hook_path</span><span class="o">.</span><span class="n">name</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">valid_name</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="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">28</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_NAME_PATTERNS</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 class="k">if</span> <span class="ow">not</span> <span class="n">valid_name</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="p">[</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">32</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">33</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案名稱不符合規範: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;建議使用 snake-case 或 kebab-case 命名&#34;</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="p">)]</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ol>
<li><strong>直覺易懂</strong>：每個 <code>check_*</code> 方法負責一項檢查</li>
<li><strong>彈性高</strong>：容易新增或修改檢查邏輯</li>
<li><strong>調試方便</strong>：可以單獨執行任一檢查方法</li>
</ol>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要<strong>設定類別</strong>時（例如 Hook 配置），命令式驗證會有問題：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfig</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">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="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">command</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="c1"># 驗證邏輯散落在 __init__ 中</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">re</span><span class="o">.</span><span class="k">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span> <span class="n">name</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="sa">f</span><span class="s2">&#34;無效的 Hook 名稱: </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 class="k">if</span> <span class="n">event</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</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">event</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">if</span> <span class="ow">not</span> <span class="n">command</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</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">11</span><span class="cl">
</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">name</span> <span class="o">=</span> <span class="n">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">event</span> <span class="o">=</span> <span class="n">event</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">command</span> <span class="o">=</span> <span class="n">command</span></span></span></code></pre></div><p>問題：</p>
<ul>
<li>驗證邏輯在 <code>__init__</code> 中，不容易重用</li>
<li>修改屬性時不會重新驗證</li>
<li>無法在類別定義中看到驗證規則</li>
</ul>
<h2 id="進階解決方案宣告式驗證">進階解決方案：宣告式驗證</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>驗證規則在類別定義中可見</strong></li>
<li><strong>賦值時自動驗證</strong></li>
<li><strong>驗證邏輯可重用</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1建立基礎-descriptor">步驟 1：建立基礎 Descriptor</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">re</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">ValidatedField</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">    驗證欄位 Descriptor
</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></span><span class="line"><span class="ln">12</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">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">validator</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="nb">bool</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">error_msg</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="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            validator: 驗證函式，接受值，返回 bool
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</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="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">23</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="c1"># __set_name__ 會設定這些</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">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">26</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_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">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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</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">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="s2">        Python 3.6+ 自動呼叫，取得屬性名稱
</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">        Args:
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">            owner: 擁有此 Descriptor 的類別
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">            name: 屬性名稱
</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="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">37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</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">&#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="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</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">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">        讀取屬性時呼叫
</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">            obj: 實例（如果透過實例存取）
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">            objtype: 類別
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="s2">            屬性值，或透過類別存取時返回 Descriptor 本身
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</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">return</span> <span class="bp">self</span>  <span class="c1"># 透過類別存取，返回 Descriptor</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="kc">None</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">55</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</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">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">            obj: 實例
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="s2">            value: 要設定的值
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="s2">            ValueError: 驗證失敗時
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">65</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">validator</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 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="bp">self</span><span class="o">.</span><span class="n">name</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">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span></span></span></code></pre></div><h4 id="步驟-2建立特化的驗證-descriptor">步驟 2：建立特化的驗證 Descriptor</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">PatternField</span><span class="p">(</span><span class="n">ValidatedField</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></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_msg</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"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">            pattern: 正則表達式模式
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</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="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">15</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">16</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</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="nb">str</span><span class="p">(</span><span class="n">v</span><span class="p">))),</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><span class="n">ValidatedField</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">    限制值必須在指定選項中。
</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">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">error_msg</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">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">            choices: 允許的選項
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</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="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln">34</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">35</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</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">choices</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須是: </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="nb">str</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">choices</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="k">class</span> <span class="nc">NonEmptyField</span><span class="p">(</span><span class="n">ValidatedField</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">    非空驗證欄位
</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">    &#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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">error_msg</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">47</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">48</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="n">v</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</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">RangeField</span><span class="p">(</span><span class="n">ValidatedField</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">    範圍驗證欄位
</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">    確保數值在指定範圍內。
</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></span><span class="line"><span class="ln">60</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="n">min_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</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">62</span><span class="cl">        <span class="n">max_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</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">63</span><span class="cl">        <span class="n">error_msg</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">64</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">66</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">67</span><span class="cl"><span class="s2">            min_val: 最小值（None 表示無下限）
</span></span></span><span class="line"><span class="ln">68</span><span class="cl"><span class="s2">            max_val: 最大值（None 表示無上限）
</span></span></span><span class="line"><span class="ln">69</span><span class="cl"><span class="s2">            error_msg: 驗證失敗時的錯誤訊息
</span></span></span><span class="line"><span class="ln">70</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</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">min_val</span> <span class="o">=</span> <span class="n">min_val</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">max_val</span> <span class="o">=</span> <span class="n">max_val</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">validator</span><span class="p">(</span><span class="n">v</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">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">min_val</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="kc">False</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">            <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">max_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">78</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">            <span class="k">return</span> <span class="kc">True</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="n">range_desc</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">        <span class="k">if</span> <span class="n">min_val</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">83</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&gt;= </span><span class="si">{</span><span class="n">min_val</span><span class="si">}</span><span class="s2">&#34;</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="n">max_val</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">85</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&lt;= </span><span class="si">{</span><span class="n">max_val</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">86</span><span class="cl">
</span></span><span class="line"><span class="ln">87</span><span class="cl">        <span class="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">88</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="n">validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須 </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">range_desc</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <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">HookConfig</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">    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">    驗證規則直接在類別定義中可見。
</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="c1"># 宣告式驗證：欄位定義即驗證規則</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="s2">&#34;Hook 名稱必須是小寫字母、數字、連字號或底線，且不能以連字號開頭或結尾&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionEnd&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;無效的事件類型&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">command</span> <span class="o">=</span> <span class="n">NonEmptyField</span><span class="p">(</span><span class="s2">&#34;命令不能為空&#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="n">timeout</span> <span class="o">=</span> <span class="n">RangeField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">min_val</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">max_val</span><span class="o">=</span><span class="mi">300</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">error_msg</span><span class="o">=</span><span class="s2">&#34;超時時間無效&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</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="p">,</span>
</span></span><span class="line"><span class="ln">29</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">30</span><span class="cl">        <span class="n">event</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="n">command</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</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">30</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        初始化 Hook 配置
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">            name: Hook 名稱
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            event: 事件類型
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">            command: 執行的命令
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="s2">            timeout: 超時時間（秒）
</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">        &#34;&#34;&#34;</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">name</span> <span class="o">=</span> <span class="n">name</span>      <span class="c1"># 自動驗證名稱格式</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">event</span> <span class="o">=</span> <span class="n">event</span>    <span class="c1"># 自動驗證事件類型</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">command</span> <span class="o">=</span> <span class="n">command</span>  <span class="c1"># 自動驗證非空</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">timeout</span> <span class="o">=</span> <span class="n">timeout</span>  <span class="c1"># 自動驗證範圍</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">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;HookConfig(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">!r}</span><span class="s2">, event=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">event</span><span class="si">!r}</span><span class="s2">, &#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">            <span class="sa">f</span><span class="s2">&#34;command=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="si">!r}</span><span class="s2">, timeout=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="si">}</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="ln">54</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">展示如何用 Descriptor Protocol 實現宣告式驗證。
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">  7</span><span class="cl">
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">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">Optional</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"># ===== Descriptor 定義 =====</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">ValidatedField</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證欄位 Descriptor 基類&#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></span><span class="line"><span class="ln"> 17</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">        <span class="n">validator</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="nb">bool</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="n">error_msg</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"> 20</span><span class="cl">    <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">validator</span> <span class="o">=</span> <span class="n">validator</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">error_msg</span> <span class="o">=</span> <span class="n">error_msg</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">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"> 24</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_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"> 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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</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"> 27</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"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</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">&#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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</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"> 31</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</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">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="kc">None</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">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 36</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">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 37</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="bp">self</span><span class="o">.</span><span class="n">name</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">error_msg</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</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">PatternField</span><span class="p">(</span><span class="n">ValidatedField</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></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="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">error_msg</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"> 44</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"> 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="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</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="nb">str</span><span class="p">(</span><span class="n">v</span><span class="p">))),</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</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">ChoiceField</span><span class="p">(</span><span class="n">ValidatedField</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></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">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">,</span> <span class="n">error_msg</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">choices</span> <span class="o">=</span> <span class="n">choices</span>
</span></span><span class="line"><span class="ln"> 55</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"> 56</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</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">choices</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須是: </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="nb">str</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">choices</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</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="k">class</span> <span class="nc">NonEmptyField</span><span class="p">(</span><span class="n">ValidatedField</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></span><span class="line"><span class="ln"> 63</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">error_msg</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"> 64</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"> 65</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="k">lambda</span> <span class="n">v</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="n">v</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="n">error_msg</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">class</span> <span class="nc">RangeField</span><span class="p">(</span><span class="n">ValidatedField</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">    <span class="s2">&#34;&#34;&#34;範圍驗證欄位&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="n">min_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</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"> 75</span><span class="cl">        <span class="n">max_val</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">error_msg</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"> 77</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">min_val</span> <span class="o">=</span> <span class="n">min_val</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_val</span> <span class="o">=</span> <span class="n">max_val</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="k">def</span> <span class="nf">validator</span><span class="p">(</span><span class="n">v</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">            <span class="k">if</span> <span class="n">min_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">min_val</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">            <span class="k">if</span> <span class="n">max_val</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">max_val</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="kc">False</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">            <span class="k">return</span> <span class="kc">True</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">range_desc</span> <span class="o">=</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">min_val</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"> 90</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&gt;= </span><span class="si">{</span><span class="n">min_val</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 class="k">if</span> <span class="n">max_val</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"> 92</span><span class="cl">            <span class="n">range_desc</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;&lt;= </span><span class="si">{</span><span class="n">max_val</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></span><span class="line"><span class="ln"> 94</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"> 95</span><span class="cl">            <span class="n">validator</span><span class="o">=</span><span class="n">validator</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">            <span class="n">error_msg</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">error_msg</span><span class="si">}</span><span class="s2">，必須 </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">range_desc</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">
</span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="c1"># ===== 使用範例 =====</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">class</span> <span class="nc">HookConfig</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Hook 配置類別 - 宣告式驗證&#34;&#34;&#34;</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">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">        <span class="s2">&#34;Hook 名稱格式無效&#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></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">        <span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionStart&#34;</span><span class="p">,</span> <span class="s2">&#34;SessionEnd&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">        <span class="s2">&#34;無效的事件類型&#34;</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">113</span><span class="cl">
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="n">command</span> <span class="o">=</span> <span class="n">NonEmptyField</span><span class="p">(</span><span class="s2">&#34;命令不能為空&#34;</span><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="n">timeout</span> <span class="o">=</span> <span class="n">RangeField</span><span class="p">(</span><span class="n">min_val</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">max_val</span><span class="o">=</span><span class="mi">300</span><span class="p">,</span> <span class="n">error_msg</span><span class="o">=</span><span class="s2">&#34;超時時間無效&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="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="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">command</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">timeout</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">30</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">name</span> <span class="o">=</span> <span class="n">name</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">event</span> <span class="o">=</span> <span class="n">event</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">command</span> <span class="o">=</span> <span class="n">command</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">timeout</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">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;HookConfig(</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">event</span><span class="si">!r}</span><span class="s2">, </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="si">!r}</span><span class="s2">)&#34;</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="c1"># ===== 測試 =====</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">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">130</span><span class="cl">    <span class="c1"># 正確的配置</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">    <span class="n">config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">132</span><span class="cl">        <span class="n">name</span><span class="o">=</span><span class="s2">&#34;check-format&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="n">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="n">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">136</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">config</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></span><span class="line"><span class="ln">138</span><span class="cl">    <span class="c1"># 修改屬性也會驗證</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">    <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">60</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;修改 timeout: </span><span class="si">{</span><span class="n">config</span><span class="o">.</span><span class="n">timeout</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">
</span></span><span class="line"><span class="ln">142</span><span class="cl">    <span class="c1"># 驗證失敗的例子</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">        <span class="n">bad_config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">            <span class="n">name</span><span class="o">=</span><span class="s2">&#34;Check-Format&#34;</span><span class="p">,</span>  <span class="c1"># 錯誤：大寫</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">            <span class="n">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">147</span><span class="cl">            <span class="n">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln">148</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">149</span><span class="cl">    <span 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">150</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">151</span><span class="cl">
</span></span><span class="line"><span class="ln">152</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">153</span><span class="cl">        <span class="n">config</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="s2">&#34;InvalidEvent&#34;</span>  <span class="c1"># 錯誤：無效的事件</span>
</span></span><span class="line"><span class="ln">154</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">155</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">156</span><span class="cl">
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">158</span><span class="cl">        <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">500</span>  <span class="c1"># 錯誤：超出範圍</span>
</span></span><span class="line"><span class="ln">159</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">160</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></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="o">&gt;&gt;&gt;</span> <span class="n">config</span> <span class="o">=</span> <span class="n">HookConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="o">...</span>     <span class="n">name</span><span class="o">=</span><span class="s2">&#34;check-format&#34;</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">event</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</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">command</span><span class="o">=</span><span class="s2">&#34;python check.py&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="o">...</span> <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">HookConfig</span><span class="p">(</span><span class="s1">&#39;check-format&#39;</span><span class="p">,</span> <span class="s1">&#39;PreToolUse&#39;</span><span class="p">,</span> <span class="s1">&#39;python check.py&#39;</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="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">60</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="mi">60</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 驗證失敗</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Invalid Name&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">name</span><span class="p">:</span> <span class="n">Hook</span> <span class="n">名稱格式無效</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="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">event</span> <span class="o">=</span> <span class="s2">&#34;BadEvent&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">event</span><span class="p">:</span> <span class="n">無效的事件類型</span><span class="err">，</span><span class="n">必須是</span><span class="p">:</span> <span class="n">PreToolUse</span><span class="p">,</span> <span class="n">PostToolUse</span><span class="p">,</span> <span class="n">Stop</span><span class="p">,</span> <span class="n">SessionStart</span><span class="p">,</span> <span class="n">SessionEnd</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="o">&gt;&gt;&gt;</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="ne">ValueError</span><span class="p">:</span> <span class="n">timeout</span><span class="p">:</span> <span class="n">超時時間無效</span><span class="err">，</span><span class="n">必須</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="n">且</span> <span class="o">&lt;=</span> <span class="mi">300</span></span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>命令式驗證</th>
          <th>宣告式驗證（Descriptor）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>可讀性</td>
          <td>驗證邏輯散落在方法中</td>
          <td>驗證規則在類別定義中可見</td>
      </tr>
      <tr>
          <td>重用性</td>
          <td>需要複製驗證邏輯</td>
          <td>Descriptor 可在多個類別重用</td>
      </tr>
      <tr>
          <td>賦值驗證</td>
          <td>需要手動呼叫驗證</td>
          <td>自動在賦值時驗證</td>
      </tr>
      <tr>
          <td>複雜度</td>
          <td>簡單直覺</td>
          <td>需要理解 Descriptor Protocol</td>
      </tr>
      <tr>
          <td>調試</td>
          <td>容易追蹤</td>
          <td>需要了解 <code>__get__</code>/<code>__set__</code></td>
      </tr>
      <tr>
          <td>彈性</td>
          <td>高</td>
          <td>中等（需要遵循 Descriptor 協議）</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用宣告式驗證">什麼時候該用宣告式驗證？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>資料類別（Data Class）需要驗證</li>
<li>同樣的驗證規則需要在多處使用</li>
<li>希望在類別定義中清楚看到驗證規則</li>
<li>需要在屬性賦值時自動驗證</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的一次性驗證</li>
<li>驗證邏輯需要存取多個欄位</li>
<li>團隊不熟悉 Descriptor Protocol</li>
<li>驗證邏輯經常變動</li>
</ul>
<h2 id="進階與-dataclass-結合">進階：與 dataclass 結合</h2>
<p>Python 3.7+ 的 <code>dataclass</code> 可以與 Descriptor 結合：</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></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">class</span> <span class="nc">HookConfigDataclass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;使用 dataclass + Descriptor&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="c1"># Descriptor 欄位需要特殊處理</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="n">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="nb">repr</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">_event</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="nb">repr</span><span class="o">=</span><span class="kc">False</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">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">event</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="c1"># Descriptor 定義（類別變數）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">PatternField</span><span class="p">(</span><span class="sa">r</span><span class="s2">&#34;^[a-z0-9][a-z0-9\-_]*[a-z0-9]$&#34;</span><span class="p">,</span> <span class="s2">&#34;無效的名稱&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">((</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">),</span> <span class="s2">&#34;無效的事件&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">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">20</span><span class="cl">        <span class="c1"># 觸發 Descriptor 驗證</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">name</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</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">event</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">event</span></span></span></code></pre></div><h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<ol>
<li>實作一個 <code>EmailField</code> Descriptor，驗證 email 格式</li>
<li>實作一個 <code>LengthField</code> Descriptor，驗證字串長度</li>
</ol>
<h3 id="進階練習">進階練習</h3>
<ol start="3">
<li>修改 <code>ValidatedField</code>，支援可選欄位（允許 <code>None</code>）</li>
<li>實作一個 <code>CompositeField</code>，可以組合多個驗證規則</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<ol start="5">
<li>參考 <code>hook_validator.py</code> 的 <code>check_lib_imports</code> 方法，用 Descriptor 實現「根據欄位值決定是否需要驗證另一個欄位」的邏輯</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/howto/descriptor.html">Python Descriptor HOWTO</a></li>
<li><a href="https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/">Django Model Field</a></li>
<li><a href="https://docs.pydantic.dev/latest/concepts/validators/">Pydantic Field Validators</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/" data-link-title="案例研究：元編程實戰" data-link-desc="基於 Hook 系統的元編程實戰案例">案例研究索引</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">案例：自動註冊機制</a></em></p>
]]></content:encoded></item><item><title>2.1 Descriptor Protocol 完整指南</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/</guid><description>&lt;p>Descriptor 是 Python 中最強大但也最容易被忽略的機制之一。理解 Descriptor 是深入 Python 物件模型的關鍵。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/04-oop/singleton-cache/" data-link-title="4.4 單例與快取模式" data-link-desc="控制物件生命週期">4.4 單例與快取&lt;/a>（@property 的使用）&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>理解 @property 實際上是一個 Descriptor&lt;/li>
&lt;li>區分 Data Descriptor 和 Non-data Descriptor&lt;/li>
&lt;li>理解屬性查找順序&lt;/li>
&lt;li>實作自訂的 Descriptor&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="原理層property-的真相">【原理層】@property 的真相&lt;/h2>
&lt;h3 id="property-是一個-descriptor">property 是一個 Descriptor&lt;/h3>
&lt;p>當你使用 &lt;code>@property&lt;/code> 時，實際上是建立了一個 Descriptor：&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">Circle&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">radius&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">_radius&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">radius&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">@property&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">radius&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 class="o">.&lt;/span>&lt;span class="n">_radius&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="nd">@radius.setter&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">def&lt;/span> &lt;span class="nf">radius&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>&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">value&lt;/span> &lt;span class="o">&amp;lt;&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">12&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;半徑不能為負&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_radius&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">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="c1"># 等價於&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Circle&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">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">radius&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">_radius&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">radius&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_radius&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="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_radius&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">set_radius&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>&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="n">value&lt;/span> &lt;span class="o">&amp;lt;&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">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;半徑不能為負&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">_radius&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">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="n">radius&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">property&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_radius&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">set_radius&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>property&lt;/code> 是 Python 內建的 Descriptor 類別。&lt;/p>
&lt;h3 id="descriptor-protocol">Descriptor Protocol&lt;/h3>
&lt;p>Descriptor 是實現了以下方法之一的物件：&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">Descriptor&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">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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"> 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">def&lt;/span> &lt;span class="fm">__set__&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">obj&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"> 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">def&lt;/span> &lt;span class="fm">__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">obj&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">def&lt;/span> &lt;span class="nf">__set_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="n">owner&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">15&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Python 3.6+：設定屬性名時呼叫&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;/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">Verbose&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;一個會報告存取情況的 Descriptor&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">__set_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="n">owner&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"> 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>&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">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">obj&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"> 9&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">10&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;讀取 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="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">11&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&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">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="k">def&lt;/span> &lt;span class="fm">__set__&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">obj&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">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="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"> = &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">15&lt;/span>&lt;span class="cl"> &lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&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">name&lt;/span>&lt;span class="p">]&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">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">class&lt;/span> &lt;span class="nc">MyClass&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">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Verbose&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="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">10&lt;/span> &lt;span class="c1"># 輸出：設定 x = 10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 輸出：讀取 x，然後 10&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層data-vs-non-data-descriptor">【設計層】Data vs Non-data Descriptor&lt;/h2>
&lt;h3 id="兩種-descriptor">兩種 Descriptor&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"># Data Descriptor：有 __set__ 或 __delete__&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">DataDescriptor&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">def&lt;/span> &lt;span class="fm">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;data descriptor&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">__set__&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">obj&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"> 7&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"> 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"># Non-data Descriptor：只有 __get__&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">class&lt;/span> &lt;span class="nc">NonDataDescriptor&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">def&lt;/span> &lt;span class="fm">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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">12&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;non-data descriptor&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="屬性查找順序">屬性查找順序&lt;/h3>
&lt;p>這是理解 Descriptor 的關鍵：&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">obj.attr 的查找順序：
&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">1. Data Descriptor（在類別或父類別中）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">2. Instance __dict__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">3. Non-data Descriptor（在類別或父類別中）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">4. Class __dict__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">5. __getattr__（如果定義了）&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">DataDesc&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">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;data descriptor&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">def&lt;/span> &lt;span class="fm">__set__&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">obj&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"> 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="k">class&lt;/span> &lt;span class="nc">NonDataDesc&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="fm">__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">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;non-data descriptor&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">MyClass&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">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">DataDesc&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">nondata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NonDataDesc&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="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;data&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;instance value&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;nondata&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;instance value&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&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="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># data descriptor（Data Descriptor 優先）&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="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nondata&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># instance value（Instance 優先於 Non-data）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼-method-是-non-data-descriptor">為什麼 method 是 Non-data Descriptor？&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">MyClass&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="nf">method&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="k">return&lt;/span> &lt;span class="s2">&amp;#34;method&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">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">method&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">lambda&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;overridden&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># overridden&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果 method 是 Data Descriptor，就無法這樣覆蓋了。&lt;/p></description><content:encoded><![CDATA[<p>Descriptor 是 Python 中最強大但也最容易被忽略的機制之一。理解 Descriptor 是深入 Python 物件模型的關鍵。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/04-oop/singleton-cache/" data-link-title="4.4 單例與快取模式" data-link-desc="控制物件生命週期">4.4 單例與快取</a>（@property 的使用）</li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解 @property 實際上是一個 Descriptor</li>
<li>區分 Data Descriptor 和 Non-data Descriptor</li>
<li>理解屬性查找順序</li>
<li>實作自訂的 Descriptor</li>
</ol>
<hr>
<h2 id="原理層property-的真相">【原理層】@property 的真相</h2>
<h3 id="property-是一個-descriptor">property 是一個 Descriptor</h3>
<p>當你使用 <code>@property</code> 時，實際上是建立了一個 Descriptor：</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">Circle</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">radius</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">_radius</span> <span class="o">=</span> <span class="n">radius</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">@property</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">radius</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 class="o">.</span><span class="n">_radius</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">@radius.setter</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">radius</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</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">value</span> <span class="o">&lt;</span> <span class="mi">0</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">ValueError</span><span class="p">(</span><span class="s2">&#34;半徑不能為負&#34;</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">_radius</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 等價於</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">class</span> <span class="nc">Circle</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">radius</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">_radius</span> <span class="o">=</span> <span class="n">radius</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_radius</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="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_radius</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">set_radius</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</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">value</span> <span class="o">&lt;</span> <span class="mi">0</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;半徑不能為負&#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">_radius</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">radius</span> <span class="o">=</span> <span class="nb">property</span><span class="p">(</span><span class="n">get_radius</span><span class="p">,</span> <span class="n">set_radius</span><span class="p">)</span></span></span></code></pre></div><p><code>property</code> 是 Python 內建的 Descriptor 類別。</p>
<h3 id="descriptor-protocol">Descriptor Protocol</h3>
<p>Descriptor 是實現了以下方法之一的物件：</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">Descriptor</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</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="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">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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">def</span> <span class="fm">__delete__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</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">def</span> <span class="nf">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Python 3.6+：設定屬性名時呼叫&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="簡單範例">簡單範例</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Verbose</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;一個會報告存取情況的 Descriptor&#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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</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></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</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="k">if</span> <span class="n">obj</span> <span class="ow">is</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="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;讀取 </span><span class="si">{</span><span class="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">11</span><span class="cl">        <span class="k">return</span> <span class="n">obj</span><span class="o">.</span><span class="vm">__dict__</span><span class="o">.</span><span class="n">get</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">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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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="bp">self</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">value</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="n">obj</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="bp">self</span><span class="o">.</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">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">x</span> <span class="o">=</span> <span class="n">Verbose</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="n">m</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">m</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>  <span class="c1"># 輸出：設定 x = 10</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="n">m</span><span class="o">.</span><span class="n">x</span><span class="p">)</span>  <span class="c1"># 輸出：讀取 x，然後 10</span></span></span></code></pre></div><hr>
<h2 id="設計層data-vs-non-data-descriptor">【設計層】Data vs Non-data Descriptor</h2>
<h3 id="兩種-descriptor">兩種 Descriptor</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"># Data Descriptor：有 __set__ 或 __delete__</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">DataDescriptor</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</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">return</span> <span class="s2">&#34;data descriptor&#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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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"># Non-data Descriptor：只有 __get__</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">class</span> <span class="nc">NonDataDescriptor</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="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</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="k">return</span> <span class="s2">&#34;non-data descriptor&#34;</span></span></span></code></pre></div><h3 id="屬性查找順序">屬性查找順序</h3>
<p>這是理解 Descriptor 的關鍵：</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">obj.attr 的查找順序：
</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">1. Data Descriptor（在類別或父類別中）
</span></span><span class="line"><span class="ln">4</span><span class="cl">2. Instance __dict__
</span></span><span class="line"><span class="ln">5</span><span class="cl">3. Non-data Descriptor（在類別或父類別中）
</span></span><span class="line"><span class="ln">6</span><span class="cl">4. Class __dict__
</span></span><span class="line"><span class="ln">7</span><span class="cl">5. __getattr__（如果定義了）</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="k">class</span> <span class="nc">DataDesc</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</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">return</span> <span class="s2">&#34;data descriptor&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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="k">class</span> <span class="nc">NonDataDesc</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="fm">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</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="k">return</span> <span class="s2">&#34;non-data descriptor&#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">MyClass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">DataDesc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">nondata</span> <span class="o">=</span> <span class="n">NonDataDesc</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">m</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">m</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="s1">&#39;data&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;instance value&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">m</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="s1">&#39;nondata&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;instance value&#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="nb">print</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>     <span class="c1"># data descriptor（Data Descriptor 優先）</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="n">m</span><span class="o">.</span><span class="n">nondata</span><span class="p">)</span>  <span class="c1"># instance value（Instance 優先於 Non-data）</span></span></span></code></pre></div><h3 id="為什麼-method-是-non-data-descriptor">為什麼 method 是 Non-data Descriptor？</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">MyClass</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="nf">method</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="k">return</span> <span class="s2">&#34;method&#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">m</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">m</span><span class="o">.</span><span class="n">method</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="s2">&#34;overridden&#34;</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">m</span><span class="o">.</span><span class="n">method</span><span class="p">())</span>  <span class="c1"># overridden</span></span></span></code></pre></div><p>如果 method 是 Data Descriptor，就無法這樣覆蓋了。</p>
<hr>
<h2 id="實作層實用的-descriptor">【實作層】實用的 Descriptor</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">LazyProperty</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">func</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">func</span> <span class="o">=</span> <span class="n">func</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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</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">name</span> <span class="o">=</span> <span class="n">name</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</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="k">if</span> <span class="n">obj</span> <span class="ow">is</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">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">func</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">obj</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>  <span class="c1"># 快取到實例</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">return</span> <span class="n">value</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">Data</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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">values</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">values</span> <span class="o">=</span> <span class="n">values</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">@LazyProperty</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">average</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="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">22</span><span class="cl">        <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">values</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="n">Data</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">25</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">average</span><span class="p">)</span>  <span class="c1"># 計算平均值... 3.0</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">d</span><span class="o">.</span><span class="n">average</span><span class="p">)</span>  <span class="c1"># 3.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">class</span> <span class="nc">Typed</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">expected_type</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">expected_type</span> <span class="o">=</span> <span class="n">expected_type</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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</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">name</span> <span class="o">=</span> <span class="n">name</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</span><span class="o">=</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="k">if</span> <span class="n">obj</span> <span class="ow">is</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">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="n">obj</span><span class="o">.</span><span class="vm">__dict__</span><span class="o">.</span><span class="n">get</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">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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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">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">15</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">16</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">name</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">expected_type</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">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">obj</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">[</span><span class="bp">self</span><span class="o">.</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">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">Person</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="n">Typed</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="n">age</span> <span class="o">=</span> <span class="n">Typed</span><span class="p">(</span><span class="nb">int</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="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="n">age</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">name</span> <span class="o">=</span> <span class="n">name</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">age</span> <span class="o">=</span> <span class="n">age</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="n">p</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s2">&#34;Alice&#34;</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span>  <span class="c1"># OK</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">p</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s2">&#34;Bob&#34;</span><span class="p">,</span> <span class="s2">&#34;thirty&#34;</span><span class="p">)</span>  <span class="c1"># TypeError!</span></span></span></code></pre></div><h3 id="類似-django-model-field">類似 Django Model Field</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">Field</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">default</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="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"> 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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">name</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">name</span> <span class="o">=</span> <span class="n">name</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">private_name</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">&#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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">objtype</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">obj</span> <span class="ow">is</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">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">CharField</span><span class="p">(</span><span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_length</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">19</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="o">**</span><span class="n">kwargs</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">max_length</span> <span class="o">=</span> <span class="n">max_length</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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">value</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">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">24</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="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">25</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">User</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>__set_name__</code> 是 Python 3.6 才加入的？之前怎麼解決這個問題？</li>
<li><code>@property</code> 是 Data Descriptor 還是 Non-data Descriptor？為什麼？</li>
<li>如果 Descriptor 存在於實例的 <code>__dict__</code> 中會怎樣？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>實作一個 <code>@cached_property</code> 裝飾器</li>
<li>實作一個範圍驗證的 Descriptor（如 <code>age = Range(0, 150)</code>）</li>
<li>實作一個只讀屬性的 Descriptor</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/howto/descriptor.html">Python 官方 Descriptor Guide</a></li>
<li><a href="https://realpython.com/python-descriptors/">Real Python - Python Descriptors</a></li>
</ul>
<hr>
<p>下一章：<a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">Metaclass 設計與應用</a></p>
]]></content:encoded></item><item><title>案例：自動註冊機制</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_validator.py&lt;/code> 的實際程式碼，展示如何用 Metaclass 實現檢查器的自動註冊。&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/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用&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;code>HookValidator&lt;/code> 類別包含多個 &lt;code>check_*&lt;/code> 方法，需要在 &lt;code>validate_hook()&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"># 執行各項檢查 - 手動呼叫每個 check 方法&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;檢查 Hook 輸出格式&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;strong>明確的執行順序&lt;/strong>：可以精確控制檢查項的執行順序&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;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>新增檢查項時：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>需要修改兩處程式碼&lt;/strong>：新增 &lt;code>check_*&lt;/code> 方法 + 修改 &lt;code>validate_hook()&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;li>&lt;strong>難以擴展&lt;/strong>：子類別新增檢查項也需要覆寫 &lt;code>validate_hook()&lt;/code>&lt;/li>
&lt;/ol>
&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>自動繼承&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="kn">from&lt;/span> &lt;span class="nn">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">wraps&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">Callable&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"> 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">check&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">priority&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">100&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">enabled&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="n">description&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"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Callable&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;
&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"> Decorator to mark a method as a checker.
&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"> Args:
&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"> priority: Execution order (lower runs first)
&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"> enabled: Whether this check is enabled by default
&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"> description: Human-readable description
&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"> Example:
&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"> @check(priority=10, description=&amp;#34;Validate filename format&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"> def check_naming(self, hook_path):
&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">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">Callable&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>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Store metadata on the function object&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">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_is_checker&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">25&lt;/span>&lt;span class="cl"> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_priority&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">priority&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">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enabled&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">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">description&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__doc__&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&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">@wraps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&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">wrapper&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">**&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">31&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">func&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">**&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">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="c1"># Copy metadata to wrapper&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_is_checker&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">35&lt;/span>&lt;span class="cl"> &lt;span class="n">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_priority&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">priority&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">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enabled&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">wrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">_checker_description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">description&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__doc__&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">func&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&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">return&lt;/span> &lt;span class="n">wrapper&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">decorator&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用方式：&lt;/p></description><content:encoded><![CDATA[<p>本案例基於 <code>.claude/lib/hook_validator.py</code> 的實際程式碼，展示如何用 Metaclass 實現檢查器的自動註冊。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_validator.py</code> 的 <code>HookValidator</code> 類別包含多個 <code>check_*</code> 方法，需要在 <code>validate_hook()</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"># 執行各項檢查 - 手動呼叫每個 check 方法</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;檢查 Hook 輸出格式&#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><strong>明確的執行順序</strong>：可以精確控制檢查項的執行順序</li>
<li><strong>容易理解呼叫流程</strong>：閱讀 <code>validate_hook()</code> 就知道會執行哪些檢查</li>
<li><strong>簡單直覺</strong>：不需要學習額外的抽象概念</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>新增檢查項時：</p>
<ol>
<li><strong>需要修改兩處程式碼</strong>：新增 <code>check_*</code> 方法 + 修改 <code>validate_hook()</code> 呼叫</li>
<li><strong>容易忘記註冊</strong>：新增方法後忘記在 <code>validate_hook()</code> 中呼叫</li>
<li><strong>無法動態控制</strong>：無法在執行時期啟用/停用特定檢查項</li>
<li><strong>難以擴展</strong>：子類別新增檢查項也需要覆寫 <code>validate_hook()</code></li>
</ol>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li>定義檢查方法時<strong>自動註冊</strong>到執行清單</li>
<li>支援<strong>優先順序控制</strong></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="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">check</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">priority</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">description</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"> 8</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    Decorator to mark a method as a checker.
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        priority: Execution order (lower runs first)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        enabled: Whether this check is enabled by default
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        description: Human-readable description
</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">        @check(priority=10, description=&#34;Validate filename format&#34;)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        def check_naming(self, hook_path):
</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">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="c1"># Store metadata on the function object</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="n">func</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</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">@wraps</span><span class="p">(</span><span class="n">func</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">wrapper</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">31</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">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="c1"># Copy metadata to wrapper</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</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">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</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="k">class</span> <span class="nc">Validator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check filename format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">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></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="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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check library imports&#34;</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">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="n">hook_path</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="o">...</span></span></span></code></pre></div><h4 id="步驟-2用-metaclass-收集標記的方法">步驟 2：用 Metaclass 收集標記的方法</h4>
<p>接下來，用 Metaclass 在類別建立時自動收集所有被 <code>@check</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">CheckerMeta</span><span class="p">(</span><span class="nb">type</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">    Metaclass that automatically collects methods marked with @check.
</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">    When a class is created, this metaclass:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">    1. Scans all methods for the _is_checker attribute
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    2. Collects them into a _checkers registry
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    3. Sorts by priority for execution order
</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">__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></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># Create the class first</span>
</span></span><span class="line"><span class="ln">13</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">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># Collect checkers from parent classes</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">inherited_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s1">&#39;_checkers&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                <span class="n">inherited_checkers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_checkers</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"># Collect checkers from current class</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="n">current_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</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">callable</span><span class="p">(</span><span class="n">attr_value</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</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 class="n">current_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_description</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="c1"># Merge: current class can override parent checkers</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">all_checkers</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">inherited_checkers</span><span class="p">,</span> <span class="o">**</span><span class="n">current_checkers</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"># Store as class attribute</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span> <span class="o">=</span> <span class="n">all_checkers</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">return</span> <span class="bp">cls</span></span></span></code></pre></div><h4 id="步驟-3實作優先順序">步驟 3：實作優先順序</h4>
<p>在 Metaclass 中已經收集了優先順序資訊，現在需要一個方法按順序執行：</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">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</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">    Base class for validators with auto-registration.
</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">    Provides run_all_checks() to execute registered checkers.
</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="k">def</span> <span class="nf">get_sorted_checkers</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></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        Get all enabled checkers sorted by priority.
</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">        Returns:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            List of (method_name, checker_info) tuples
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">checkers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">            <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</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="c1"># Sort by priority (lower number = higher priority)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">checkers</span><span class="p">,</span> <span class="n">key</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="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</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">run_all_checks</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">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">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">        Run all enabled checkers in priority order.
</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">        Args:
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            *args, **kwargs: Arguments passed to each checker
</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">            Combined list of issues from all checkers
</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="n">all_issues</span> <span class="o">=</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">for</span> <span class="n">method_name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_sorted_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">method_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">method</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">39</span><span class="cl">                <span class="k">if</span> <span class="n">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">40</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">41</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">                <span class="c1"># Optionally handle checker errors</span>
</span></span><span class="line"><span class="ln">43</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">44</span><span class="cl">                    <span class="s1">&#39;level&#39;</span><span class="p">:</span> <span class="s1">&#39;error&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">                    <span class="s1">&#39;message&#39;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;Checker </span><span class="si">{</span><span class="n">method_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">46</span><span class="cl">                <span class="p">})</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="k">return</span> <span class="n">all_issues</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="k">class</span> <span class="nc">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</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">    Base class for validators with auto-registration.
</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="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"> 7</span><span class="cl">        <span class="c1"># Instance-level override for checker states</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">_checker_overrides</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">def</span> <span class="nf">enable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_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">11</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        Enable a specific checker for this instance.
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">            checker_name: Name of the checker method
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">if</span> <span class="n">checker_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</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="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </span><span class="si">{</span><span class="n">checker_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">def</span> <span class="nf">disable_checker</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_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">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">        Disable a specific checker for this instance.
</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">            checker_name: Name of the checker method
</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 class="k">if</span> <span class="n">checker_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</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;Unknown checker: </span><span class="si">{</span><span class="n">checker_name</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</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">is_checker_enabled</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">checker_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">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="s2">        Check if a specific checker is enabled.
</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">        Instance overrides take precedence over class defaults.
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="k">if</span> <span class="n">checker_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</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="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">checker_name</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">checker_name</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;enabled&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">def</span> <span class="nf">get_sorted_checkers</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></span><span class="line"><span class="ln">43</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get all enabled checkers sorted by priority.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">        <span class="n">checkers</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="n">name</span><span class="p">,</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</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="k">return</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">checkers</span><span class="p">,</span> <span class="n">key</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="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checkers</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></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="s2">        List all available checkers with their status.
</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">            List of dicts with checker information
</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">result</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="nb">sorted</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">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">            <span class="n">key</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="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">                <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">],</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">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="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">Auto-registration pattern using Metaclass.
</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 demonstrates how to automatically register checker methods
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">using a combination of decorators and metaclasses.
</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">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</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="n">Callable</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="kn">import</span> <span class="nn">re</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"># ===== Data Classes =====</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">ValidationIssue</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Represents a validation issue.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 20</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"> 21</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"> 22</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"> 23</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"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 26</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"> 27</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validation result for a single target.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="n">target</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl">    <span class="n">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"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">is_valid</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"> 33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;True if no errors found.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">return</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">(</span><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 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"># ===== Decorator =====</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></span><span class="line"><span class="ln"> 39</span><span class="cl">    <span class="n">priority</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">enabled</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="n">description</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"> 42</span><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">    Decorator to mark a method as a checker.
</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">    Args:
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">        priority: Execution order (lower runs first)
</span></span></span><span class="line"><span class="ln"> 48</span><span class="cl"><span class="s2">        enabled: Whether this check is enabled by default
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl"><span class="s2">        description: Human-readable description
</span></span></span><span class="line"><span class="ln"> 50</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">:</span> <span class="n">Callable</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Callable</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">        <span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</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="o">**</span><span class="n">kwargs</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="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></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="c1"># Store metadata</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_is_checker</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_priority</span> <span class="o">=</span> <span class="n">priority</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_enabled</span> <span class="o">=</span> <span class="n">enabled</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">        <span class="n">wrapper</span><span class="o">.</span><span class="n">_checker_description</span> <span class="o">=</span> <span class="n">description</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__doc__</span> <span class="ow">or</span> <span class="n">func</span><span class="o">.</span><span class="vm">__name__</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">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="k">return</span> <span class="n">decorator</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"># ===== Metaclass =====</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">CheckerMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 69</span><span class="cl"><span class="s2">    Metaclass that automatically collects @check decorated methods.
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="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></span><span class="line"><span class="ln"> 73</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"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="c1"># Inherit checkers from parent classes</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="n">inherited_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s1">&#39;_checkers&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">                <span class="n">inherited_checkers</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_checkers</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="c1"># Collect checkers from current class</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">current_checkers</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</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="n">callable</span><span class="p">(</span><span class="n">attr_value</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                <span class="n">current_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr_value</span><span class="o">.</span><span class="n">_checker_description</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="c1"># Merge checkers</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span> <span class="o">=</span> <span class="p">{</span><span class="o">**</span><span class="n">inherited_checkers</span><span class="p">,</span> <span class="o">**</span><span class="n">current_checkers</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">return</span> <span class="bp">cls</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"># ===== Base Class =====</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">class</span> <span class="nc">CheckerBase</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">CheckerMeta</span><span class="p">):</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="s2">    Base class providing auto-registration functionality.
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="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">105</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">def</span> <span class="nf">enable_checker</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">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Enable a checker for this instance.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</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">_checkers</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="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown checker: </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">111</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</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">disable_checker</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">114</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Disable a checker for this instance.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</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">_checkers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">116</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;Unknown checker: </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">117</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</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">is_checker_enabled</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">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if a checker is enabled.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</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">_checker_overrides</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="bp">self</span><span class="o">.</span><span class="n">_checker_overrides</span><span class="p">[</span><span class="n">name</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="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;enabled&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">124</span><span class="cl">
</span></span><span class="line"><span class="ln">125</span><span class="cl">    <span class="k">def</span> <span class="nf">get_sorted_checkers</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></span><span class="line"><span class="ln">126</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Get enabled checkers sorted by priority.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">127</span><span class="cl">        <span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">            <span class="p">[(</span><span class="n">n</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">n</span><span class="p">,</span> <span class="n">i</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">()</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">n</span><span class="p">)],</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">            <span class="n">key</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="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">130</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">131</span><span class="cl">
</span></span><span class="line"><span class="ln">132</span><span class="cl">    <span class="k">def</span> <span class="nf">list_checkers</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></span><span class="line"><span class="ln">133</span><span class="cl">        <span class="s2">&#34;&#34;&#34;List all checkers with their status.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">134</span><span class="cl">        <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">135</span><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">                <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">137</span><span class="cl">                <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;priority&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">                <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_checker_enabled</span><span class="p">(</span><span class="n">name</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">139</span><span class="cl">                <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">info</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">140</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">            <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">info</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">142</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_checkers</span><span class="o">.</span><span class="n">items</span><span class="p">(),</span>
</span></span><span class="line"><span class="ln">143</span><span class="cl">                <span class="n">key</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="p">[</span><span class="mi">1</span><span class="p">][</span><span class="s1">&#39;priority&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">144</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="ln">146</span><span class="cl">
</span></span><span class="line"><span class="ln">147</span><span class="cl"><span class="c1"># ===== Implementation Example =====</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="k">class</span> <span class="nc">HookValidator</span><span class="p">(</span><span class="n">CheckerBase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">150</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">151</span><span class="cl"><span class="s2">    Hook validator with auto-registered checkers.
</span></span></span><span class="line"><span class="ln">152</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">153</span><span class="cl"><span class="s2">    Checkers are automatically discovered and executed in priority order.
</span></span></span><span class="line"><span class="ln">154</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">155</span><span class="cl">
</span></span><span class="line"><span class="ln">156</span><span class="cl">    <span class="c1"># Pattern constants</span>
</span></span><span class="line"><span class="ln">157</span><span class="cl">    <span class="n">VALID_NAME_PATTERN</span> <span class="o">=</span> <span class="sa">r</span><span class="s2">&#34;^[a-z0-9](/python-advanced/02-metaprogramming/case-studies/auto-registration/[a-z0-9\-_]*[a-z0-9])?\.py$&#34;</span>
</span></span><span class="line"><span class="ln">158</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">159</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">160</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">161</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">    <span class="n">BAD_OUTPUT_PATTERNS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">163</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">164</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">165</span><span class="cl">
</span></span><span class="line"><span class="ln">166</span><span class="cl">    <span class="k">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">167</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">168</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 class="k">if</span> <span class="n">project_root</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></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="k">def</span> <span class="nf">validate</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">171</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">172</span><span class="cl"><span class="s2">        Validate a hook file by running all enabled checkers.
</span></span></span><span class="line"><span class="ln">173</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">174</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">175</span><span class="cl"><span class="s2">            hook_path: Path to the hook file
</span></span></span><span class="line"><span class="ln">176</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">177</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">178</span><span class="cl"><span class="s2">            ValidationResult with all issues found
</span></span></span><span class="line"><span class="ln">179</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">180</span><span class="cl">        <span class="n">path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">hook_path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">path</span><span class="o">.</span><span class="n">is_absolute</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">182</span><span class="cl">            <span class="n">path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="n">path</span>
</span></span><span class="line"><span class="ln">183</span><span class="cl">
</span></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="c1"># Read file content</span>
</span></span><span class="line"><span class="ln">185</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">186</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="s1">&#39;utf-8&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">187</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">188</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">189</span><span class="cl">                <span class="n">target</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">190</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">191</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">192</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Cannot read 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">193</span><span class="cl">                <span class="p">)]</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="c1"># Prepare context for checkers</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="n">context</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">198</span><span class="cl">            <span class="s1">&#39;path&#39;</span><span class="p">:</span> <span class="n">path</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">199</span><span class="cl">            <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">            <span class="s1">&#39;filename&#39;</span><span class="p">:</span> <span class="n">path</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">        <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"># Run all enabled checkers</span>
</span></span><span class="line"><span class="ln">204</span><span class="cl">        <span class="n">all_issues</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">205</span><span class="cl">        <span class="k">for</span> <span class="n">method_name</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_sorted_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">method_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">208</span><span class="cl">                <span class="n">issues</span> <span class="o">=</span> <span class="n">method</span><span class="p">(</span><span class="n">context</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">issues</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">210</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">211</span><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">212</span><span class="cl">                <span class="n">all_issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">213</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">214</span><span class="cl">                    <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Checker &#39;</span><span class="si">{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">&#39; crashed: </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">215</span><span class="cl">                <span class="p">))</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">
</span></span><span class="line"><span class="ln">217</span><span class="cl">        <span class="k">return</span> <span class="n">ValidationResult</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">),</span> <span class="n">issues</span><span class="o">=</span><span class="n">all_issues</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check filename format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</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">ctx</span><span class="p">:</span> <span class="nb">dict</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">221</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate hook filename follows naming convention.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">222</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">223</span><span class="cl">        <span class="n">filename</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;filename&#39;</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="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="bp">self</span><span class="o">.</span><span class="n">VALID_NAME_PATTERN</span><span class="p">,</span> <span class="n">filename</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">227</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">228</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Filename &#39;</span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="s2">&#39; doesn&#39;t follow naming convention&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">229</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Use snake_case or kebab-case, e.g., check_format.py&#34;</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">
</span></span><span class="line"><span class="ln">232</span><span class="cl">        <span class="k">return</span> <span class="n">issues</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check hook_io import&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">235</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">ctx</span><span class="p">:</span> <span class="nb">dict</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">236</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if hook_io module is properly imported.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">237</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">238</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</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="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">241</span><span class="cl">            <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</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">243</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl">        <span class="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">246</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">247</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">248</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;hook_io module not imported&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">249</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">250</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">
</span></span><span class="line"><span class="ln">252</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">253</span><span class="cl">
</span></span><span class="line"><span class="ln">254</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check output format&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">255</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">ctx</span><span class="p">:</span> <span class="nb">dict</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">256</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if proper output functions are used.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">257</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">258</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">
</span></span><span class="line"><span class="ln">260</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_OUTPUT_PATTERNS</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">261</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">content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">262</span><span class="cl">                <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">263</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">264</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">265</span><span class="cl">                    <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Replace with: write_hook_output(output_dict)&#34;</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 class="k">break</span>
</span></span><span class="line"><span class="ln">268</span><span class="cl">
</span></span><span class="line"><span class="ln">269</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">270</span><span class="cl">
</span></span><span class="line"><span class="ln">271</span><span class="cl">    <span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">40</span><span class="p">,</span> <span class="n">enabled</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check test file exists&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">272</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">ctx</span><span class="p">:</span> <span class="nb">dict</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">273</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if corresponding test file exists.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">274</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">275</span><span class="cl">        <span class="n">hook_name</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;path&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">stem</span>
</span></span><span class="line"><span class="ln">276</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">277</span><span class="cl">
</span></span><span class="line"><span class="ln">278</span><span class="cl">        <span class="n">test_path</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">project_root</span> <span class="o">/</span> <span class="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></span><span class="line"><span class="ln">279</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">test_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">280</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">281</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">282</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">283</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: </span><span class="si">{</span><span class="n">test_path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">284</span><span class="cl">            <span class="p">))</span>
</span></span><span class="line"><span class="ln">285</span><span class="cl">
</span></span><span class="line"><span class="ln">286</span><span class="cl">        <span class="k">return</span> <span class="n">issues</span>
</span></span><span class="line"><span class="ln">287</span><span class="cl">
</span></span><span class="line"><span class="ln">288</span><span class="cl"><span class="c1"># ===== Extended Example: Subclass =====</span>
</span></span><span class="line"><span class="ln">289</span><span class="cl">
</span></span><span class="line"><span class="ln">290</span><span class="cl"><span class="k">class</span> <span class="nc">StrictHookValidator</span><span class="p">(</span><span class="n">HookValidator</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">291</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">292</span><span class="cl"><span class="s2">    Extended validator with additional checks.
</span></span></span><span class="line"><span class="ln">293</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">294</span><span class="cl"><span class="s2">    Inherits all checks from HookValidator and adds more.
</span></span></span><span class="line"><span class="ln">295</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check shebang line&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">298</span><span class="cl">    <span class="k">def</span> <span class="nf">check_shebang</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="nb">dict</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">299</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if file starts with proper shebang.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">300</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">301</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">302</span><span class="cl">
</span></span><span class="line"><span class="ln">303</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">content</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;#!/usr/bin/env python&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">304</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">305</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">306</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Missing shebang line&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">307</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add: #!/usr/bin/env python3&#34;</span>
</span></span><span class="line"><span class="ln">308</span><span class="cl">            <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="k">return</span> <span class="n">issues</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">25</span><span class="p">,</span> <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Check docstring exists&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">313</span><span class="cl">    <span class="k">def</span> <span class="nf">check_docstring</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="nb">dict</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">314</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check if module has a docstring.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">315</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">316</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">ctx</span><span class="p">[</span><span class="s1">&#39;content&#39;</span><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="c1"># Simple check: look for triple quotes near the start</span>
</span></span><span class="line"><span class="ln">319</span><span class="cl">        <span class="n">lines</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)[:</span><span class="mi">10</span><span class="p">]</span>  <span class="c1"># First 10 lines</span>
</span></span><span class="line"><span class="ln">320</span><span class="cl">        <span class="n">has_docstring</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="s1">&#39;&#34;&#34;&#34;&#39;</span> <span class="ow">in</span> <span class="n">line</span> <span class="ow">or</span> <span class="s2">&#34;&#39;&#39;&#39;&#34;</span> <span class="ow">in</span> <span class="n">line</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">321</span><span class="cl">
</span></span><span class="line"><span class="ln">322</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">has_docstring</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">323</span><span class="cl">            <span class="n">issues</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">ValidationIssue</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">324</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">325</span><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Module docstring not found&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">326</span><span class="cl">                <span class="n">suggestion</span><span class="o">=</span><span class="s2">&#34;Add a docstring at the top of the file&#34;</span>
</span></span><span class="line"><span class="ln">327</span><span class="cl">            <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="k">return</span> <span class="n">issues</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="c1"># ===== Demo =====</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">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">334</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">335</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Auto-Registration Demo&#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">60</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="c1"># Create validator</span>
</span></span><span class="line"><span class="ln">339</span><span class="cl">    <span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">340</span><span class="cl">
</span></span><span class="line"><span class="ln">341</span><span class="cl">    <span class="c1"># List all registered checkers</span>
</span></span><span class="line"><span class="ln">342</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">[Registered Checkers]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">343</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">344</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</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;  [</span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> (priority: </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;priority&#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">346</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">checker</span><span class="p">[</span><span class="s1">&#39;description&#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">347</span><span class="cl">
</span></span><span class="line"><span class="ln">348</span><span class="cl">    <span class="c1"># Enable disabled checker</span>
</span></span><span class="line"><span class="ln">349</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">[Enable check_test_exists]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">350</span><span class="cl">    <span class="n">validator</span><span class="o">.</span><span class="n">enable_checker</span><span class="p">(</span><span class="s1">&#39;check_test_exists&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">351</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">352</span><span class="cl">        <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;check_test_exists&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">353</span><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</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="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</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">355</span><span class="cl">
</span></span><span class="line"><span class="ln">356</span><span class="cl">    <span class="c1"># Demo with extended validator</span>
</span></span><span class="line"><span class="ln">357</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">[Extended Validator - StrictHookValidator]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">358</span><span class="cl">    <span class="n">strict_validator</span> <span class="o">=</span> <span class="n">StrictHookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">359</span><span class="cl">    <span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">strict_validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">360</span><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;ON&#34;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s2">&#34;OFF&#34;</span>
</span></span><span class="line"><span class="ln">361</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">status</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2"> (priority: </span><span class="si">{</span><span class="n">checker</span><span class="p">[</span><span class="s1">&#39;priority&#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">362</span><span class="cl">
</span></span><span class="line"><span class="ln">363</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></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"># Create validator instance</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">HookValidator</span><span class="p">(</span><span class="n">project_root</span><span class="o">=</span><span class="s2">&#34;/path/to/project&#34;</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"># List available checkers</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">for</span> <span class="n">checker</span> <span class="ow">in</span> <span class="n">validator</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">():</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;[</span><span class="si">{</span><span class="s1">&#39;ON&#39;</span> <span class="k">if</span> <span class="n">checker</span><span class="p">[</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="k">else</span> <span class="s1">&#39;OFF&#39;</span><span class="si">}</span><span class="s2">] </span><span class="si">{</span><span class="n">checker</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"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># Output:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># [ON] check_naming_convention</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># [ON] check_lib_imports</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># [ON] check_output_format</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># [OFF] check_test_exists</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"># Enable/disable checkers dynamically</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">enable_checker</span><span class="p">(</span><span class="s1">&#39;check_test_exists&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">disable_checker</span><span class="p">(</span><span class="s1">&#39;check_output_format&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Validate a hook file</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">validator</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="s2">&#34;.claude/hooks/my-hook.py&#34;</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;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">21</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">22</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  [</span><span class="si">{</span><span class="n">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">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1"># Create extended validator with more checks</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">strict</span> <span class="o">=</span> <span class="n">StrictHookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Total checkers: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">strict</span><span class="o">.</span><span class="n">list_checkers</span><span class="p">())</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>  <span class="c1"># Inherits parent&#39;s checkers</span></span></span></code></pre></div><h2 id="替代方案__init_subclass__">替代方案：<code>__init_subclass__</code></h2>
<p>Python 3.6 引入了 <code>__init_subclass__</code>，可以在不使用 Metaclass 的情況下實現部分功能：</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">CheckerBase</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">    Base class using __init_subclass__ for auto-registration.
</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">    Simpler than metaclass, but less powerful.
</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">_checkers</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">__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></span><span class="line"><span class="ln">10</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">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="c1"># Collect checkers from this subclass</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="n">attr</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">,</span> <span class="kc">None</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="n">callable</span><span class="p">(</span><span class="n">attr</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">attr</span><span class="p">,</span> <span class="s1">&#39;_is_checker&#39;</span><span class="p">,</span> <span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                <span class="bp">cls</span><span class="o">.</span><span class="n">_checkers</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">attr_name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                    <span class="s1">&#39;priority&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_priority</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                    <span class="s1">&#39;enabled&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_enabled</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                    <span class="s1">&#39;description&#39;</span><span class="p">:</span> <span class="n">attr</span><span class="o">.</span><span class="n">_checker_description</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">class</span> <span class="nc">HookValidator</span><span class="p">(</span><span class="n">CheckerBase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Validator using __init_subclass__.&#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">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">def</span> <span class="nf">check_naming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check naming convention.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">pass</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">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">def</span> <span class="nf">check_imports</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Check imports.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h3 id="__init_subclass__-vs-metaclass"><code>__init_subclass__</code> vs Metaclass</h3>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th><code>__init_subclass__</code></th>
          <th>Metaclass</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>複雜度</td>
          <td>低</td>
          <td>高</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>子類別註冊</td>
          <td>完整控制類別建立</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>較好</td>
          <td>略差</td>
      </tr>
      <tr>
          <td>修改類別</td>
          <td>有限</td>
          <td>完整</td>
      </tr>
      <tr>
          <td>繼承處理</td>
          <td>需手動</td>
          <td>自動</td>
      </tr>
      <tr>
          <td>推薦情況</td>
          <td>優先使用</td>
          <td>需要進階功能時</td>
      </tr>
  </tbody>
</table>
<p><strong>原則</strong>：如果 <code>__init_subclass__</code> 能滿足需求，優先使用它。</p>
<h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>手動註冊</th>
          <th>Metaclass 自動註冊</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>程式碼重複</td>
          <td>高（每次新增都要改兩處）</td>
          <td>低（只需加裝飾器）</td>
      </tr>
      <tr>
          <td>理解難度</td>
          <td>低</td>
          <td>中（需理解 Metaclass）</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>由 priority 決定</td>
      </tr>
  </tbody>
</table>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p>適合使用：</p>
<ul>
<li><strong>插件系統</strong>：需要自動發現和載入插件</li>
<li><strong>框架開發</strong>：Django admin、pytest fixtures 等</li>
<li><strong>大量相似元件</strong>：多個檢查器/處理器需統一管理</li>
<li><strong>需要動態控制</strong>：執行時期啟用/停用功能</li>
</ul>
<p>不建議使用：</p>
<ul>
<li><strong>只有少數幾個檢查項</strong>：手動維護更簡單</li>
<li><strong>團隊不熟悉 Metaclass</strong>：增加維護負擔</li>
<li><strong>簡單的應用程式</strong>：過度工程</li>
<li><strong>執行順序非常重要</strong>：手動呼叫更明確</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="k">class</span> <span class="nc">CommandRegistry</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">    1. 用 @command(name=&#34;xxx&#34;) 裝飾器註冊命令
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    2. 提供 execute(name, *args) 執行指定命令
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    3. 提供 list_commands() 列出所有命令
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="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"># 使用範例</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">MyApp</span><span class="p">(</span><span class="n">CommandRegistry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nd">@command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;greet&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">def</span> <span class="nf">say_hello</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</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">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nd">@command</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;add&#34;</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">add_numbers</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</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">a</span> <span class="o">+</span> <span class="n">b</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">MyApp</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">app</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">&#34;greet&#34;</span><span class="p">,</span> <span class="s2">&#34;World&#34;</span><span class="p">))</span>  <span class="c1"># Hello, World!</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">app</span><span class="o">.</span><span class="n">list_commands</span><span class="p">())</span>  <span class="c1"># [&#39;add&#39;, &#39;greet&#39;]</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="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">depends_on</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;check_file_exists&#39;</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="nf">check_content</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</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></code></pre></div><p>提示：</p>
<ol>
<li>在 <code>@check</code> 裝飾器加入 <code>depends_on</code> 參數</li>
<li>在 <code>run_all_checks()</code> 中追蹤每個檢查項的結果</li>
<li>檢查相依項是否通過再執行</li>
</ol>
<h3 id="挑戰題">挑戰題</h3>
<p>實作跨模組的檢查項發現（類似 pytest）：</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"># checkers/naming.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</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">def</span> <span class="nf">check_naming</span><span class="p">(</span><span class="n">ctx</span><span class="p">):</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="c1"># checkers/imports.py</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nd">@check</span><span class="p">(</span><span class="n">priority</span><span class="o">=</span><span class="mi">20</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">check_imports</span><span class="p">(</span><span class="n">ctx</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</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"># main.py</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">validator</span> <span class="o">=</span> <span class="n">HookValidator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">validator</span><span class="o">.</span><span class="n">discover_checkers</span><span class="p">(</span><span class="s1">&#39;checkers/&#39;</span><span class="p">)</span>  <span class="c1"># 自動載入目錄下的所有檢查項</span></span></span></code></pre></div><p>提示：</p>
<ol>
<li>使用 <code>importlib</code> 動態載入模組</li>
<li>掃描模組中帶有 <code>_is_checker</code> 標記的函式</li>
<li>將函式綁定為實例方法</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#metaclasses">Python Metaclass 官方文件</a></li>
<li><a href="https://github.com/django/django/blob/main/django/db/models/base.py">Django Model 的 Metaclass 實作</a></li>
<li><a href="https://peps.python.org/pep-0487/">PEP 487 &ndash; <code>__init_subclass__</code></a></li>
<li><a href="https://docs.pytest.org/en/latest/how-to/writing_plugins.html">pytest 插件發現機制</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field</a></em></p>
]]></content:encoded></item><item><title>2.2 Metaclass 設計與應用</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/</guid><description>&lt;p>Metaclass 是「類別的類別」，控制類別本身的建立過程。這是 Python 中最深層的元編程機制。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol&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>使用 Metaclass 控制類別行為&lt;/li>
&lt;li>選擇適當的元編程工具&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="原理層類別也是物件">【原理層】類別也是物件&lt;/h2>
&lt;h3 id="type-是所有類別的-metaclass">type 是所有類別的 Metaclass&lt;/h3>
&lt;p>在 Python 中，類別本身也是物件：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyClass&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="c1"># 類別是 type 的實例&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="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MyClass&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># &amp;lt;class &amp;#39;type&amp;#39;&amp;gt;&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="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MyClass&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># True&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">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># &amp;lt;class &amp;#39;__main__.MyClass&amp;#39;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MyClass&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># True&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">type ─────────────┐
&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"> ├── MyClass ────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> │ │ │ (都是 type 的實例)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> │ └── obj │
&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"> └── int ────────┘
&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"> └── 42&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="type-的三參數形式">type() 的三參數形式&lt;/h3>
&lt;p>&lt;code>type()&lt;/code> 可以動態建立類別：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 這兩種方式等價&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyClass&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">x&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">method&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"> 5&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">x&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">def&lt;/span> &lt;span class="nf">method&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="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&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">MyClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;MyClass&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(),&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;x&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;method&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">method&lt;/span>&lt;span class="p">})&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>參數說明：&lt;/p>
&lt;ul>
&lt;li>第一個參數：類別名稱&lt;/li>
&lt;li>第二個參數：父類別的 tuple&lt;/li>
&lt;li>第三個參數：屬性和方法的 dict&lt;/li>
&lt;/ul>





&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">Parent&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">def&lt;/span> &lt;span class="nf">greet&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;Hello&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="n">Child&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Child&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">Parent&lt;/span>&lt;span class="p">,),&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;Child Class&amp;#39;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Child&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">greet&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># Hello&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Child Class&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;p>當 Python 執行 &lt;code>class&lt;/code> 語句時，會依序呼叫這些方法：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">class MyClass(Parent, metaclass=Meta):
&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">1. Meta.__prepare__(name, bases) → 返回類別的 namespace（通常是 dict）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">2. 執行類別主體，填充 namespace
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">3. Meta.__new__(mcs, name, bases, namespace) → 建立類別物件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">4. Meta.__init__(cls, name, bases, namespace) → 初始化類別物件&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&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">LoggingMeta&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"> 2&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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__prepare__&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;1. __prepare__: 準備 &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"> 的 namespace&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="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="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"> 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;2. __new__: 建立 &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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&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">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">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="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">12&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;3. __init__: 初始化 &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">13&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">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">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">MyClass&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">LoggingMeta&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">x&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">17&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">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"># 輸出：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="c1"># 1. __prepare__: 準備 MyClass 的 namespace&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="c1"># （執行類別主體）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="c1"># 2. __new__: 建立 MyClass&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"># 3. __init__: 初始化 MyClass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="call-控制實例建立">&lt;strong>call&lt;/strong> 控制實例建立&lt;/h3>
&lt;p>Metaclass 的 &lt;code>__call__&lt;/code> 控制類別被呼叫時的行為：&lt;/p></description><content:encoded><![CDATA[<p>Metaclass 是「類別的類別」，控制類別本身的建立過程。這是 Python 中最深層的元編程機制。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>理解「類別也是物件」的概念</li>
<li>理解類別的建立流程</li>
<li>使用 Metaclass 控制類別行為</li>
<li>選擇適當的元編程工具</li>
</ol>
<hr>
<h2 id="原理層類別也是物件">【原理層】類別也是物件</h2>
<h3 id="type-是所有類別的-metaclass">type 是所有類別的 Metaclass</h3>
<p>在 Python 中，類別本身也是物件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</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="c1"># 類別是 type 的實例</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="nb">type</span><span class="p">(</span><span class="n">MyClass</span><span class="p">))</span>  <span class="c1"># &lt;class &#39;type&#39;&gt;</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="nb">isinstance</span><span class="p">(</span><span class="n">MyClass</span><span class="p">,</span> <span class="nb">type</span><span class="p">))</span>  <span class="c1"># True</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">obj</span> <span class="o">=</span> <span class="n">MyClass</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="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">))</span>  <span class="c1"># &lt;class &#39;__main__.MyClass&#39;&gt;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">MyClass</span><span class="p">))</span>  <span class="c1"># True</span></span></span></code></pre></div>




<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">type ─────────────┐
</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">  ├── MyClass ────┤
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  │     │         │ (都是 type 的實例)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  │     └── obj   │
</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">  └── int ────────┘
</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">        └── 42</span></span></code></pre></div><h3 id="type-的三參數形式">type() 的三參數形式</h3>
<p><code>type()</code> 可以動態建立類別：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 這兩種方式等價</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="bp">self</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 class="o">.</span><span class="n">x</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">method</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="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</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">MyClass</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="s1">&#39;MyClass&#39;</span><span class="p">,</span> <span class="p">(),</span> <span class="p">{</span><span class="s1">&#39;x&#39;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">method</span><span class="p">})</span></span></span></code></pre></div><p>參數說明：</p>
<ul>
<li>第一個參數：類別名稱</li>
<li>第二個參數：父類別的 tuple</li>
<li>第三個參數：屬性和方法的 dict</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"># 繼承的例子</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">Parent</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="bp">self</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="s2">&#34;Hello&#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="n">Child</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="s1">&#39;Child&#39;</span><span class="p">,</span> <span class="p">(</span><span class="n">Parent</span><span class="p">,),</span> <span class="p">{</span><span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="s1">&#39;Child Class&#39;</span><span class="p">})</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">c</span> <span class="o">=</span> <span class="n">Child</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">c</span><span class="o">.</span><span class="n">greet</span><span class="p">())</span>  <span class="c1"># Hello</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">c</span><span class="o">.</span><span class="n">name</span><span class="p">)</span>     <span class="c1"># Child Class</span></span></span></code></pre></div><hr>
<h2 id="設計層類別建立過程">【設計層】類別建立過程</h2>
<h3 id="建立流程">建立流程</h3>
<p>當 Python 執行 <code>class</code> 語句時，會依序呼叫這些方法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">class MyClass(Parent, metaclass=Meta):
</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">1. Meta.__prepare__(name, bases) → 返回類別的 namespace（通常是 dict）
</span></span><span class="line"><span class="ln">6</span><span class="cl">2. 執行類別主體，填充 namespace
</span></span><span class="line"><span class="ln">7</span><span class="cl">3. Meta.__new__(mcs, name, bases, namespace) → 建立類別物件
</span></span><span class="line"><span class="ln">8</span><span class="cl">4. Meta.__init__(cls, name, bases, namespace) → 初始化類別物件</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="k">class</span> <span class="nc">LoggingMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="fm">__prepare__</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></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;1. __prepare__: 準備 </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> 的 namespace&#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="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="n">bases</span><span class="p">,</span> <span class="n">namespace</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;2. __new__: 建立 </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"> 9</span><span class="cl">        <span class="k">return</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">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">cls</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">12</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;3. __init__: 初始化 </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">13</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">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">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">MyClass</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">LoggingMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">x</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="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">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># 輸出：</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># 1. __prepare__: 準備 MyClass 的 namespace</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">#    （執行類別主體）</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># 2. __new__: 建立 MyClass</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1"># 3. __init__: 初始化 MyClass</span></span></span></code></pre></div><h3 id="call-控制實例建立"><strong>call</strong> 控制實例建立</h3>
<p>Metaclass 的 <code>__call__</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">SingletonMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">_instances</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">cls</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"> 5</span><span class="cl">        <span class="k">if</span> <span class="bp">cls</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__call__</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"> 7</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">[</span><span class="bp">cls</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">class</span> <span class="nc">Singleton</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">SingletonMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="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></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="n">s1</span> <span class="o">=</span> <span class="n">Singleton</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">s2</span> <span class="o">=</span> <span class="n">Singleton</span><span class="p">(</span><span class="mi">2</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">s1</span> <span class="ow">is</span> <span class="n">s2</span><span class="p">)</span>  <span class="c1"># True</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">s1</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># 1（第二次初始化被跳過）</span></span></span></code></pre></div><hr>
<h2 id="設計層init_---輕量替代方案">【設計層】<strong>init_subclass</strong> - 輕量替代方案</h2>
<p>Python 3.6 引入了 <code>__init_subclass__</code>，很多情況下可以替代 Metaclass：</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">Plugin</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</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"> 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">__init_subclass__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">plugin_name</span><span class="o">=</span><span class="kc">None</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"> 5</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"> 6</span><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="n">plugin_name</span> <span class="ow">or</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="bp">cls</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="bp">cls</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">name</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="k">class</span> <span class="nc">EmailPlugin</span><span class="p">(</span><span class="n">Plugin</span><span class="p">,</span> <span class="n">plugin_name</span><span class="o">=</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">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="k">class</span> <span class="nc">SMSPlugin</span><span class="p">(</span><span class="n">Plugin</span><span class="p">,</span> <span class="n">plugin_name</span><span class="o">=</span><span class="s2">&#34;sms&#34;</span><span class="p">):</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="nb">print</span><span class="p">(</span><span class="n">Plugin</span><span class="o">.</span><span class="n">_plugins</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># {&#39;email&#39;: &lt;class &#39;EmailPlugin&#39;&gt;, &#39;sms&#39;: &lt;class &#39;SMSPlugin&#39;&gt;}</span></span></span></code></pre></div><h3 id="何時用-init_何時用-metaclass">何時用 <strong>init_subclass</strong>，何時用 Metaclass？</h3>
<table>
  <thead>
      <tr>
          <th>需求</th>
          <th>推薦方案</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>註冊子類別</td>
          <td><code>__init_subclass__</code></td>
      </tr>
      <tr>
          <td>驗證類別屬性</td>
          <td><code>__init_subclass__</code></td>
      </tr>
      <tr>
          <td>修改類別的 namespace</td>
          <td>Metaclass（<code>__prepare__</code>）</td>
      </tr>
      <tr>
          <td>控制實例建立</td>
          <td>Metaclass（<code>__call__</code>）</td>
      </tr>
      <tr>
          <td>修改類別建立過程</td>
          <td>Metaclass</td>
      </tr>
      <tr>
          <td>需要影響多層繼承</td>
          <td>Metaclass</td>
      </tr>
  </tbody>
</table>
<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="k">class</span> <span class="nc">Registry</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">_registry</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">__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></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">        <span class="k">if</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span> <span class="o">!=</span> <span class="s1">&#39;Base&#39;</span><span class="p">:</span>  <span class="c1"># 跳過中間類別</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">Registry</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="vm">__name__</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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">@classmethod</span>
</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">cls</span><span class="p">,</span> <span class="n">name</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">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">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">create</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="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">15</span><span class="cl">        <span class="n">klass</span> <span class="o">=</span> <span class="bp">cls</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">16</span><span class="cl">        <span class="k">if</span> <span class="n">klass</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">klass</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="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown type: </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">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">Handler</span><span class="p">(</span><span class="n">Registry</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">pass</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">JSONHandler</span><span class="p">(</span><span class="n">Handler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</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></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Processing JSON: </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">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">XMLHandler</span><span class="p">(</span><span class="n">Handler</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">28</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></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Processing XML: </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">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="n">handler</span> <span class="o">=</span> <span class="n">Registry</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;JSONHandler&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">handler</span><span class="o">.</span><span class="n">process</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">))</span>  <span class="c1"># Processing JSON: 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="k">class</span> <span class="nc">InterfaceMeta</span><span class="p">(</span><span class="nb">type</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">__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"> 3</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"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="c1"># 跳過基類本身</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="n">bases</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="n">required</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">&#39;_required_methods&#39;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="k">for</span> <span class="n">method</span> <span class="ow">in</span> <span class="n">required</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">method</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">namespace</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">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                        <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">method</span><span class="si">}</span><span class="s2"> 方法&#34;</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 class="k">return</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="k">class</span> <span class="nc">Serializable</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">InterfaceMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">_required_methods</span> <span class="o">=</span> <span class="p">[</span><span class="s1">&#39;serialize&#39;</span><span class="p">,</span> <span class="s1">&#39;deserialize&#39;</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"># 這會引發 TypeError</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1"># class BadSerializer(Serializable):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">#     pass</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">GoodSerializer</span><span class="p">(</span><span class="n">Serializable</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</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="nb">str</span><span class="p">(</span><span class="n">obj</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">deserialize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</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="nb">eval</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div><h3 id="使用-prepare-記錄定義順序">使用 <strong>prepare</strong> 記錄定義順序</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">collections</span> <span class="kn">import</span> <span class="n">OrderedDict</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">OrderedMeta</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__prepare__</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></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="n">OrderedDict</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">__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 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="nb">dict</span><span class="p">(</span><span class="n">namespace</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_field_order</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="n">k</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">keys</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="ow">not</span> <span class="n">k</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;_&#39;</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 class="k">return</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="k">class</span> <span class="nc">Form</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">OrderedMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;text&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="s2">&#34;email&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">age</span> <span class="o">=</span> <span class="s2">&#34;number&#34;</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="nb">print</span><span class="p">(</span><span class="n">Form</span><span class="o">.</span><span class="n">_field_order</span><span class="p">)</span>  <span class="c1"># [&#39;name&#39;, &#39;email&#39;, &#39;age&#39;]</span></span></span></code></pre></div><h3 id="簡單的-orm-基類">簡單的 ORM 基類</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">Field</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">CharField</span><span class="p">(</span><span class="n">Field</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="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></span><span class="line"><span class="ln"> 6</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"> 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">IntegerField</span><span class="p">(</span><span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</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">ModelMeta</span><span class="p">(</span><span class="nb">type</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="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">13</span><span class="cl">        <span class="c1"># 收集欄位</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">fields</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">namespace</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">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">                <span class="n">fields</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</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">20</span><span class="cl">        <span class="bp">cls</span><span class="o">.</span><span class="n">_fields</span> <span class="o">=</span> <span class="n">fields</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</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">Model</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">ModelMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</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">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="nb">setattr</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">kwargs</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">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="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">fields</span> <span class="o">=</span> <span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <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="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span><span class="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</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="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="vm">__class__</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">fields</span><span class="si">}</span><span class="s2">)&#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">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">Model</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="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">age</span> <span class="o">=</span> <span class="n">IntegerField</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="n">u</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">age</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>  <span class="c1"># User(name=&#39;Alice&#39;, age=30)</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="n">User</span><span class="o">.</span><span class="n">_fields</span><span class="p">)</span>  <span class="c1"># {&#39;name&#39;: CharField, &#39;age&#39;: IntegerField}</span></span></span></code></pre></div><hr>
<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">│   └── 用 __init_subclass__
</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">│   ├── 簡單驗證 → __init_subclass__
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">│   └── 需要修改 namespace → Metaclass
</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">│   ├── 簡單快取 → __new__
</span></span><span class="line"><span class="ln">12</span><span class="cl">│   └── 複雜邏輯 → Metaclass.__call__
</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">│   └── Metaclass.__prepare__
</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></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>type(type)</code> 是 <code>type</code> 自己？這是如何實現的？</li>
<li>如果一個類別同時有 Metaclass 和 <code>__init_subclass__</code>，執行順序是什麼？</li>
<li>Django 的 Model 是如何使用 Metaclass 的？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>實作一個 Metaclass，自動為類別添加 <code>__repr__</code> 方法</li>
<li>實作一個 <code>@abstractmethod</code> 裝飾器和配套的 Metaclass</li>
<li>使用 <code>__init_subclass__</code> 實作一個事件處理器註冊系統</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#customizing-class-creation">Python 官方 - Data Model</a></li>
<li><a href="https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/">Ionel MC - Understanding Python Metaclasses</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">Descriptor Protocol 完整指南</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">類別裝飾器與動態類別</a></em></p>
]]></content:encoded></item><item><title>模組二：元編程</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/</guid><description>&lt;p>元編程（Metaprogramming）是指撰寫可以操作程式碼的程式碼。Python 提供了豐富的元編程機制，包括 Descriptor、Metaclass、類別裝飾器等。&lt;/p>
&lt;h2 id="你已經在使用元編程">你已經在使用元編程&lt;/h2>
&lt;p>如果你完成了入門系列，你其實已經接觸過元編程了：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 1. @property - Descriptor Protocol 的應用&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">User&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="nd">@property&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">full_name&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"> 5&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">first&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">last&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"> 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"># 2. @dataclass - 類別裝飾器 + 元類別技術&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&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"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">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="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">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">30&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"># 3. ABC - 元類別控制子類別行為&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">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">15&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Parser&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">16&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">17&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse&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="nb">dict&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">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這些都是元編程的入門應用。本模組將深入探討它們背後的原理，以及如何創建自己的元編程工具。&lt;/p>
&lt;h2 id="為什麼學習元編程">為什麼學習元編程？&lt;/h2>
&lt;p>理解元編程機制有多重好處：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>理解框架&lt;/strong>：Django Model、SQLAlchemy、Pydantic 都大量使用元編程&lt;/li>
&lt;li>&lt;strong>建立抽象&lt;/strong>：可以設計更優雅的 API&lt;/li>
&lt;li>&lt;strong>深入 Python&lt;/strong>：理解 Python 物件模型的本質&lt;/li>
&lt;/ul>
&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/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1&lt;/a>&lt;/td>
 &lt;td>Descriptor Protocol 完整指南&lt;/td>
 &lt;td>理解 @property 的本質&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2&lt;/a>&lt;/td>
 &lt;td>Metaclass 設計與應用&lt;/td>
 &lt;td>控制類別的建立過程&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3&lt;/a>&lt;/td>
 &lt;td>類別裝飾器與動態類別&lt;/td>
 &lt;td>@dataclass 的實現原理&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/introspection/" data-link-title="2.4 反射與 inspect 模組" data-link-desc="使用反射和 inspect 模組檢視和操作 Python 物件">2.4&lt;/a>&lt;/td>
 &lt;td>反射與 inspect 模組&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/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Descriptor Protocol&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Metaclass&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>Descriptor + dataclass&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>入門系列 &lt;a href="https://tarrragon.github.io/blog/python/04-oop/singleton-cache/" data-link-title="4.4 單例與快取模式" data-link-desc="控制物件生命週期">4.4 單例與快取&lt;/a>&lt;/li>
&lt;li>熟悉 Python 的類別與物件&lt;/li>
&lt;/ul>
&lt;h2 id="何時使用元編程">何時使用元編程？&lt;/h2>
&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;/td>
 &lt;td>先考慮 @property&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要修改類別&lt;/td>
 &lt;td>先考慮類別裝飾器&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要控制子類別&lt;/td>
 &lt;td>先考慮 &lt;strong>init_subclass&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>需要深度定制&lt;/td>
 &lt;td>才考慮 Metaclass&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>原則&lt;/strong>：能用簡單方案解決就不用複雜方案。&lt;/p>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>每章節約 30-45 分鐘，全模組約 2-3 小時&lt;/p>
&lt;hr>
&lt;p>&lt;em>上一模組：&lt;a href="https://tarrragon.github.io/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計&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>元編程（Metaprogramming）是指撰寫可以操作程式碼的程式碼。Python 提供了豐富的元編程機制，包括 Descriptor、Metaclass、類別裝飾器等。</p>
<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="c1"># 1. @property - Descriptor Protocol 的應用</span>
</span></span><span class="line"><span class="ln"> 2</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"> 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">full_name</span><span class="p">(</span><span class="bp">self</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="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">first</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">last</span><span class="si">}</span><span class="s2">&#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. @dataclass - 類別裝飾器 + 元類別技術</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">Config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</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">11</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">30</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 3. ABC - 元類別控制子類別行為</span>
</span></span><span class="line"><span class="ln">14</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">15</span><span class="cl"><span class="k">class</span> <span class="nc">Parser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><p>這些都是元編程的入門應用。本模組將深入探討它們背後的原理，以及如何創建自己的元編程工具。</p>
<h2 id="為什麼學習元編程">為什麼學習元編程？</h2>
<p>理解元編程機制有多重好處：</p>
<ul>
<li><strong>理解框架</strong>：Django Model、SQLAlchemy、Pydantic 都大量使用元編程</li>
<li><strong>建立抽象</strong>：可以設計更優雅的 API</li>
<li><strong>深入 Python</strong>：理解 Python 物件模型的本質</li>
</ul>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1</a></td>
          <td>Descriptor Protocol 完整指南</td>
          <td>理解 @property 的本質</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2</a></td>
          <td>Metaclass 設計與應用</td>
          <td>控制類別的建立過程</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3</a></td>
          <td>類別裝飾器與動態類別</td>
          <td>@dataclass 的實現原理</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/introspection/" data-link-title="2.4 反射與 inspect 模組" data-link-desc="使用反射和 inspect 模組檢視和操作 Python 物件">2.4</a></td>
          <td>反射與 inspect 模組</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/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證</a></td>
          <td>hook_validator.py</td>
          <td>Descriptor Protocol</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a></td>
          <td>hook_validator.py</td>
          <td>Metaclass</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field</a></td>
          <td>hook_io.py</td>
          <td>Descriptor + dataclass</td>
      </tr>
  </tbody>
</table>
<h2 id="先備知識">先備知識</h2>
<ul>
<li>入門系列 <a href="/blog/python/04-oop/singleton-cache/" data-link-title="4.4 單例與快取模式" data-link-desc="控制物件生命週期">4.4 單例與快取</a></li>
<li>熟悉 Python 的類別與物件</li>
</ul>
<h2 id="何時使用元編程">何時使用元編程？</h2>
<p>元編程強大但複雜，使用前請考慮：</p>
<table>
  <thead>
      <tr>
          <th>場景</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>需要驗證屬性</td>
          <td>先考慮 @property</td>
      </tr>
      <tr>
          <td>需要修改類別</td>
          <td>先考慮類別裝飾器</td>
      </tr>
      <tr>
          <td>需要控制子類別</td>
          <td>先考慮 <strong>init_subclass</strong></td>
      </tr>
      <tr>
          <td>需要深度定制</td>
          <td>才考慮 Metaclass</td>
      </tr>
  </tbody>
</table>
<p><strong>原則</strong>：能用簡單方案解決就不用複雜方案。</p>
<h2 id="學習時間">學習時間</h2>
<p>每章節約 30-45 分鐘，全模組約 2-3 小時</p>
<hr>
<p><em>上一模組：<a href="/blog/python-advanced/01-asyncio/" data-link-title="模組一：非同步程式設計（asyncio）" data-link-desc="Python 的異步程式設計模型，掌握現代 Web/網路開發的必備技能">模組一：非同步程式設計</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>案例：類似 Django Field 的設計</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/</guid><description>&lt;p>本案例基於 &lt;code>.claude/lib/hook_io.py&lt;/code> 的實際程式碼，展示如何結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API。&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/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="問題背景">問題背景&lt;/h2>
&lt;h3 id="現有設計">現有設計&lt;/h3>
&lt;p>&lt;code>hook_io.py&lt;/code> 使用函式工廠模式建構 Hook 輸出：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_pretooluse_output&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">decision&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"> 3&lt;/span>&lt;span class="cl"> &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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">user_prompt&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">system_message&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="n">suppress_output&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&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"> 8&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"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2"> 建立 PreToolUse 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">
&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"> Args:
&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"> decision: 決策結果 (&amp;#34;allow&amp;#34; | &amp;#34;deny&amp;#34; | &amp;#34;ask&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="s2"> reason: 決策原因說明
&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"> user_prompt: 詢問用戶的訊息（僅當 decision 為 &amp;#34;ask&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 class="s2"> system_message: 系統訊息（可選）
&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"> suppress_output: 是否抑制輸出（預設 False）
&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"> Returns:
&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"> dict: 標準 PreToolUse Hook 輸出格式
&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="n">output&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">22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;hookSpecificOutput&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="s2">&amp;#34;hookEventName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;PreToolUse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;permissionDecision&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">decision&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;permissionDecisionReason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">reason&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &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="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="k">if&lt;/span> &lt;span class="n">user_prompt&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">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;hookSpecificOutput&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;userPrompt&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">user_prompt&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">if&lt;/span> &lt;span class="n">system_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="n">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;systemMessage&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">system_message&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">if&lt;/span> &lt;span class="n">suppress_output&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">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;suppressOutput&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">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">return&lt;/span> &lt;span class="n">output&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="這個設計的優點">這個設計的優點&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>清晰的建構流程&lt;/strong>：函式簽名清楚說明所需參數&lt;/li>
&lt;li>&lt;strong>支援可選參數&lt;/strong>：使用 &lt;code>Optional&lt;/code> 和預設值處理可選欄位&lt;/li>
&lt;li>&lt;strong>型別提示完整&lt;/strong>：有完整的參數型別標註&lt;/li>
&lt;/ul>
&lt;h3 id="這個設計的限制">這個設計的限制&lt;/h3>
&lt;p>當需要序列化/反序列化時：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>需要手動處理每個欄位&lt;/strong>：to_dict 和 from_dict 需要逐一處理&lt;/li>
&lt;li>&lt;strong>欄位定義與驗證分離&lt;/strong>：型別檢查和業務驗證在不同地方&lt;/li>
&lt;li>&lt;strong>難以生成文件或 schema&lt;/strong>：無法自動產生 JSON Schema 或 API 文件&lt;/li>
&lt;/ul>
&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;code>__init__&lt;/code>、&lt;code>to_dict&lt;/code>、&lt;code>from_dict&lt;/code>&lt;/strong>&lt;/li>
&lt;li>&lt;strong>支援巢狀結構&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h3 id="實作步驟">實作步驟&lt;/h3>
&lt;h4 id="步驟-1設計-field-基類">步驟 1：設計 Field 基類&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">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">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&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">TypeVar&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Callable&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">get_type_hints&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&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"> 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">Field&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"> 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"> Field Descriptor base class
&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"> Combines Django&amp;#39;s field declaration style with Python&amp;#39;s
&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"> descriptor protocol for type-safe, declarative model design.
&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="k">def&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">14&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">15&lt;/span>&lt;span class="cl"> &lt;span class="o">*&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">default&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">T&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">17&lt;/span>&lt;span class="cl"> &lt;span class="n">required&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">serialized_name&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">19&lt;/span>&lt;span class="cl"> &lt;span class="n">validator&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">Callable&lt;/span>&lt;span class="p">[[&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">bool&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">error_msg&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;Validation failed&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="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"> Args:
&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"> default: Default value when not provided
&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"> required: Whether field is required (default True)
&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"> serialized_name: Name used in serialization (default: attribute name)
&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"> validator: Optional validation function
&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"> error_msg: Error message for validation failure
&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;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">default&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">required&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">required&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">serialized_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">serialized_name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">error_msg&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"># Set by __set_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="bp">self&lt;/span>&lt;span class="o">.&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="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">38&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_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">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">__set_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="n">owner&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">&lt;span class="s2"> Called automatically when the descriptor is assigned to a class attribute.
&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"> This is where we get the attribute name from the class definition,
&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"> eliminating the need to pass it explicitly.
&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;&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">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">48&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">private_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;_field_&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>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="c1"># Use attribute name as serialized name if not specified&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">if&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">serialized_name&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">51&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">serialized_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">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">def&lt;/span> &lt;span class="fm">__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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">objtype&lt;/span>&lt;span class="p">:&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 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">54&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Return field value or descriptor itself if accessed on class.&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 class="k">if&lt;/span> &lt;span class="n">obj&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">56&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span> &lt;span class="c1"># Class access returns descriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">57&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&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">default&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">58&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">59&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__set__&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">obj&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Any&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">60&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Validate and set field value.&amp;#34;&amp;#34;&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="c1"># Handle None for optional fields&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">if&lt;/span> &lt;span class="n">value&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">63&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">required&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default&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">64&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;&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">: This field is required&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">65&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&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">default&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">return&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="c1"># Run custom validator if provided&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validator&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">70&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;&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">: &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">error_msg&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>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="nb">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&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">private_name&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">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">serialize&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&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">75&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Convert Python value to serializable format.&amp;#34;&amp;#34;&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="k">return&lt;/span> &lt;span class="n">value&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">deserialize&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">T&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;Convert serialized value to Python type.&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="k">return&lt;/span> &lt;span class="n">value&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_io.py</code> 的實際程式碼，展示如何結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">2.1 宣告式驗證</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別</a></li>
</ul>
<h2 id="問題背景">問題背景</h2>
<h3 id="現有設計">現有設計</h3>
<p><code>hook_io.py</code> 使用函式工廠模式建構 Hook 輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">create_pretooluse_output</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <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"> 3</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"> 4</span><span class="cl">    <span class="n">user_prompt</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">system_message</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"> 6</span><span class="cl">    <span class="n">suppress_output</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"> 7</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"> 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">    建立 PreToolUse Hook 輸出格式
</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">        decision: 決策結果 (&#34;allow&#34; | &#34;deny&#34; | &#34;ask&#34;)
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        reason: 決策原因說明
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        user_prompt: 詢問用戶的訊息（僅當 decision 為 &#34;ask&#34; 時使用）
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        system_message: 系統訊息（可選）
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        suppress_output: 是否抑制輸出（預設 False）
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        dict: 標準 PreToolUse Hook 輸出格式
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">output</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">22</span><span class="cl">        <span class="s2">&#34;hookSpecificOutput&#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="s2">&#34;hookEventName&#34;</span><span class="p">:</span> <span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="s2">&#34;permissionDecision&#34;</span><span class="p">:</span> <span class="n">decision</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="s2">&#34;permissionDecisionReason&#34;</span><span class="p">:</span> <span class="n">reason</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 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">if</span> <span class="n">user_prompt</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;hookSpecificOutput&#34;</span><span class="p">][</span><span class="s2">&#34;userPrompt&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">user_prompt</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">if</span> <span class="n">system_message</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;systemMessage&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">system_message</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">suppress_output</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="s2">&#34;suppressOutput&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</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">return</span> <span class="n">output</span></span></span></code></pre></div><h3 id="這個設計的優點">這個設計的優點</h3>
<ul>
<li><strong>清晰的建構流程</strong>：函式簽名清楚說明所需參數</li>
<li><strong>支援可選參數</strong>：使用 <code>Optional</code> 和預設值處理可選欄位</li>
<li><strong>型別提示完整</strong>：有完整的參數型別標註</li>
</ul>
<h3 id="這個設計的限制">這個設計的限制</h3>
<p>當需要序列化/反序列化時：</p>
<ul>
<li><strong>需要手動處理每個欄位</strong>：to_dict 和 from_dict 需要逐一處理</li>
<li><strong>欄位定義與驗證分離</strong>：型別檢查和業務驗證在不同地方</li>
<li><strong>難以生成文件或 schema</strong>：無法自動產生 JSON Schema 或 API 文件</li>
</ul>
<h2 id="進階解決方案">進階解決方案</h2>
<h3 id="設計目標">設計目標</h3>
<ol>
<li><strong>欄位定義包含型別、驗證、序列化</strong></li>
<li><strong>自動生成 <code>__init__</code>、<code>to_dict</code>、<code>from_dict</code></strong></li>
<li><strong>支援巢狀結構</strong></li>
</ol>
<h3 id="實作步驟">實作步驟</h3>
<h4 id="步驟-1設計-field-基類">步驟 1：設計 Field 基類</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">Any</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</span><span class="p">,</span> <span class="n">get_type_hints</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">Field</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;
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Field Descriptor base class
</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">    Combines Django&#39;s field declaration style with Python&#39;s
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">    descriptor protocol for type-safe, declarative model design.
</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></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">default</span><span class="p">:</span> <span class="n">Optional</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="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">required</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">serialized_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></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="n">T</span><span class="p">],</span> <span class="nb">bool</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">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Validation failed&#34;</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="s2">&#34;&#34;&#34;
</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">            default: Default value when not provided
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            required: Whether field is required (default True)
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">            serialized_name: Name used in serialization (default: attribute name)
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">            validator: Optional validation function
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">            error_msg: Error message for validation failure
</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="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">31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="o">=</span> <span class="n">required</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">serialized_name</span> <span class="o">=</span> <span class="n">serialized_name</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</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"># Set by __set_name__</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">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">38</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_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">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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</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">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">        Called automatically when the descriptor is assigned to a class attribute.
</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">        This is where we get the attribute name from the class definition,
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        eliminating the need to pass it explicitly.
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="s2">        &#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">name</span> <span class="o">=</span> <span class="n">name</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">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_field_</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">49</span><span class="cl">        <span class="c1"># Use attribute name as serialized name if not specified</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">serialized_name</span> <span class="ow">is</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="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="o">=</span> <span class="n">name</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</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">54</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Return field value or descriptor itself if accessed on class.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</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="c1"># Class access returns descriptor</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</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">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Validate and set field value.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">61</span><span class="cl">        <span class="c1"># Handle None for optional fields</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</span> <span class="kc">None</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="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">64</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: This field is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">            <span class="k">return</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"># Run custom validator if provided</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</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">ValueError</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">: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">error_msg</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="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</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">serialize</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">Any</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert Python value to serializable format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="k">return</span> <span class="n">value</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">deserialize</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">T</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">79</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert serialized value to Python type.&#34;&#34;&#34;</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></span></code></pre></div><p><strong>關鍵設計要點</strong>：</p>
<ol>
<li><strong><code>__set_name__</code></strong> 自動取得屬性名稱，不需要像 Django 早期版本手動傳入</li>
<li><strong><code>serialized_name</code></strong> 支援序列化時使用不同的欄位名稱（如 camelCase）</li>
<li><strong><code>serialize</code>/<code>deserialize</code></strong> 方法供子類覆寫，實現型別轉換</li>
</ol>
<h4 id="步驟-2實作型別特定的-field">步驟 2：實作型別特定的 Field</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">re</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">List</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">TypeVar</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">StringField</span><span class="p">(</span><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">  6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;String field with optional pattern validation.&#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></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="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">        <span class="n">pattern</span><span class="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"> 12</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="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</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="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 class="k">if</span> <span class="n">pattern</span> <span class="k">else</span> <span class="kc">None</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">min_length</span> <span class="o">=</span> <span class="n">min_length</span>
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">max_length</span> <span class="o">=</span> <span class="n">max_length</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 22</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"> 23</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"> 24</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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"> 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="bp">self</span><span class="o">.</span><span class="n">min_length</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"> 27</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum length is </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"> 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="bp">self</span><span class="o">.</span><span class="n">max_length</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"> 30</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum 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"> 31</span><span class="cl">
</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">pattern</span> <span class="ow">and</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"> 33</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Does not match pattern&#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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">IntField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">int</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Integer field with range validation.&#34;&#34;&#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="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</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"> 48</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"> 49</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"> 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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 52</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"> 53</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">int</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 54</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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"> 55</span><span class="cl">
</span></span><span class="line"><span class="ln"> 56</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"> 57</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum value is </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"> 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">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"> 60</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum value is </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"> 61</span><span class="cl">
</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">__set__</span><span class="p">(</span><span class="n">obj</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">class</span> <span class="nc">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field.&#34;&#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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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="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">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 69</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected bool, 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"> 70</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><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"> 73</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Field with predefined choices.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</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"> 76</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">choices</span> <span class="o">=</span> <span class="n">choices</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 80</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="n">value</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">            <span class="n">choices_str</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="nb">repr</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 82</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Must be one of: </span><span class="si">{</span><span class="n">choices_str</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">
</span></span><span class="line"><span class="ln"> 85</span><span class="cl"><span class="k">class</span> <span class="nc">ListField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">list</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">    <span class="s2">&#34;&#34;&#34;List field with item type validation.&#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="o">*</span><span class="p">,</span> <span class="n">item_field</span><span class="p">:</span> <span class="n">Field</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"> 89</span><span class="cl">        <span class="c1"># ListField defaults to empty list, not required</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;default&#34;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;required&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 92</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="o">**</span><span class="n">kwargs</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="o">.</span><span class="n">item_field</span> <span class="o">=</span> <span class="n">item_field</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 96</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"> 97</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">list</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 98</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected list, 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"> 99</span><span class="cl">            <span class="c1"># Validate each item using item_field&#39;s validation logic</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">            <span class="c1"># (simplified - in production you&#39;d want proper item validation)</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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="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="k">def</span> <span class="nf">serialize</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="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Serialize list items.&#34;&#34;&#34;</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="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">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="k">def</span> <span class="nf">deserialize</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="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Deserialize list items.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">109</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">item_field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl"><span class="k">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">112</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with ISO format serialization.&#34;&#34;&#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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="nb">format</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">T%H:%M:%S&#34;</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">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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">format</span> <span class="o">=</span> <span class="nb">format</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">
</span></span><span class="line"><span class="ln">118</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">119</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">120</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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">121</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">def</span> <span class="nf">serialize</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">datetime</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">124</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Convert datetime to ISO string.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">125</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</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">def</span> <span class="nf">deserialize</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">datetime</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">128</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Parse ISO string to datetime.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">129</span><span class="cl">        <span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">strptime</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">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span></span></span></code></pre></div><h4 id="步驟-3用-metaclass-處理欄位收集">步驟 3：用 Metaclass 處理欄位收集</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">ModelMeta</span><span class="p">(</span><span class="nb">type</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">    Metaclass for Model classes.
</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">    Responsibilities:
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">    1. Collect all Field descriptors from class definition
</span></span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="s2">    2. Store field metadata for serialization/deserialization
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="s2">    3. Generate __init__ signature from fields
</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">__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></span><span class="line"><span class="ln"> 12</span><span class="cl">        <span class="c1"># Collect fields from this class and parent classes</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">        <span class="n">fields</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">Field</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"># Inherit fields from parent Model classes</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</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="n">base</span><span class="p">,</span> <span class="s2">&#34;_fields&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">                <span class="n">fields</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_fields</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"># Collect fields from current class</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</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="nb">isinstance</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">                <span class="n">fields</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">attr_value</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"># Store fields metadata</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="n">namespace</span><span class="p">[</span><span class="s2">&#34;_fields&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">fields</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">return</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"> 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">Model</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">ModelMeta</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">    Base class for declarative models.
</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">    Provides:
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">    - Automatic __init__ from field definitions
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    - to_dict() for serialization
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">    - from_dict() for deserialization
</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></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="n">_fields</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">Field</span><span class="p">]</span>  <span class="c1"># Set by metaclass</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="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">        Initialize model from keyword arguments.
</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">        All defined fields can be passed as keyword arguments.
</span></span></span><span class="line"><span class="ln"> 47</span><span class="cl"><span class="s2">        Required fields must be provided unless they have defaults.
</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="c1"># Set each field value, triggering descriptor validation</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</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="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">field</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">value</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="c1"># Check for unknown fields</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="n">unknown</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">keys</span><span class="p">())</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">_fields</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="k">if</span> <span class="n">unknown</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 57</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;Unknown fields: </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">unknown</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"> 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">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"> 60</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 61</span><span class="cl"><span class="s2">        Serialize model to dictionary.
</span></span></span><span class="line"><span class="ln"> 62</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 63</span><span class="cl"><span class="s2">        Uses each field&#39;s serialized_name and serialize() method.
</span></span></span><span class="line"><span class="ln"> 64</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</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="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</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="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">required</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">                <span class="n">serialized_name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">                <span class="n">result</span><span class="p">[</span><span class="n">serialized_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">value</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">result</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="nd">@classmethod</span>
</span></span><span class="line"><span class="ln"> 74</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;Model&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">        Deserialize dictionary to model instance.
</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">        Handles field name mapping and type conversion.
</span></span></span><span class="line"><span class="ln"> 79</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">        <span class="n">kwargs</span> <span class="o">=</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="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="n">serialized_name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</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">serialized_name</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="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">serialized_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">            <span class="k">elif</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">                <span class="c1"># Fallback to field name if serialized name not found</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">field_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</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">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Generate readable representation.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">field_strs</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">            <span class="n">field_strs</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">field_name</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="n">value</span><span class="si">!r}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</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">field_strs</span><span class="p">)</span><span class="si">}</span><span class="s2">)&#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">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</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">102</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Compare models by their field values.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</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">other</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">            <span class="k">if</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field_name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">                <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">return</span> <span class="kc">True</span></span></span></code></pre></div><h4 id="步驟-4加入巢狀-model-支援">步驟 4：加入巢狀 Model 支援</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">EmbeddedField</span><span class="p">(</span><span class="n">Field</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">    Embedded model field for nested structures.
</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">    Allows nesting Model instances within other Models.
</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="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="p">,</span> <span class="n">model_class</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">Model</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"> 9</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="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="bp">self</span><span class="o">.</span><span class="n">model_class</span> <span class="o">=</span> <span class="n">model_class</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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">13</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">14</span><span class="cl">            <span class="c1"># Accept dict and convert to model</span>
</span></span><span class="line"><span class="ln">15</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">16</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">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">elif</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="bp">self</span><span class="o">.</span><span class="n">model_class</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="ne">TypeError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</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">name</span><span class="si">}</span><span class="s2">: Expected </span><span class="si">{</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="si">}</span><span class="s2"> or dict, &#34;</span>
</span></span><span class="line"><span class="ln">20</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">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">21</span><span class="cl">                <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="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">serialize</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">Model</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="s2">&#34;&#34;&#34;Serialize embedded model to dict.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</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">def</span> <span class="nf">deserialize</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">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Model</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="s2">&#34;&#34;&#34;Deserialize dict to embedded model.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</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="k">else</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="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">Django-style Field Descriptor - Complete Implementation
</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 combine Descriptor protocol with Metaclass
</span></span></span><span class="line"><span class="ln">  6</span><span class="cl"><span class="s2">to create a declarative API similar to Django Model Fields.
</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 class="kn">import</span> <span class="nn">re</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="kn">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"> 12</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">Optional</span><span class="p">,</span> <span class="n">Type</span><span class="p">,</span> <span class="n">Generic</span><span class="p">,</span> <span class="n">TypeVar</span><span class="p">,</span> <span class="n">Callable</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">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"> 15</span><span class="cl">
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="c1"># ===== Field Descriptors =====</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">Field</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;Base Field Descriptor with validation and serialization.&#34;&#34;&#34;</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="fm">__init__</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="p">,</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="n">default</span><span class="p">:</span> <span class="n">Optional</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="p">,</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">        <span class="n">required</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">        <span class="n">serialized_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></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="n">validator</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Callable</span><span class="p">[[</span><span class="n">T</span><span class="p">],</span> <span class="nb">bool</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="n">error_msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Validation failed&#34;</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 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"> 31</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="o">=</span> <span class="n">required</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">serialized_name</span> <span class="o">=</span> <span class="n">serialized_name</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="o">=</span> <span class="n">validator</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">error_msg</span> <span class="o">=</span> <span class="n">error_msg</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">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"> 36</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_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"> 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">__set_name__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">:</span> <span class="nb">type</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"> 39</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"> 40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;_field_</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"> 41</span><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">is</span> <span class="kc">None</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">serialized_name</span> <span class="o">=</span> <span class="n">name</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">__get__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">objtype</span><span class="p">:</span> <span class="nb">type</span> <span class="o">=</span> <span class="kc">None</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"> 45</span><span class="cl">        <span class="k">if</span> <span class="n">obj</span> <span class="ow">is</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="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</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">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 50</span><span class="cl">        <span class="k">if</span> <span class="n">value</span> <span class="ow">is</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="bp">self</span><span class="o">.</span><span class="n">required</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="kc">None</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">ValueError</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">: This field is required&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">            <span class="k">return</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">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validator</span><span class="p">(</span><span class="n">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 57</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="bp">self</span><span class="o">.</span><span class="n">name</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">error_msg</span><span class="si">}</span><span class="s2">&#34;</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="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">private_name</span><span class="p">,</span> <span class="n">value</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">serialize</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">Any</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">value</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">deserialize</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">T</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="n">value</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">StringField</span><span class="p">(</span><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"> 68</span><span class="cl">    <span class="s2">&#34;&#34;&#34;String field with pattern and length validation.&#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></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="n">pattern</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"> 74</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="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="n">max_length</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</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="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</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"> 79</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 class="k">if</span> <span class="n">pattern</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 80</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"> 81</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"> 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">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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"> 84</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"> 85</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"> 86</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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"> 87</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">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"> 88</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum length is </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"> 89</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">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"> 90</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum 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"> 91</span><span class="cl">            <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">pattern</span> <span class="ow">and</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"> 92</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Does not match required pattern&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">IntField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">int</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Integer field with range validation.&#34;&#34;&#34;</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="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="o">*</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="n">min_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="n">max_value</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="o">**</span><span class="n">kwargs</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">    <span class="p">):</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</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">106</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">107</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">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">110</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">111</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">int</span><span class="p">)</span> <span class="ow">or</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">112</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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">113</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">114</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Minimum value is </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">115</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">116</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Maximum value is </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">117</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">
</span></span><span class="line"><span class="ln">122</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">123</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">bool</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">124</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected bool, 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">125</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">ChoiceField</span><span class="p">(</span><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">128</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Field with predefined choices.&#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="o">*</span><span class="p">,</span> <span class="n">choices</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</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">131</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="o">**</span><span class="n">kwargs</span><span class="p">)</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">choices</span> <span class="o">=</span> <span class="n">choices</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="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">135</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="n">value</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">136</span><span class="cl">            <span class="n">choices_str</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="nb">repr</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">choices</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">137</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Must be one of: </span><span class="si">{</span><span class="n">choices_str</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">138</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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">class</span> <span class="nc">ListField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">list</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">141</span><span class="cl">    <span class="s2">&#34;&#34;&#34;List field with item type.&#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="o">*</span><span class="p">,</span> <span class="n">item_field</span><span class="p">:</span> <span class="n">Field</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">144</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;default&#34;</span><span class="p">,</span> <span class="p">[])</span>
</span></span><span class="line"><span class="ln">145</span><span class="cl">        <span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&#34;required&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">146</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="o">**</span><span class="n">kwargs</span><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">item_field</span> <span class="o">=</span> <span class="n">item_field</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="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">150</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">list</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">151</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: Expected list, 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">152</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</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="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">serialize</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="o">-&gt;</span> <span class="nb">list</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="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="p">(</span><span class="n">value</span> <span class="ow">or</span> <span class="p">[])]</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">deserialize</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="o">-&gt;</span> <span class="nb">list</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 class="bp">self</span><span class="o">.</span><span class="n">item_field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">item</span><span class="p">)</span> <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="p">(</span><span class="n">value</span> <span class="ow">or</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">class</span> <span class="nc">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">161</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with ISO format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">162</span><span class="cl">
</span></span><span class="line"><span class="ln">163</span><span class="cl">    <span class="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="p">,</span> <span class="nb">format</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;%Y-%m-</span><span class="si">%d</span><span class="s2">T%H:%M:%S&#34;</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">164</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="o">**</span><span class="n">kwargs</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">format</span> <span class="o">=</span> <span class="nb">format</span>
</span></span><span class="line"><span class="ln">166</span><span class="cl">
</span></span><span class="line"><span class="ln">167</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">168</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">169</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="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: 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">170</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">171</span><span class="cl">
</span></span><span class="line"><span class="ln">172</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</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">datetime</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">173</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</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">def</span> <span class="nf">deserialize</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">Optional</span><span class="p">[</span><span class="n">datetime</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">datetime</span><span class="o">.</span><span class="n">strptime</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">format</span><span class="p">)</span> <span class="k">if</span> <span class="n">value</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="c1"># ===== Metaclass and Model =====</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">ModelMeta</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">181</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Metaclass that collects Field descriptors.&#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">__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></span><span class="line"><span class="ln">184</span><span class="cl">        <span class="n">fields</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">Field</span><span class="p">]</span> <span class="o">=</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="c1"># Inherit from parent classes</span>
</span></span><span class="line"><span class="ln">187</span><span class="cl">        <span class="k">for</span> <span class="n">base</span> <span class="ow">in</span> <span class="n">bases</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">188</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s2">&#34;_fields&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">189</span><span class="cl">                <span class="n">fields</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">_fields</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">190</span><span class="cl">
</span></span><span class="line"><span class="ln">191</span><span class="cl">        <span class="c1"># Collect from current class</span>
</span></span><span class="line"><span class="ln">192</span><span class="cl">        <span class="k">for</span> <span class="n">attr_name</span><span class="p">,</span> <span class="n">attr_value</span> <span class="ow">in</span> <span class="n">namespace</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">193</span><span class="cl">            <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">attr_value</span><span class="p">,</span> <span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">194</span><span class="cl">                <span class="n">fields</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">attr_value</span>
</span></span><span class="line"><span class="ln">195</span><span class="cl">
</span></span><span class="line"><span class="ln">196</span><span class="cl">        <span class="n">namespace</span><span class="p">[</span><span class="s2">&#34;_fields&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">fields</span>
</span></span><span class="line"><span class="ln">197</span><span class="cl">        <span class="k">return</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">198</span><span class="cl">
</span></span><span class="line"><span class="ln">199</span><span class="cl"><span class="k">class</span> <span class="nc">Model</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">ModelMeta</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">200</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Base Model with automatic serialization.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">201</span><span class="cl">
</span></span><span class="line"><span class="ln">202</span><span class="cl">    <span class="n">_fields</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">Field</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">203</span><span class="cl">
</span></span><span class="line"><span class="ln">204</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</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">205</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">206</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">field</span><span class="o">.</span><span class="n">default</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">207</span><span class="cl">            <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">value</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="n">unknown</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">keys</span><span class="p">())</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">_fields</span><span class="o">.</span><span class="n">keys</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">210</span><span class="cl">        <span class="k">if</span> <span class="n">unknown</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">211</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;Unknown fields: </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">unknown</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">212</span><span class="cl">
</span></span><span class="line"><span class="ln">213</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">214</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">215</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">216</span><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field_name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">217</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">or</span> <span class="n">field</span><span class="o">.</span><span class="n">required</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">218</span><span class="cl">                <span class="n">key</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln">219</span><span class="cl">                <span class="n">result</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">220</span><span class="cl">        <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">221</span><span class="cl">
</span></span><span class="line"><span class="ln">222</span><span class="cl">    <span class="nd">@classmethod</span>
</span></span><span class="line"><span class="ln">223</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;Model&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">224</span><span class="cl">        <span class="n">kwargs</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">225</span><span class="cl">        <span class="k">for</span> <span class="n">field_name</span><span class="p">,</span> <span class="n">field</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_fields</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">226</span><span class="cl">            <span class="n">key</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">serialized_name</span> <span class="ow">or</span> <span class="n">field_name</span>
</span></span><span class="line"><span class="ln">227</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">228</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</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">229</span><span class="cl">            <span class="k">elif</span> <span class="n">field_name</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">230</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">field_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">field_name</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">231</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</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">232</span><span class="cl">
</span></span><span class="line"><span class="ln">233</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">234</span><span class="cl">        <span class="n">parts</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="n">k</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">235</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="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</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">parts</span><span class="p">)</span><span class="si">}</span><span class="s2">)&#34;</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="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</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">238</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">other</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">239</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">240</span><span class="cl">        <span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">241</span><span class="cl">            <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">k</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">242</span><span class="cl">            <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fields</span>
</span></span><span class="line"><span class="ln">243</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">244</span><span class="cl">
</span></span><span class="line"><span class="ln">245</span><span class="cl"><span class="k">class</span> <span class="nc">EmbeddedField</span><span class="p">(</span><span class="n">Field</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">246</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Embedded model field for nested structures.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">247</span><span class="cl">
</span></span><span class="line"><span class="ln">248</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="p">,</span> <span class="n">model_class</span><span class="p">:</span> <span class="n">Type</span><span class="p">[</span><span class="n">Model</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">249</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="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">250</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span> <span class="o">=</span> <span class="n">model_class</span>
</span></span><span class="line"><span class="ln">251</span><span class="cl">
</span></span><span class="line"><span class="ln">252</span><span class="cl">    <span class="k">def</span> <span class="fm">__set__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">obj</span><span class="p">:</span> <span class="n">Any</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">253</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">254</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">255</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">model_class</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">256</span><span class="cl">            <span class="k">elif</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="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">257</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">258</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">name</span><span class="si">}</span><span class="s2">: Expected </span><span class="si">{</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="si">}</span><span class="s2"> or dict&#34;</span>
</span></span><span class="line"><span class="ln">259</span><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="ln">260</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__set__</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">261</span><span class="cl">
</span></span><span class="line"><span class="ln">262</span><span class="cl">    <span class="k">def</span> <span class="nf">serialize</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">Model</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">263</span><span class="cl">        <span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span> <span class="k">if</span> <span class="n">value</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">264</span><span class="cl">
</span></span><span class="line"><span class="ln">265</span><span class="cl">    <span class="k">def</span> <span class="nf">deserialize</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">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Model</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln">266</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">model_class</span><span class="o">.</span><span class="n">from_dict</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="k">else</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"># ===== Define Models =====</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">HookSpecificOutput</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Nested model for hook-specific output.&#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="n">hook_event_name</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">choices</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span> <span class="s2">&#34;Stop&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;hookEventName&#34;</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">permission_decision</span> <span class="o">=</span> <span class="n">ChoiceField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">choices</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span> <span class="s2">&#34;deny&#34;</span><span class="p">,</span> <span class="s2">&#34;ask&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;permissionDecision&#34;</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 class="n">permission_reason</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">15</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">16</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;permissionDecisionReason&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">user_prompt</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;userPrompt&#34;</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">class</span> <span class="nc">HookOutput</span><span class="p">(</span><span class="n">Model</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">    Hook Output model - similar to create_pretooluse_output
</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">    Declarative definition replaces the factory function.
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="n">hook_specific_output</span> <span class="o">=</span> <span class="n">EmbeddedField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">model_class</span><span class="o">=</span><span class="n">HookSpecificOutput</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;hookSpecificOutput&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">system_message</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;systemMessage&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">suppress_output</span> <span class="o">=</span> <span class="n">BoolField</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">        <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="n">serialized_name</span><span class="o">=</span><span class="s2">&#34;suppressOutput&#34;</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># ===== Usage Examples =====</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">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">47</span><span class="cl">    <span class="c1"># Create model instance</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="n">hook_specific_output</span><span class="o">=</span><span class="n">HookSpecificOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">            <span class="n">hook_event_name</span><span class="o">=</span><span class="s2">&#34;PreToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">            <span class="n">permission_decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">            <span class="n">permission_reason</span><span class="o">=</span><span class="s2">&#34;Operation permitted&#34;</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="n">system_message</span><span class="o">=</span><span class="s2">&#34;Check completed&#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Created: </span><span class="si">{</span><span class="n">output</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">
</span></span><span class="line"><span class="ln">58</span><span class="cl">    <span class="c1"># Serialize to dict (ready for JSON)</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">output</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Serialized: </span><span class="si">{</span><span class="n">data</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="c1"># Output:</span>
</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">#     &#34;hookSpecificOutput&#34;: {</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">    <span class="c1">#         &#34;hookEventName&#34;: &#34;PreToolUse&#34;,</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">    <span class="c1">#         &#34;permissionDecision&#34;: &#34;allow&#34;,</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">    <span class="c1">#         &#34;permissionDecisionReason&#34;: &#34;Operation permitted&#34;</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="c1">#     },</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">    <span class="c1">#     &#34;systemMessage&#34;: &#34;Check completed&#34;,</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">    <span class="c1">#     &#34;suppressOutput&#34;: False</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">    <span class="c1"># }</span>
</span></span><span class="line"><span class="ln">71</span><span class="cl">
</span></span><span class="line"><span class="ln">72</span><span class="cl">    <span class="c1"># Deserialize from dict</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="n">restored</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">74</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Restored: </span><span class="si">{</span><span class="n">restored</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">75</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Equal: </span><span class="si">{</span><span class="n">output</span> <span class="o">==</span> <span class="n">restored</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></span><span class="line"><span class="ln">77</span><span class="cl">    <span class="c1"># Validation examples</span>
</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="n">bad</span> <span class="o">=</span> <span class="n">HookSpecificOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">80</span><span class="cl">            <span class="n">hook_event_name</span><span class="o">=</span><span class="s2">&#34;InvalidEvent&#34;</span><span class="p">,</span>  <span class="c1"># Error: not in choices</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">            <span class="n">permission_decision</span><span class="o">=</span><span class="s2">&#34;allow&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">            <span class="n">permission_reason</span><span class="o">=</span><span class="s2">&#34;Test&#34;</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="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">85</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Validation 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">86</span><span class="cl">
</span></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="c1"># Nested dict conversion</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">    <span class="n">output2</span> <span class="o">=</span> <span class="n">HookOutput</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">89</span><span class="cl">        <span class="n">hook_specific_output</span><span class="o">=</span><span class="p">{</span>  <span class="c1"># Auto-converts dict to model</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">            <span class="s2">&#34;hook_event_name&#34;</span><span class="p">:</span> <span class="s2">&#34;PostToolUse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">91</span><span class="cl">            <span class="s2">&#34;permission_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">92</span><span class="cl">            <span class="s2">&#34;permission_reason&#34;</span><span class="p">:</span> <span class="s2">&#34;Access denied&#34;</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 class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;From nested dict: </span><span class="si">{</span><span class="n">output2</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div><p><strong>執行結果</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Created: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=&#39;Check completed&#39;, suppress_output=False)
</span></span><span class="line"><span class="ln">2</span><span class="cl">Serialized: {&#39;hookSpecificOutput&#39;: {&#39;hookEventName&#39;: &#39;PreToolUse&#39;, &#39;permissionDecision&#39;: &#39;allow&#39;, &#39;permissionDecisionReason&#39;: &#39;Operation permitted&#39;}, &#39;systemMessage&#39;: &#39;Check completed&#39;, &#39;suppressOutput&#39;: False}
</span></span><span class="line"><span class="ln">3</span><span class="cl">Restored: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=&#39;Check completed&#39;, suppress_output=False)
</span></span><span class="line"><span class="ln">4</span><span class="cl">Equal: True
</span></span><span class="line"><span class="ln">5</span><span class="cl">Validation error: hook_event_name: Must be one of: &#39;PreToolUse&#39;, &#39;PostToolUse&#39;, &#39;Stop&#39;
</span></span><span class="line"><span class="ln">6</span><span class="cl">From nested dict: HookOutput(hook_specific_output=HookSpecificOutput(...), system_message=None, suppress_output=False)</span></span></code></pre></div><h2 id="設計權衡">設計權衡</h2>
<table>
  <thead>
      <tr>
          <th>面向</th>
          <th>函式工廠模式</th>
          <th>Field Descriptor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>學習曲線</strong></td>
          <td>低，只需了解函式</td>
          <td>中，需理解 Descriptor + Metaclass</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>
      <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>EmbeddedField 自動處理</td>
      </tr>
      <tr>
          <td><strong>Schema 生成</strong></td>
          <td>無法自動化</td>
          <td>可從 _fields 元資料生成</td>
      </tr>
      <tr>
          <td><strong>調試</strong></td>
          <td>直覺，錯誤位置明確</td>
          <td>需理解 Descriptor 協議</td>
      </tr>
  </tbody>
</table>
<h3 id="關鍵差異說明">關鍵差異說明</h3>
<p><strong>函式工廠模式</strong>（如 hook_io.py）：</p>
<ul>
<li>適合簡單、一次性的資料建構</li>
<li>不需要額外的學習成本</li>
<li>但重複程式碼多，難以維護</li>
</ul>
<p><strong>Field Descriptor 模式</strong>：</p>
<ul>
<li>初期投入較高，但長期收益大</li>
<li>欄位定義即文檔</li>
<li>易於擴展和測試</li>
</ul>
<h2 id="什麼時候該用這個技術">什麼時候該用這個技術？</h2>
<p><strong>適合使用</strong>：</p>
<ul>
<li>ORM/ODM 設計（如 Django Model、MongoDB ODM）</li>
<li>配置檔案解析（型別安全的 YAML/JSON 解析）</li>
<li>API 請求/回應模型（REST API、GraphQL）</li>
<li>需要 Schema 自動生成的場景</li>
<li>多個類似結構的資料類別</li>
</ul>
<p><strong>不建議使用</strong>：</p>
<ul>
<li>簡單的資料容器（直接用 dataclass）</li>
<li>不需要序列化的內部類別</li>
<li>Pydantic 已經滿足需求</li>
<li>團隊不熟悉元編程</li>
<li>單一用途的資料結構</li>
</ul>
<h2 id="與-pydantic-的比較">與 Pydantic 的比較</h2>
<table>
  <thead>
      <tr>
          <th>功能</th>
          <th>自製 Field Descriptor</th>
          <th>Pydantic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>型別驗證</td>
          <td>手動實作</td>
          <td>自動</td>
      </tr>
      <tr>
          <td>JSON Schema</td>
          <td>需額外實作</td>
          <td>內建</td>
      </tr>
      <tr>
          <td>效能</td>
          <td>依實作而定</td>
          <td>高度優化</td>
      </tr>
      <tr>
          <td>學習價值</td>
          <td>高，理解原理</td>
          <td>低，直接使用</td>
      </tr>
      <tr>
          <td>客製化彈性</td>
          <td>完全控制</td>
          <td>受框架限制</td>
      </tr>
  </tbody>
</table>
<p><strong>建議</strong>：在生產環境優先考慮 Pydantic；學習元編程時使用自製實作。</p>
<h2 id="練習">練習</h2>
<h3 id="基礎練習">基礎練習</h3>
<h4 id="1-實作-boolfield-和-datetimefield">1. 實作 <code>BoolField</code> 和 <code>DateTimeField</code></h4>
<p>完成以下 Field 類別，支援型別驗證和序列化：</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">BoolField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="nb">bool</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;Boolean field with strict type checking.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># TODO: implement</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">DateTimeField</span><span class="p">(</span><span class="n">Field</span><span class="p">[</span><span class="n">datetime</span><span class="p">]):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="s2">&#34;&#34;&#34;DateTime field with custom format.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># TODO: implement</span></span></span></code></pre></div><h4 id="2-新增欄位的預設值和-required-屬性">2. 新增欄位的預設值和 required 屬性</h4>
<p>修改 Field 基類，讓以下程式碼能正確運作：</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">Config</span><span class="p">(</span><span class="n">Model</span><span class="p">):</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="n">StringField</span><span class="p">(</span><span class="n">required</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 class="n">timeout</span> <span class="o">=</span> <span class="n">IntField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">debug</span> <span class="o">=</span> <span class="n">BoolField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 應該能建立實例</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">Config</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&#34;test&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">assert</span> <span class="n">config</span><span class="o">.</span><span class="n">timeout</span> <span class="o">==</span> <span class="mi">30</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="k">assert</span> <span class="n">config</span><span class="o">.</span><span class="n">debug</span> <span class="o">==</span> <span class="kc">False</span></span></span></code></pre></div><h3 id="進階練習">進階練習</h3>
<h4 id="3-實作巢狀-modelembeddedfield">3. 實作巢狀 Model（<code>EmbeddedField</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="k">class</span> <span class="nc">Address</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">city</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">street</span> <span class="o">=</span> <span class="n">StringField</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">Person</span><span class="p">(</span><span class="n">Model</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="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">address</span> <span class="o">=</span> <span class="n">EmbeddedField</span><span class="p">(</span><span class="n">model_class</span><span class="o">=</span><span class="n">Address</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"># 從巢狀 dict 建立</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">person</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">from_dict</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">11</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">12</span><span class="cl">    <span class="s2">&#34;address&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;city&#34;</span><span class="p">:</span> <span class="s2">&#34;Taipei&#34;</span><span class="p">,</span> <span class="s2">&#34;street&#34;</span><span class="p">:</span> <span class="s2">&#34;Main St&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 序列化回 dict</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">person</span><span class="o">.</span><span class="n">to_dict</span><span class="p">()</span></span></span></code></pre></div><h4 id="4-實作-listfield-的完整驗證">4. 實作 ListField 的完整驗證</h4>
<p>讓以下程式碼能驗證列表中的每個項目：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">Tag</span><span class="p">(</span><span class="n">Model</span><span class="p">):</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="n">StringField</span><span class="p">(</span><span class="n">pattern</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;^[a-z]+$&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">priority</span> <span class="o">=</span> <span class="n">IntField</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">10</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">Article</span><span class="p">(</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">tags</span> <span class="o">=</span> <span class="n">ListField</span><span class="p">(</span><span class="n">item_field</span><span class="o">=</span><span class="n">EmbeddedField</span><span class="p">(</span><span class="n">model_class</span><span class="o">=</span><span class="n">Tag</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"># 每個 tag 都應該被驗證</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">article</span> <span class="o">=</span> <span class="n">Article</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">title</span><span class="o">=</span><span class="s2">&#34;Python Tips&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">tags</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;priority&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;INVALID&#34;</span><span class="p">,</span> <span class="s2">&#34;priority&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">}</span>  <span class="c1"># Should raise error</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="挑戰題">挑戰題</h3>
<h4 id="5-實作-schema-自動生成">5. 實作 Schema 自動生成</h4>
<p>為 Model 類別新增 <code>to_json_schema()</code> 類別方法，自動生成 JSON Schema：</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">User</span><span class="p">(</span><span class="n">Model</span><span class="p">):</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="n">StringField</span><span class="p">(</span><span class="n">min_length</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">age</span> <span class="o">=</span> <span class="n">IntField</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="n">email</span> <span class="o">=</span> <span class="n">StringField</span><span class="p">(</span><span class="n">pattern</span><span class="o">=</span><span class="sa">r</span><span class="s2">&#34;^[\w.-]+@[\w.-]+\.\w+$&#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">schema</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">to_json_schema</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="c1"># {</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1">#     &#34;type&#34;: &#34;object&#34;,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1">#     &#34;properties&#34;: {</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">#         &#34;name&#34;: {&#34;type&#34;: &#34;string&#34;, &#34;minLength&#34;: 1, &#34;maxLength&#34;: 100},</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">#         &#34;age&#34;: {&#34;type&#34;: &#34;integer&#34;, &#34;minimum&#34;: 0, &#34;maximum&#34;: 150},</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">#         &#34;email&#34;: {&#34;type&#34;: &#34;string&#34;, &#34;pattern&#34;: &#34;^[\\w.-]+@[\\w.-]+\\.\\w+$&#34;}</span>
</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="c1">#     &#34;required&#34;: [&#34;name&#34;, &#34;age&#34;, &#34;email&#34;]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># }</span></span></span></code></pre></div><h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://github.com/django/django/blob/main/django/db/models/fields/__init__.py">Django Model Field 原始碼</a></li>
<li><a href="https://github.com/pydantic/pydantic">Pydantic Field 實作</a></li>
<li><a href="https://docs.python.org/3/howto/descriptor.html">Python Descriptor HOWTO</a></li>
<li><a href="https://www.attrs.org/">attrs 專案</a> - 另一個優秀的 Python 類別輔助工具</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a></em>
<em>返回：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></em></p>
]]></content:encoded></item><item><title>2.3 類別裝飾器與動態類別</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/class-creation/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/class-creation/</guid><description>&lt;p>類別裝飾器是比 Metaclass 更簡單、更實用的元編程工具。大多數情況下，類別裝飾器就能滿足需求。&lt;/p>
&lt;h2 id="先備知識">先備知識&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>編寫類別裝飾器&lt;/li>
&lt;li>理解 &lt;code>@dataclass&lt;/code> 的實現原理&lt;/li>
&lt;li>使用 &lt;code>type()&lt;/code> 動態建立類別&lt;/li>
&lt;li>選擇合適的元編程工具&lt;/li>
&lt;/ol>
&lt;hr>
&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="k">def&lt;/span> &lt;span class="nf">my_decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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"># 修改或增強 cls&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="bp">cls&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">@my_decorator&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">MyClass&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">pass&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">class&lt;/span> &lt;span class="nc">MyClass&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">pass&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">MyClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">my_decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MyClass&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">def&lt;/span> &lt;span class="nf">func_decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">func&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">def&lt;/span> &lt;span class="nf">wrapper&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">**&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"> 4&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;Before&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="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">func&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">**&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"> 6&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;After&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">return&lt;/span> &lt;span class="n">result&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">wrapper&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">def&lt;/span> &lt;span class="nf">class_decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">new_attribute&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;added&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">return&lt;/span> &lt;span class="bp">cls&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">def&lt;/span> &lt;span class="nf">decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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="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="bp">cls&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"> 3&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"> 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">@decorator&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">MyClass&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">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"> 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="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="c1"># 裝飾 MyClass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>類別裝飾器在類別定義完成後立即執行。&lt;/p>
&lt;hr>
&lt;h2 id="設計層帶參數的裝飾器">【設計層】帶參數的裝飾器&lt;/h2>
&lt;h3 id="裝飾器工廠">裝飾器工廠&lt;/h3>





&lt;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">repeat_method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&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;讓指定方法重複執行 times 次&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">def&lt;/span> &lt;span class="nf">decorator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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">original_init&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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">new_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">args&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">times&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">original_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">args&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"> 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="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">new_init&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">cls&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">return&lt;/span> &lt;span class="n">decorator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="nd">@repeat_method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&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">class&lt;/span> &lt;span class="nc">Counter&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">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">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="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>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl"> &lt;span class="n">Counter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">count&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">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="n">c&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Counter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">count&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 3&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="實用範例singleton">實用範例：@singleton&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">def&lt;/span> &lt;span class="nf">singleton&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;單例模式裝飾器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">instances&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>&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">get_instance&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">**&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="bp">cls&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">instances&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">instances&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="bp">cls&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">**&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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">instances&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="bp">cls&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="k">return&lt;/span> &lt;span class="n">get_instance&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="nd">@singleton&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">Database&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">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">host&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">host&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">host&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;連接到 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">host&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">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="n">db1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 連接到 localhost&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">db2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;remote&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">20&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">db1&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="n">db2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># True&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意：這個實現返回的是函式而不是類別，所以 &lt;code>isinstance(db1, Database)&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">singleton&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;保持類別特性的單例裝飾器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="n">_instance&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">class&lt;/span> &lt;span class="nc">SingletonWrapper&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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">def&lt;/span> &lt;span class="fm">__new__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">wrapper_cls&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">**&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">nonlocal&lt;/span> &lt;span class="n">_instance&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">if&lt;/span> &lt;span class="n">_instance&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"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">_instance&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">wrapper_cls&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="n">_instance&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="n">SingletonWrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__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="vm">__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="n">SingletonWrapper&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__qualname__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__qualname__&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">SingletonWrapper&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="實作層實用類別裝飾器">【實作層】實用類別裝飾器&lt;/h2>
&lt;h3 id="auto_repr">@auto_repr&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">def&lt;/span> &lt;span class="nf">auto_repr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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;自動生成 __repr__ 方法&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">def&lt;/span> &lt;span class="fm">__repr__&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">attrs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&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">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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">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="vm">__dict__&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">k&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span 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">cls&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">attrs&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">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="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__repr__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="fm">__repr__&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">return&lt;/span> &lt;span class="bp">cls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="nd">@auto_repr&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Person&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">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="n">age&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">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">18&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Person&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">30&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Person(name=&amp;#39;Alice&amp;#39;, age=30)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="dataclass-的簡化實現">@dataclass 的簡化實現&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">def&lt;/span> &lt;span class="nf">dataclass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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;簡化版 dataclass&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 收集類別屬性中的型別註解&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl"> &lt;span class="n">annotations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;__annotations__&amp;#39;&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"> 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"># 生成 __init__&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="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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">annotations&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">setattr&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="n">kwargs&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">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"># 生成 __repr__&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="fm">__repr__&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">13&lt;/span>&lt;span class="cl"> &lt;span class="n">attrs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&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">name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">getattr&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="si">!r}&lt;/span>&lt;span class="s2">&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 class="k">for&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">annotations&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span 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">cls&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">attrs&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">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"># 生成 __eq__&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="fm">__eq__&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">other&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">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">other&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">cls&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="bp">NotImplemented&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">return&lt;/span> &lt;span class="nb">all&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl"> &lt;span class="nb">getattr&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="o">==&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">other&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">25&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">annotations&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &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="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="fm">__init__&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">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__repr__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="fm">__repr__&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">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__eq__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="fm">__eq__&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">return&lt;/span> &lt;span class="bp">cls&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">@dataclass&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">class&lt;/span> &lt;span class="nc">Point&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">x&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">37&lt;/span>&lt;span class="cl"> &lt;span class="n">y&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">38&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="n">p1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&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="n">p2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&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="n">p1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Point(x=1, y=2)&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="n">p1&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">p2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># True&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="timer方法計時">@timer（方法計時）&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 class="kn">from&lt;/span> &lt;span class="nn">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">wraps&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">timer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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">for&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">method&lt;/span> &lt;span class="ow">in&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="vm">__dict__&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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">callable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="nd">@wraps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">method&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">def&lt;/span> &lt;span class="nf">timed_method&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="n">_method&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_name&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="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="n">start&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">_method&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">**&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">12&lt;/span>&lt;span class="cl"> &lt;span class="n">elapsed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">perf_counter&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&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">_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">elapsed&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.4f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">s&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">return&lt;/span> &lt;span class="n">result&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="nb">setattr&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="n">timed_method&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">return&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">@timer&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">Calculator&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">def&lt;/span> &lt;span class="nf">slow_add&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">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&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">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="mf">0.1&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="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&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="n">calc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Calculator&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">calc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">slow_add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># slow_add: 0.1001s&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層動態建立類別">【設計層】動態建立類別&lt;/h2>
&lt;h3 id="使用-type">使用 type()&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">def&lt;/span> &lt;span class="nf">greet&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="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Hello, &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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n">Person&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Person&amp;#39;&lt;/span>&lt;span class="p">,&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"> 6&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;species&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;Human&amp;#39;&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="s1">&amp;#39;greet&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">greet&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Person&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">p&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&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">greet&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># Hello, Alice&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">def&lt;/span> &lt;span class="nf">create_model&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">fields&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="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"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">field&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">fields&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="nb">setattr&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="n">kwargs&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">field&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">__repr__&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">attrs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">getattr&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">f&lt;/span>&lt;span class="p">)&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">f&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">fields&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="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">(&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">attrs&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">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">return&lt;/span> &lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&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">13&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;__init__&amp;#39;&lt;/span>&lt;span class="p">:&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">14&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;__repr__&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="fm">__repr__&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="s1">&amp;#39;_fields&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">fields&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="n">User&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;User&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;email&amp;#39;&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">Product&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Product&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;price&amp;#39;&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="n">u&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&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">22&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># User(id=1, name=&amp;#39;Alice&amp;#39;, email=&amp;#39;alice@example.com&amp;#39;)&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">def&lt;/span> &lt;span class="nf">create_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">use_async&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"> 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">if&lt;/span> &lt;span class="n">use_async&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="kn">import&lt;/span> &lt;span class="nn">asyncio&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">AsyncBase&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">async&lt;/span> &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">data&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="mf">0.1&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Async: &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">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="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AsyncBase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="k">class&lt;/span> &lt;span class="nc">SyncBase&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">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">data&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Sync: &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">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="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SyncBase&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="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Handler&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">base&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">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="n">SyncHandler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">use_async&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="n">AsyncHandler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">use_async&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;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="選擇指南">【選擇指南】&lt;/h2>
&lt;h3 id="metaclass-vs-類別裝飾器-vs-init_">Metaclass vs 類別裝飾器 vs &lt;strong>init_subclass&lt;/strong>&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>特性&lt;/th>
 &lt;th>類別裝飾器&lt;/th>
 &lt;th>&lt;strong>init_subclass&lt;/strong>&lt;/th>
 &lt;th>Metaclass&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>學習曲線&lt;/td>
 &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;td>是&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>可堆疊&lt;/td>
 &lt;td>是&lt;/td>
 &lt;td>是（需 super）&lt;/td>
 &lt;td>困難&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>控制程度&lt;/td>
 &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;td>深度自訂&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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">│
&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;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">│ │ └── __init_subclass__
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">│ └── Metaclass
&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>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> └── type()&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="實戰組合使用">【實戰】組合使用&lt;/h2>





&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">functools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">wraps&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_types&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&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">original_init&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&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">annotations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">cls&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;__annotations__&amp;#39;&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"> 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">@wraps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">original_init&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">def&lt;/span> &lt;span class="nf">validated_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">args&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"># 將位置參數轉換為關鍵字參數&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">inspect&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">sig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">inspect&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">signature&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">original_init&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">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sig&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parameters&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&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"># 跳過 self&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">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">arg&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&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">16&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">params&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">kwargs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">params&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">arg&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"># 驗證型別&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">for&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">expected_type&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">annotations&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">21&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="n">kwargs&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">kwargs&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">23&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="n">expected_type&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">raise&lt;/span> &lt;span class="ne">TypeError&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="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"> 應為 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">expected_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">，&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="sa">f&lt;/span>&lt;span class="s2">&amp;#34;但得到 &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>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="n">original_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">kwargs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="bp">cls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">validated_init&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="bp">cls&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">@validate_types&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">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">36&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>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl"> &lt;span class="n">age&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">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="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="n">age&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="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">41&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl">&lt;span class="n">User&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Alice&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">30&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">44&lt;/span>&lt;span class="cl">&lt;span class="c1"># User(&amp;#34;Bob&amp;#34;, &amp;#34;thirty&amp;#34;) # TypeError!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="思考題">思考題&lt;/h2>
&lt;ol>
&lt;li>類別裝飾器和 Metaclass 可以同時使用嗎？執行順序是什麼？&lt;/li>
&lt;li>&lt;code>@dataclass&lt;/code> 實際上使用了哪些技術？&lt;/li>
&lt;li>動態建立的類別和靜態定義的類別有什麼差異？&lt;/li>
&lt;/ol>
&lt;h2 id="實作練習">實作練習&lt;/h2>
&lt;ol>
&lt;li>實作一個 &lt;code>@frozen&lt;/code> 裝飾器，讓類別的實例不可變&lt;/li>
&lt;li>實作一個 &lt;code>@trace&lt;/code> 裝飾器，追蹤所有方法呼叫&lt;/li>
&lt;li>使用 &lt;code>type()&lt;/code> 建立一個簡單的 Enum 類別&lt;/li>
&lt;/ol>
&lt;h2 id="延伸閱讀">延伸閱讀&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://peps.python.org/pep-0557/">PEP 557 - Data Classes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.python.org/3/library/dataclasses.html">Python 官方 - dataclasses 模組&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>上一章：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">Metaclass 設計與應用&lt;/a>&lt;/em>
&lt;em>下一章：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/introspection/" data-link-title="2.4 反射與 inspect 模組" data-link-desc="使用反射和 inspect 模組檢視和操作 Python 物件">反射與 inspect 模組&lt;/a>&lt;/em>&lt;/p></description><content:encoded><![CDATA[<p>類別裝飾器是比 Metaclass 更簡單、更實用的元編程工具。大多數情況下，類別裝飾器就能滿足需求。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>編寫類別裝飾器</li>
<li>理解 <code>@dataclass</code> 的實現原理</li>
<li>使用 <code>type()</code> 動態建立類別</li>
<li>選擇合適的元編程工具</li>
</ol>
<hr>
<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="k">def</span> <span class="nf">my_decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="c1"># 修改或增強 cls</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</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">@my_decorator</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</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"># 等價於</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</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 class="n">MyClass</span> <span class="o">=</span> <span class="n">my_decorator</span><span class="p">(</span><span class="n">MyClass</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">def</span> <span class="nf">func_decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="nf">wrapper</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"> 4</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Before&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">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"> 6</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;After&#34;</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">result</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="n">wrapper</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">def</span> <span class="nf">class_decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="bp">cls</span><span class="o">.</span><span class="n">new_attribute</span> <span class="o">=</span> <span class="s2">&#34;added&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</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">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;裝飾 </span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</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="bp">cls</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">@decorator</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</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"> 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="c1"># 定義類別主體</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># 裝飾 MyClass</span></span></span></code></pre></div><p>類別裝飾器在類別定義完成後立即執行。</p>
<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="k">def</span> <span class="nf">repeat_method</span><span class="p">(</span><span class="n">times</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;讓指定方法重複執行 times 次&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</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></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">original_init</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="fm">__init__</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">new_init</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">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">times</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                <span class="n">original_init</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">**</span><span class="n">kwargs</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="bp">cls</span><span class="o">.</span><span class="fm">__init__</span> <span class="o">=</span> <span class="n">new_init</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="bp">cls</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="n">decorator</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">@repeat_method</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">class</span> <span class="nc">Counter</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</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">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="fm">__init__</span><span class="p">(</span><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">Counter</span><span class="o">.</span><span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">c</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">Counter</span><span class="o">.</span><span class="n">count</span><span class="p">)</span>  <span class="c1"># 3</span></span></span></code></pre></div><h3 id="實用範例singleton">實用範例：@singleton</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">singleton</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;單例模式裝飾器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">instances</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="nf">get_instance</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"> 6</span><span class="cl">        <span class="k">if</span> <span class="bp">cls</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="n">instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span> <span class="o">=</span> <span class="bp">cls</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"> 8</span><span class="cl">        <span class="k">return</span> <span class="n">instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">return</span> <span class="n">get_instance</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">@singleton</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">class</span> <span class="nc">Database</span><span class="p">:</span>
</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">host</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">host</span> <span class="o">=</span> <span class="n">host</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;連接到 </span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s2">&#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="n">db1</span> <span class="o">=</span> <span class="n">Database</span><span class="p">(</span><span class="s2">&#34;localhost&#34;</span><span class="p">)</span>  <span class="c1"># 連接到 localhost</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">db2</span> <span class="o">=</span> <span class="n">Database</span><span class="p">(</span><span class="s2">&#34;remote&#34;</span><span class="p">)</span>     <span class="c1"># 不會再次連接</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="n">db1</span> <span class="ow">is</span> <span class="n">db2</span><span class="p">)</span>  <span class="c1"># True</span></span></span></code></pre></div><p>注意：這個實現返回的是函式而不是類別，所以 <code>isinstance(db1, Database)</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">singleton</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;保持類別特性的單例裝飾器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">_instance</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">class</span> <span class="nc">SingletonWrapper</span><span class="p">(</span><span class="bp">cls</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">__new__</span><span class="p">(</span><span class="n">wrapper_cls</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"> 7</span><span class="cl">            <span class="k">nonlocal</span> <span class="n">_instance</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="k">if</span> <span class="n">_instance</span> <span class="ow">is</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">_instance</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">wrapper_cls</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">_instance</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">SingletonWrapper</span><span class="o">.</span><span class="vm">__name__</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">SingletonWrapper</span><span class="o">.</span><span class="vm">__qualname__</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__qualname__</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">return</span> <span class="n">SingletonWrapper</span></span></span></code></pre></div><hr>
<h2 id="實作層實用類別裝飾器">【實作層】實用類別裝飾器</h2>
<h3 id="auto_repr">@auto_repr</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">auto_repr</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;自動生成 __repr__ 方法&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="n">attrs</span> <span class="o">=</span> <span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            <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></span><span class="line"><span class="ln"> 6</span><span class="cl">            <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="vm">__dict__</span><span class="o">.</span><span class="n">items</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">if</span> <span class="ow">not</span> <span class="n">k</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;_&#39;</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 class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="n">attrs</span><span class="si">}</span><span class="s2">)&#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="bp">cls</span><span class="o">.</span><span class="fm">__repr__</span> <span class="o">=</span> <span class="fm">__repr__</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</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">@auto_repr</span>
</span></span><span class="line"><span class="ln">15</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">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">name</span><span class="p">,</span> <span class="n">age</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">name</span> <span class="o">=</span> <span class="n">name</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">age</span> <span class="o">=</span> <span class="n">age</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">p</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s2">&#34;Alice&#34;</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>  <span class="c1"># Person(name=&#39;Alice&#39;, age=30)</span></span></span></code></pre></div><h3 id="dataclass-的簡化實現">@dataclass 的簡化實現</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">dataclass</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡化版 dataclass&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="c1"># 收集類別屬性中的型別註解</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">annotations</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">&#39;__annotations__&#39;</span><span class="p">,</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"># 生成 __init__</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="o">**</span><span class="n">kwargs</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">name</span> <span class="ow">in</span> <span class="n">annotations</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="nb">setattr</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">kwargs</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">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># 生成 __repr__</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">attrs</span> <span class="o">=</span> <span class="s1">&#39;, &#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <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="nb">getattr</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="si">!r}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">(</span><span class="si">{</span><span class="n">attrs</span><span class="si">}</span><span class="s2">)&#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"># 生成 __eq__</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="bp">cls</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="bp">NotImplemented</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="nb">getattr</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="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">annotations</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="bp">cls</span><span class="o">.</span><span class="fm">__init__</span> <span class="o">=</span> <span class="fm">__init__</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="bp">cls</span><span class="o">.</span><span class="fm">__repr__</span> <span class="o">=</span> <span class="fm">__repr__</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="bp">cls</span><span class="o">.</span><span class="fm">__eq__</span> <span class="o">=</span> <span class="fm">__eq__</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="k">class</span> <span class="nc">Point</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="n">x</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">
</span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="n">p1</span> <span class="o">=</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="n">p2</span> <span class="o">=</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">2</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="n">p1</span><span class="p">)</span>         <span class="c1"># Point(x=1, y=2)</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="n">p1</span> <span class="o">==</span> <span class="n">p2</span><span class="p">)</span>   <span class="c1"># True</span></span></span></code></pre></div><h3 id="timer方法計時">@timer（方法計時）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</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">timer</span><span class="p">(</span><span class="bp">cls</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">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">method</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__dict__</span><span class="o">.</span><span class="n">items</span><span class="p">()):</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="k">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">method</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;_&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="nd">@wraps</span><span class="p">(</span><span class="n">method</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">timed_method</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="n">_method</span><span class="o">=</span><span class="n">method</span><span class="p">,</span> <span class="n">_name</span><span class="o">=</span><span class="n">name</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="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">11</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">_method</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">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</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">13</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">_name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">s&#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">result</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="nb">setattr</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="n">timed_method</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">return</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">@timer</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">class</span> <span class="nc">Calculator</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">slow_add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">23</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="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</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="n">calc</span> <span class="o">=</span> <span class="n">Calculator</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="n">calc</span><span class="o">.</span><span class="n">slow_add</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="c1"># slow_add: 0.1001s</span></span></span></code></pre></div><hr>
<h2 id="設計層動態建立類別">【設計層】動態建立類別</h2>
<h3 id="使用-type">使用 type()</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">def</span> <span class="nf">greet</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="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </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></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">Person</span> <span class="o">=</span> <span class="nb">type</span><span class="p">(</span><span class="s1">&#39;Person&#39;</span><span class="p">,</span> <span class="p">(),</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="s1">&#39;species&#39;</span><span class="p">:</span> <span class="s1">&#39;Human&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s1">&#39;greet&#39;</span><span class="p">:</span> <span class="n">greet</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">p</span> <span class="o">=</span> <span class="n">Person</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">p</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Alice&#34;</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">p</span><span class="o">.</span><span class="n">greet</span><span class="p">())</span>  <span class="c1"># Hello, Alice</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">def</span> <span class="nf">create_model</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">fields</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="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">fields</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="nb">setattr</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="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">field</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">__repr__</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">attrs</span> <span class="o">=</span> <span class="s1">&#39;, &#39;</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">f</span><span class="si">}</span><span class="s2">=</span><span class="si">{</span><span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span><span class="si">!r}</span><span class="s2">&#34;</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">fields</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</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">attrs</span><span class="si">}</span><span class="s2">)&#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">return</span> <span class="nb">type</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="p">(),</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s1">&#39;__init__&#39;</span><span class="p">:</span> <span class="fm">__init__</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s1">&#39;__repr__&#39;</span><span class="p">:</span> <span class="fm">__repr__</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s1">&#39;_fields&#39;</span><span class="p">:</span> <span class="n">fields</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="n">User</span> <span class="o">=</span> <span class="n">create_model</span><span class="p">(</span><span class="s1">&#39;User&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="p">,</span> <span class="s1">&#39;email&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">Product</span> <span class="o">=</span> <span class="n">create_model</span><span class="p">(</span><span class="s1">&#39;Product&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">,</span> <span class="s1">&#39;name&#39;</span><span class="p">,</span> <span class="s1">&#39;price&#39;</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">u</span> <span class="o">=</span> <span class="n">User</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="mi">1</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">22</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">u</span><span class="p">)</span>  <span class="c1"># User(id=1, name=&#39;Alice&#39;, email=&#39;alice@example.com&#39;)</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">def</span> <span class="nf">create_handler</span><span class="p">(</span><span class="n">use_async</span><span class="o">=</span><span class="kc">False</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">if</span> <span class="n">use_async</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="kn">import</span> <span class="nn">asyncio</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">AsyncBase</span><span class="p">:</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">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</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="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">                <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Async: </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">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="n">AsyncBase</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">class</span> <span class="nc">SyncBase</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">def</span> <span class="nf">handle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</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="sa">f</span><span class="s2">&#34;Sync: </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">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="n">SyncBase</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="nb">type</span><span class="p">(</span><span class="s1">&#39;Handler&#39;</span><span class="p">,</span> <span class="p">(</span><span class="n">base</span><span class="p">,),</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="n">SyncHandler</span> <span class="o">=</span> <span class="n">create_handler</span><span class="p">(</span><span class="n">use_async</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="n">AsyncHandler</span> <span class="o">=</span> <span class="n">create_handler</span><span class="p">(</span><span class="n">use_async</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span></span></span></code></pre></div><hr>
<h2 id="選擇指南">【選擇指南】</h2>
<h3 id="metaclass-vs-類別裝飾器-vs-init_">Metaclass vs 類別裝飾器 vs <strong>init_subclass</strong></h3>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>類別裝飾器</th>
          <th><strong>init_subclass</strong></th>
          <th>Metaclass</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>是（需 super）</td>
          <td>困難</td>
      </tr>
      <tr>
          <td>控制程度</td>
          <td>類別建立後</td>
          <td>子類別定義時</td>
          <td>類別建立中</td>
      </tr>
      <tr>
          <td>適用場景</td>
          <td>添加/修改方法</td>
          <td>註冊/驗證</td>
          <td>深度自訂</td>
      </tr>
  </tbody>
</table>
<h3 id="決策流程">決策流程</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-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><span class="line"><span class="ln"> 8</span><span class="cl">│   │   └── __init_subclass__
</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">│       └── Metaclass
</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">    └── type()</span></span></code></pre></div><hr>
<h2 id="實戰組合使用">【實戰】組合使用</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">validate_types</span><span class="p">(</span><span class="bp">cls</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">original_init</span> <span class="o">=</span> <span class="bp">cls</span><span class="o">.</span><span class="fm">__init__</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">annotations</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">&#39;__annotations__&#39;</span><span class="p">,</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="nd">@wraps</span><span class="p">(</span><span class="n">original_init</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">validated_init</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">**</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"># 將位置參數轉換為關鍵字參數</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="kn">import</span> <span class="nn">inspect</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="n">sig</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">original_init</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">params</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">sig</span><span class="o">.</span><span class="n">parameters</span><span class="o">.</span><span class="n">keys</span><span class="p">())[</span><span class="mi">1</span><span class="p">:]</span>  <span class="c1"># 跳過 self</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">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">arg</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">            <span class="k">if</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">params</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">                <span class="n">kwargs</span><span class="p">[</span><span class="n">params</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span> <span class="o">=</span> <span class="n">arg</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">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">expected_type</span> <span class="ow">in</span> <span class="n">annotations</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">if</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                <span class="n">value</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">[</span><span class="n">name</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="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">expected_type</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</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">25</span><span class="cl">                        <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">expected_type</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">26</span><span class="cl">                        <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">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">27</span><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">        <span class="n">original_init</span><span class="p">(</span><span class="bp">self</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></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="bp">cls</span><span class="o">.</span><span class="fm">__init__</span> <span class="o">=</span> <span class="n">validated_init</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="k">return</span> <span class="bp">cls</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">
</span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="nd">@validate_types</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="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">age</span><span class="p">:</span> <span class="nb">int</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="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="n">age</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</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">age</span> <span class="o">=</span> <span class="n">age</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="n">User</span><span class="p">(</span><span class="s2">&#34;Alice&#34;</span><span class="p">,</span> <span class="mi">30</span><span class="p">)</span>  <span class="c1"># OK</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># User(&#34;Bob&#34;, &#34;thirty&#34;)  # TypeError!</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>類別裝飾器和 Metaclass 可以同時使用嗎？執行順序是什麼？</li>
<li><code>@dataclass</code> 實際上使用了哪些技術？</li>
<li>動態建立的類別和靜態定義的類別有什麼差異？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>實作一個 <code>@frozen</code> 裝飾器，讓類別的實例不可變</li>
<li>實作一個 <code>@trace</code> 裝飾器，追蹤所有方法呼叫</li>
<li>使用 <code>type()</code> 建立一個簡單的 Enum 類別</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://peps.python.org/pep-0557/">PEP 557 - Data Classes</a></li>
<li><a href="https://docs.python.org/3/library/dataclasses.html">Python 官方 - dataclasses 模組</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">Metaclass 設計與應用</a></em>
<em>下一章：<a href="/blog/python-advanced/02-metaprogramming/introspection/" data-link-title="2.4 反射與 inspect 模組" data-link-desc="使用反射和 inspect 模組檢視和操作 Python 物件">反射與 inspect 模組</a></em></p>
]]></content:encoded></item><item><title>2.4 反射與 inspect 模組</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/introspection/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/introspection/</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/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>使用內建反射函式（getattr、setattr 等）&lt;/li>
&lt;li>使用 inspect 模組分析物件&lt;/li>
&lt;li>理解 &lt;strong>getattr&lt;/strong> 和 &lt;strong>getattribute&lt;/strong> 的差異&lt;/li>
&lt;li>實作動態代理和 Mock 物件&lt;/li>
&lt;/ol>
&lt;hr>
&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="k">class&lt;/span> &lt;span class="nc">MyClass&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">value&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">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"> 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">method&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"> 6&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 class="o">*&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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"> 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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># &amp;lt;class &amp;#39;MyClass&amp;#39;&amp;gt;&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__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="c1"># MyClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">dir&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># [&amp;#39;__class__&amp;#39;, &amp;#39;method&amp;#39;, &amp;#39;value&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># True&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="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># 10&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="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MyClass&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"> 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"># getattr - 取得屬性&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">value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 10&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">default&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;missing&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;default&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># &amp;#39;default&amp;#39;&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"># setattr - 設定屬性&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">setattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 20&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"># hasattr - 檢查屬性是否存在&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;missing&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># False&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"># delattr - 刪除屬性&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">delattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&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="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># False&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="vars-和-dict">vars() 和 &lt;strong>dict&lt;/strong>&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">Person&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">species&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Human&amp;#34;&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>&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>&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 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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Person&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Alice&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 實例的 __dict__：只包含實例屬性&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">vars&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># {&amp;#39;name&amp;#39;: &amp;#39;Alice&amp;#39;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__dict__&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># {&amp;#39;name&amp;#39;: &amp;#39;Alice&amp;#39;}&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"># 類別的 __dict__：包含類別屬性和方法&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="nb">vars&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Person&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1"># {&amp;#39;species&amp;#39;: &amp;#39;Human&amp;#39;, &amp;#39;__init__&amp;#39;: ..., ...}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="設計層getattr-vs-getattribute">【設計層】&lt;strong>getattr&lt;/strong> vs &lt;strong>getattribute&lt;/strong>&lt;/h2>
&lt;h3 id="getattr">&lt;strong>getattr&lt;/strong>&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">class&lt;/span> &lt;span class="nc">FlexibleObject&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>&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">existing&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;I exist&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="k">def&lt;/span> &lt;span class="fm">__getattr__&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>&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="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">&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="n">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FlexibleObject&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">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">existing&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># I exist（正常查找成功）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">missing&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 動態屬性: missing（__getattr__ 被呼叫）&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="getattribute">&lt;strong>getattribute&lt;/strong>&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">class&lt;/span> &lt;span class="nc">LoggedObject&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">value&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">object&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__setattr__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;value&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&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">def&lt;/span> &lt;span class="fm">__getattribute__&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>&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;存取屬性: &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 class="k">return&lt;/span> &lt;span class="nb">object&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__getattribute__&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>&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">obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">LoggedObject&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">10&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">obj&lt;/span>&lt;span class="o">.&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">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="c1"># 存取屬性: value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 10&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意：在 &lt;code>__getattribute__&lt;/code> 中要避免無限遞迴：&lt;/p></description><content:encoded><![CDATA[<p>反射是程式檢視和修改自身結構的能力。Python 提供了強大的反射工具，讓你能夠動態地檢視物件、取得函式簽名、甚至修改執行中的程式。</p>
<h2 id="先備知識">先備知識</h2>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">2.3 類別裝飾器與動態類別</a></li>
</ul>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>使用內建反射函式（getattr、setattr 等）</li>
<li>使用 inspect 模組分析物件</li>
<li>理解 <strong>getattr</strong> 和 <strong>getattribute</strong> 的差異</li>
<li>實作動態代理和 Mock 物件</li>
</ol>
<hr>
<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="k">class</span> <span class="nc">MyClass</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">value</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">value</span> <span class="o">=</span> <span class="n">value</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">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">*</span> <span class="mi">2</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">obj</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">(</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="c1"># 反射：程式檢視自己</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">))</span>                    <span class="c1"># &lt;class &#39;MyClass&#39;&gt;</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">obj</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="p">)</span>       <span class="c1"># MyClass</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="nb">dir</span><span class="p">(</span><span class="n">obj</span><span class="p">))</span>                     <span class="c1"># [&#39;__class__&#39;, &#39;method&#39;, &#39;value&#39;, ...]</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="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">))</span>        <span class="c1"># True</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="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">))</span>        <span class="c1"># 10</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="n">obj</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># getattr - 取得屬性</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="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">)</span>                    <span class="c1"># 10</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">default</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;missing&#39;</span><span class="p">,</span> <span class="s1">&#39;default&#39;</span><span class="p">)</span>     <span class="c1"># &#39;default&#39;</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"># setattr - 設定屬性</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="mi">20</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">obj</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>  <span class="c1"># 20</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"># hasattr - 檢查屬性是否存在</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="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">))</span>   <span class="c1"># True</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="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;missing&#39;</span><span class="p">))</span> <span class="c1"># False</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"># delattr - 刪除屬性</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">delattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</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="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">))</span>   <span class="c1"># False</span></span></span></code></pre></div><h3 id="vars-和-dict">vars() 和 <strong>dict</strong></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">Person</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">species</span> <span class="o">=</span> <span class="s2">&#34;Human&#34;</span>  <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="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 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 class="c1"># 實例屬性</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">p</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="s2">&#34;Alice&#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"># 實例的 __dict__：只包含實例屬性</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="nb">vars</span><span class="p">(</span><span class="n">p</span><span class="p">))</span>         <span class="c1"># {&#39;name&#39;: &#39;Alice&#39;}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span>      <span class="c1"># {&#39;name&#39;: &#39;Alice&#39;}</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"># 類別的 __dict__：包含類別屬性和方法</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="nb">vars</span><span class="p">(</span><span class="n">Person</span><span class="p">))</span>    <span class="c1"># {&#39;species&#39;: &#39;Human&#39;, &#39;__init__&#39;: ..., ...}</span></span></span></code></pre></div><hr>
<h2 id="設計層getattr-vs-getattribute">【設計層】<strong>getattr</strong> vs <strong>getattribute</strong></h2>
<h3 id="getattr"><strong>getattr</strong></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">FlexibleObject</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></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">existing</span> <span class="o">=</span> <span class="s2">&#34;I exist&#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">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</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">name</span><span class="si">}</span><span class="s2">&#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="n">obj</span> <span class="o">=</span> <span class="n">FlexibleObject</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">obj</span><span class="o">.</span><span class="n">existing</span><span class="p">)</span>  <span class="c1"># I exist（正常查找成功）</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">obj</span><span class="o">.</span><span class="n">missing</span><span class="p">)</span>   <span class="c1"># 動態屬性: missing（__getattr__ 被呼叫）</span></span></span></code></pre></div><h3 id="getattribute"><strong>getattribute</strong></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">LoggedObject</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">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="nb">object</span><span class="o">.</span><span class="fm">__setattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s1">&#39;value&#39;</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">def</span> <span class="fm">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</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">&#34;</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="nb">object</span><span class="o">.</span><span class="fm">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">obj</span> <span class="o">=</span> <span class="n">LoggedObject</span><span class="p">(</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="nb">print</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">value</span><span class="p">)</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"># 存取屬性: value</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 10</span></span></span></code></pre></div><p>注意：在 <code>__getattribute__</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">Dangerous</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">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="c1"># 錯誤！這會造成無限遞迴</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="c1"># return self.data[name]</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"># 正確：使用 object.__getattribute__</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="nb">object</span><span class="o">.</span><span class="fm">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s1">&#39;data&#39;</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">data</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></code></pre></div><hr>
<h2 id="實作層inspect-模組">【實作層】inspect 模組</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">inspect</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">my_function</span><span class="p">():</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">MyClass</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="nf">method</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="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="nb">print</span><span class="p">(</span><span class="n">inspect</span><span class="o">.</span><span class="n">isfunction</span><span class="p">(</span><span class="n">my_function</span><span class="p">))</span>  <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">inspect</span><span class="o">.</span><span class="n">ismethod</span><span class="p">(</span><span class="n">my_function</span><span class="p">))</span>    <span class="c1"># False</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">inspect</span><span class="o">.</span><span class="n">isclass</span><span class="p">(</span><span class="n">MyClass</span><span class="p">))</span>         <span class="c1"># True</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">obj</span> <span class="o">=</span> <span class="n">MyClass</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">inspect</span><span class="o">.</span><span class="n">ismethod</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">method</span><span class="p">))</span>     <span class="c1"># True</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">inspect</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">greet</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">greeting</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;Hello&#34;</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"> 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">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">greeting</span><span class="si">}</span><span class="s2">, </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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 取得簽名</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">sig</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">greet</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">sig</span><span class="p">)</span>  <span class="c1"># (name: str, greeting: str = &#39;Hello&#39;) -&gt; str</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="k">for</span> <span class="n">param_name</span><span class="p">,</span> <span class="n">param</span> <span class="ow">in</span> <span class="n">sig</span><span class="o">.</span><span class="n">parameters</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;  </span><span class="si">{</span><span class="n">param_name</span><span class="si">}</span><span class="s2">:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    default: </span><span class="si">{</span><span class="n">param</span><span class="o">.</span><span class="n">default</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    annotation: </span><span class="si">{</span><span class="n">param</span><span class="o">.</span><span class="n">annotation</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;    kind: </span><span class="si">{</span><span class="n">param</span><span class="o">.</span><span class="n">kind</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># 輸出：</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1">#   name:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="c1">#     default: &lt;class &#39;inspect._empty&#39;&gt;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">#     annotation: &lt;class &#39;str&#39;&gt;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">#     kind: POSITIONAL_OR_KEYWORD</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">#   greeting:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="c1">#     default: Hello</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1">#     annotation: &lt;class &#39;str&#39;&gt;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1">#     kind: POSITIONAL_OR_KEYWORD</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">inspect</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">example_function</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">x</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</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="nb">print</span><span class="p">(</span><span class="n">inspect</span><span class="o">.</span><span class="n">getsource</span><span class="p">(</span><span class="n">example_function</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># def example_function():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1">#     &#34;&#34;&#34;這是範例函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1">#     x = 1</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1">#     return x + 1</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 取得文件字串</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">inspect</span><span class="o">.</span><span class="n">getdoc</span><span class="p">(</span><span class="n">example_function</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 這是範例函式</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></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="nb">print</span><span class="p">(</span><span class="n">inspect</span><span class="o">.</span><span class="n">getfile</span><span class="p">(</span><span class="n">example_function</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">inspect</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">inner</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">stack</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</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="k">for</span> <span class="n">frame_info</span> <span class="ow">in</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">frame_info</span><span class="o">.</span><span class="n">function</span><span class="si">}</span><span class="s2"> at </span><span class="si">{</span><span class="n">frame_info</span><span class="o">.</span><span class="n">lineno</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="nf">outer</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">inner</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">main</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">outer</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">main</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1"># 輸出：</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># inner at 5</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># outer at 9</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># main at 12</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="c1"># &lt;module&gt; at 14</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="k">class</span> <span class="nc">Calculator</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="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</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">a</span> <span class="o">+</span> <span class="n">b</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">subtract</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</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">a</span> <span class="o">-</span> <span class="n">b</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">multiply</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</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">a</span> <span class="o">*</span> <span class="n">b</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">execute</span><span class="p">(</span><span class="n">calc</span><span class="p">,</span> <span class="n">operation</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</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="n">method</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">calc</span><span class="p">,</span> <span class="n">operation</span><span class="p">,</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="n">method</span> <span class="ow">and</span> <span class="n">callable</span><span class="p">(</span><span class="n">method</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">method</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</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">operation</span><span class="si">}</span><span class="s2">&#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="n">calc</span> <span class="o">=</span> <span class="n">Calculator</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="n">execute</span><span class="p">(</span><span class="n">calc</span><span class="p">,</span> <span class="s2">&#34;add&#34;</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>       <span class="c1"># 8</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="n">execute</span><span class="p">(</span><span class="n">calc</span><span class="p">,</span> <span class="s2">&#34;multiply&#34;</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span>  <span class="c1"># 15</span></span></span></code></pre></div><h3 id="簡單的-mock-物件">簡單的 Mock 物件</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">Mock</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;簡單的 Mock 物件&#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></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_calls</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">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">def</span> <span class="nf">method</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"> 9</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_calls</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">                <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">                <span class="s1">&#39;args&#39;</span><span class="p">:</span> <span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                <span class="s1">&#39;kwargs&#39;</span><span class="p">:</span> <span class="n">kwargs</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 class="k">return</span> <span class="n">Mock</span><span class="p">()</span>  <span class="c1"># 支援鏈式呼叫</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">method</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="nd">@property</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">def</span> <span class="nf">call_count</span><span class="p">(</span><span class="bp">self</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">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_calls</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">assert_called_with</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">method</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">23</span><span class="cl">        <span class="k">for</span> <span class="n">call</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_calls</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="p">(</span><span class="n">call</span><span class="p">[</span><span class="s1">&#39;method&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">method</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">                <span class="n">call</span><span class="p">[</span><span class="s1">&#39;args&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">args</span> <span class="ow">and</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">                <span class="n">call</span><span class="p">[</span><span class="s1">&#39;kwargs&#39;</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">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        <span class="k">raise</span> <span class="ne">AssertionError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">method</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="c1"># 使用</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="n">mock</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="n">flush</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 class="n">mock</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="s2">&#34;file.txt&#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="nb">print</span><span class="p">(</span><span class="n">mock</span><span class="o">.</span><span class="n">call_count</span><span class="p">)</span>  <span class="c1"># 2</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="s2">&#34;save&#34;</span><span class="p">,</span> <span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>  <span class="c1"># OK</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">LoggingProxy</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">target</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="nb">object</span><span class="o">.</span><span class="fm">__setattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s1">&#39;_target&#39;</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="nb">object</span><span class="o">.</span><span class="fm">__setattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s1">&#39;_log&#39;</span><span class="p">,</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">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="n">attr</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_target</span><span class="p">,</span> <span class="n">name</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">if</span> <span class="n">callable</span><span class="p">(</span><span class="n">attr</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">logged_method</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">13</span><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_log</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">                    <span class="s1">&#39;method&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">                    <span class="s1">&#39;args&#39;</span><span class="p">:</span> <span class="n">args</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">                    <span class="s1">&#39;kwargs&#39;</span><span class="p">:</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="p">})</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">                <span class="k">return</span> <span class="n">attr</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">19</span><span class="cl">            <span class="k">return</span> <span class="n">logged_method</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">return</span> <span class="n">attr</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">__setattr__</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">value</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_target</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">value</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">get_log</span><span class="p">(</span><span class="bp">self</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">_log</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">class</span> <span class="nc">Database</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">query</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sql</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="n">sql</span><span class="si">}</span><span class="s2">&#34;</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">insert</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">table</span><span class="p">,</span> <span class="n">data</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="sa">f</span><span class="s2">&#34;插入到 </span><span class="si">{</span><span class="n">table</span><span class="si">}</span><span class="s2">&#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="n">db</span> <span class="o">=</span> <span class="n">LoggingProxy</span><span class="p">(</span><span class="n">Database</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="n">db</span><span class="o">.</span><span class="n">query</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">39</span><span class="cl"><span class="n">db</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="s2">&#34;users&#34;</span><span class="p">,</span> <span class="p">{</span><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">40</span><span class="cl">
</span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="k">for</span> <span class="n">entry</span> <span class="ow">in</span> <span class="n">db</span><span class="o">.</span><span class="n">get_log</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="n">entry</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="c1"># {&#39;method&#39;: &#39;query&#39;, &#39;args&#39;: (&#39;SELECT * FROM users&#39;,), &#39;kwargs&#39;: {}}</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="c1"># {&#39;method&#39;: &#39;insert&#39;, &#39;args&#39;: (&#39;users&#39;, {&#39;name&#39;: &#39;Alice&#39;}), &#39;kwargs&#39;: {}}</span></span></span></code></pre></div><h3 id="自動生成-api-文件">自動生成 API 文件</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">inspect</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">generate_api_doc</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;為類別生成簡單的 API 文件&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</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">cls</span><span class="o">.</span><span class="vm">__name__</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;&#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="k">if</span> <span class="bp">cls</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span><span class="bp">cls</span><span class="o">.</span><span class="vm">__doc__</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></span><span class="line"><span class="ln">10</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;## Methods&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</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">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">method</span> <span class="ow">in</span> <span class="n">inspect</span><span class="o">.</span><span class="n">getmembers</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">predicate</span><span class="o">=</span><span class="n">inspect</span><span class="o">.</span><span class="n">isfunction</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="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;_&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="k">continue</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">sig</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">doc</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</span><span class="n">getdoc</span><span class="p">(</span><span class="n">method</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;No description&#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="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">name</span><span class="si">}{</span><span class="n">sig</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="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">22</span><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">doc</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="k">return</span> <span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="k">class</span> <span class="nc">UserService</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="nf">create_user</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">email</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">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">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">            name: 用戶名稱
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">            email: 電子郵件
</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">        Returns:
</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 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">def</span> <span class="nf">get_user</span><span class="p">(</span><span class="bp">self</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="nb">dict</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="k">pass</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">generate_api_doc</span><span class="p">(</span><span class="n">UserService</span><span class="p">))</span></span></span></code></pre></div><hr>
<h2 id="框架應用">【框架應用】</h2>
<h3 id="pytest-的反射使用">pytest 的反射使用</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"># pytest 使用反射來發現測試</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></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">discover_tests</span><span class="p">(</span><span class="n">module</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬 pytest 的測試發現&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">tests</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">obj</span> <span class="ow">in</span> <span class="n">inspect</span><span class="o">.</span><span class="n">getmembers</span><span class="p">(</span><span class="n">module</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">name</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;test_&#39;</span><span class="p">)</span> <span class="ow">and</span> <span class="n">inspect</span><span class="o">.</span><span class="n">isfunction</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="n">tests</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">obj</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">return</span> <span class="n">tests</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 使用</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">def</span> <span class="nf">test_addition</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">assert</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">==</span> <span class="mi">2</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">test_subtraction</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="k">assert</span> <span class="mi">2</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">==</span> <span class="mi">1</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">helper_function</span><span class="p">():</span>  <span class="c1"># 不會被發現</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="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">tests</span> <span class="o">=</span> <span class="n">discover_tests</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="vm">__name__</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="k">for</span> <span class="n">test</span> <span class="ow">in</span> <span class="n">tests</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;發現測試: </span><span class="si">{</span><span class="n">test</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></code></pre></div><h3 id="fastapi-的參數解析">FastAPI 的參數解析</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">inspect</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">get_type_hints</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">parse_function_params</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;模擬 FastAPI 的參數解析&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">sig</span> <span class="o">=</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">hints</span> <span class="o">=</span> <span class="n">get_type_hints</span><span class="p">(</span><span class="n">func</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">params</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">param</span> <span class="ow">in</span> <span class="n">sig</span><span class="o">.</span><span class="n">parameters</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">params</span><span class="o">.</span><span class="n">append</span><span class="p">({</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">            <span class="s1">&#39;type&#39;</span><span class="p">:</span> <span class="n">hints</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="s1">&#39;Any&#39;</span><span class="p">),</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="s1">&#39;default&#39;</span><span class="p">:</span> <span class="kc">None</span> <span class="k">if</span> <span class="n">param</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="n">inspect</span><span class="o">.</span><span class="n">Parameter</span><span class="o">.</span><span class="n">empty</span> <span class="k">else</span> <span class="n">param</span><span class="o">.</span><span class="n">default</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">            <span class="s1">&#39;required&#39;</span><span class="p">:</span> <span class="n">param</span><span class="o">.</span><span class="n">default</span> <span class="ow">is</span> <span class="n">inspect</span><span class="o">.</span><span class="n">Parameter</span><span class="o">.</span><span class="n">empty</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">return</span> <span class="n">params</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">create_user</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">age</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">active</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">params</span> <span class="o">=</span> <span class="n">parse_function_params</span><span class="p">(</span><span class="n">create_user</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">params</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="c1"># {&#39;name&#39;: &#39;name&#39;, &#39;type&#39;: &lt;class &#39;str&#39;&gt;, &#39;default&#39;: None, &#39;required&#39;: True}</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="c1"># {&#39;name&#39;: &#39;age&#39;, &#39;type&#39;: &lt;class &#39;int&#39;&gt;, &#39;default&#39;: None, &#39;required&#39;: True}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># {&#39;name&#39;: &#39;active&#39;, &#39;type&#39;: &lt;class &#39;bool&#39;&gt;, &#39;default&#39;: True, &#39;required&#39;: False}</span></span></span></code></pre></div><hr>
<h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>hasattr</code> 可能會觸發副作用？什麼情況下應該避免使用？</li>
<li><code>__getattr__</code> 和 <code>__getattribute__</code> 在效能上有什麼差異？</li>
<li>如何用反射實作一個簡單的依賴注入框架？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>實作一個 <code>@deprecated</code> 裝飾器，使用 <code>inspect</code> 記錄呼叫位置</li>
<li>建立一個簡單的 RPC 框架，根據方法名稱動態呼叫遠端方法</li>
<li>實作一個物件比較工具，使用反射比較兩個物件的所有屬性</li>
</ol>
<h2 id="延伸閱讀">延伸閱讀</h2>
<ul>
<li><a href="https://docs.python.org/3/library/inspect.html">Python 官方 - inspect 模組</a></li>
<li><a href="https://docs.python.org/3/library/functions.html">Python 官方 - 內建函式</a></li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python-advanced/02-metaprogramming/class-creation/" data-link-title="2.3 類別裝飾器與動態類別" data-link-desc="使用類別裝飾器和 type() 動態建立類別">類別裝飾器與動態類別</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>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>案例研究：元編程實戰</title><link>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/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/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Descriptor Protocol&lt;/td>
 &lt;td>中階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制&lt;/a>&lt;/td>
 &lt;td>hook_validator.py&lt;/td>
 &lt;td>Metaclass&lt;/td>
 &lt;td>高階&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field&lt;/a>&lt;/td>
 &lt;td>hook_io.py&lt;/td>
 &lt;td>Descriptor + dataclass&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">類似 Django Field（整合）&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/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>返回：&lt;a href="https://tarrragon.github.io/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程&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/02-metaprogramming/case-studies/declarative-validation/" data-link-title="案例：宣告式驗證" data-link-desc="用 Descriptor Protocol 將驗證邏輯從方法變成屬性定義">宣告式驗證</a></td>
          <td>hook_validator.py</td>
          <td>Descriptor Protocol</td>
          <td>中階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a></td>
          <td>hook_validator.py</td>
          <td>Metaclass</td>
          <td>高階</td>
      </tr>
      <tr>
          <td><a href="/blog/python-advanced/02-metaprogramming/case-studies/field-descriptor/" data-link-title="案例：類似 Django Field 的設計" data-link-desc="結合 Descriptor 和 dataclass 設計類似 Django Model Field 的宣告式 API">類似 Django Field</a></td>
          <td>hook_io.py</td>
          <td>Descriptor + dataclass</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">類似 Django Field（整合）</span></span></code></pre></div><h2 id="先備知識">先備知識</h2>
<p>建議先完成以下章節：</p>
<ul>
<li><a href="/blog/python-advanced/02-metaprogramming/descriptors/" data-link-title="2.1 Descriptor Protocol 完整指南" data-link-desc="深入理解 Python 的 Descriptor Protocol，@property 的本質">2.1 Descriptor Protocol 完整指南</a></li>
<li><a href="/blog/python-advanced/02-metaprogramming/metaclasses/" data-link-title="2.2 Metaclass 設計與應用" data-link-desc="理解 Python 的類別建立機制與 Metaclass">2.2 Metaclass 設計與應用</a></li>
</ul>
<hr>
<p>返回：<a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">模組二：元編程</a></p>
]]></content:encoded></item></channel></rss>