<?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>Inheritance on Tarragon</title><link>https://tarrragon.github.io/blog/tags/inheritance/</link><description>Recent content in Inheritance on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 30 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/inheritance/index.xml" rel="self" type="application/rss+xml"/><item><title>4.2 抽象基類 ABC</title><link>https://tarrragon.github.io/blog/python/04-oop/abc/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/04-oop/abc/</guid><description>&lt;p>抽象基類（Abstract Base Class，ABC）用於定義介面契約，確保子類別實作必要的方法。這在建立可擴展的框架時特別有用。&lt;/p>
&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">JsonParser&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">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"> 3&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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">YamlParser&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="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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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="k">class&lt;/span> &lt;span class="nc">XmlParser&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"># 忘記實作 parse 方法！&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">read&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 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="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>問題：沒有強制的介面，容易出錯。&lt;/p>
&lt;h3 id="使用-abc-解決">使用 ABC 解決&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">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>&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">BaseParser&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;解析器抽象基類&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;解析內容並返回字典&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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">XmlParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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="c1"># 如果沒有實作 parse，會報錯&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">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">14&lt;/span>&lt;span class="cl"> &lt;span class="o">...&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="基本語法">基本語法&lt;/h2>
&lt;h3 id="定義抽象基類">定義抽象基類&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kn">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">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">BaseParser&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>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&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;utf-8&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encoding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encoding&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="nd">@abstractmethod&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">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">12&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解析內容（子類別必須實作）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&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"> content: 要解析的內容
&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: 解析後的資料
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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="nd">@abstractmethod&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">def&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證內容格式（子類別必須實作）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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">parse_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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="nb">dict&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"> 解析檔案（共用方法）
&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"> 這是具體實作，子類別可以直接使用或覆寫。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">with&lt;/span> &lt;span class="nb">open&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">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&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">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;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">JsonParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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;JSON 解析器&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">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"> 5&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">bool&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">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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="kc">True&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">except&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">JSONDecodeError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&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">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">YamlParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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;YAML 解析器&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>&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">return&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">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">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">bool&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">try&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">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">safe_load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&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">return&lt;/span> &lt;span class="kc">True&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">except&lt;/span> &lt;span class="n">yaml&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">YAMLError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="抽象屬性">抽象屬性&lt;/h2>
&lt;p>除了方法，也可以定義抽象屬性：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-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>&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">BaseParser&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"> 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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">file_extension&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"> 8&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;支援的檔案副檔名&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&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">13&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">mime_type&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">14&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;MIME 類型&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 class="k">pass&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">JsonParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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="nd">@property&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">file_extension&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">21&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;.json&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">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="nd">@property&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">def&lt;/span> &lt;span class="nf">mime_type&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">25&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="範本方法模式">範本方法模式&lt;/h2>
&lt;p>ABC 常與範本方法模式搭配使用：&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>&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">DataProcessor&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;資料處理器基類&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">process&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 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"> 7&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2"> 範本方法：定義處理流程
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2"> 1. 驗證
&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"> 2. 前處理
&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"> 3. 解析
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="s2"> 4. 後處理
&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;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 步驟 1：驗證（子類別實作）&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="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">validate&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">17&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 data&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"># 步驟 2：前處理（可選覆寫）&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">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">preprocess&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">21&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 步驟 3：解析（子類別實作）&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&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">24&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="c1"># 步驟 4：後處理（可選覆寫）&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">postprocess&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">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="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&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 class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">30&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證資料（子類別必須實作）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">31&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">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">data&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">35&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;解析資料（子類別必須實作）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;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">preprocess&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 class="nb">str&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">39&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;前處理（預設不做任何處理）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">postprocess&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">result&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">43&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;後處理（預設不做任何處理）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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">class&lt;/span> &lt;span class="nc">BaseParser&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">2&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">3&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">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="c1"># 錯誤：無法實例化抽象類別&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">BaseParser&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># TypeError!&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">IncompleteParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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"># 沒有實作 parse 方法&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">pass&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">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">IncompleteParser&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># TypeError!&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&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">abc&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ABC&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">BaseParser&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"> 4&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">JsonParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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"># 使用 isinstance 和 issubclass&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">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">JsonParser&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="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">parser&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BaseParser&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">12&lt;/span>&lt;span class="cl">&lt;span class="nb">issubclass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">JsonParser&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">BaseParser&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;h2 id="與-protocol-的比較">與 Protocol 的比較&lt;/h2>
&lt;p>Python 3.8+ 引入了 &lt;code>Protocol&lt;/code>，提供結構化子型別（Structural Subtyping）：&lt;/p></description><content:encoded><![CDATA[<p>抽象基類（Abstract Base Class，ABC）用於定義介面契約，確保子類別實作必要的方法。這在建立可擴展的框架時特別有用。</p>
<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">JsonParser</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">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"> 3</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</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">YamlParser</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">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"> 7</span><span class="cl">        <span class="k">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">content</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">XmlParser</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># 忘記實作 parse 方法！</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">def</span> <span class="nf">read</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 class="c1"># 方法名稱錯誤</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="o">...</span></span></span></code></pre></div><p>問題：沒有強制的介面，容易出錯。</p>
<h3 id="使用-abc-解決">使用 ABC 解決</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">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;解析器抽象基類&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">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"> 8</span><span class="cl">        <span class="s2">&#34;&#34;&#34;解析內容並返回字典&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="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">XmlParser</span><span class="p">(</span><span class="n">BaseParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># 如果沒有實作 parse，會報錯</span>
</span></span><span class="line"><span class="ln">13</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">14</span><span class="cl">        <span class="o">...</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="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">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">BaseParser</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></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">encoding</span> <span class="o">=</span> <span class="n">encoding</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">11</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">12</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        解析內容（子類別必須實作）
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">            content: 要解析的內容
</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: 解析後的資料
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">24</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">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證內容格式（子類別必須實作）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">pass</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">parse_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</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">        解析檔案（共用方法）
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">        這是具體實作，子類別可以直接使用或覆寫。
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">encoding</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><h3 id="實作子類別">實作子類別</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">JsonParser</span><span class="p">(</span><span class="n">BaseParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;JSON 解析器&#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">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"> 5</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</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="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">except</span> <span class="n">json</span><span class="o">.</span><span class="n">JSONDecodeError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">class</span> <span class="nc">YamlParser</span><span class="p">(</span><span class="n">BaseParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;&#34;&#34;YAML 解析器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">def</span> <span class="nf">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">return</span> <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="ow">or</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">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">yaml</span><span class="o">.</span><span class="n">safe_load</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        <span class="k">except</span> <span class="n">yaml</span><span class="o">.</span><span class="n">YAMLError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="k">return</span> <span class="kc">False</span></span></span></code></pre></div><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></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">def</span> <span class="nf">file_extension</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="s2">&#34;&#34;&#34;支援的檔案副檔名&#34;&#34;&#34;</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="nd">@property</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">def</span> <span class="nf">mime_type</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="s2">&#34;&#34;&#34;MIME 類型&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="k">pass</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">JsonParser</span><span class="p">(</span><span class="n">BaseParser</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="nd">@property</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">file_extension</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">21</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;.json&#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="nd">@property</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="k">def</span> <span class="nf">mime_type</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">25</span><span class="cl">        <span class="k">return</span> <span class="s2">&#34;application/json&#34;</span></span></span></code></pre></div><h2 id="範本方法模式">範本方法模式</h2>
<p>ABC 常與範本方法模式搭配使用：</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></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">DataProcessor</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;資料處理器基類&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">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"> 7</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        範本方法：定義處理流程
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">        1. 驗證
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        2. 前處理
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        3. 解析
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">        4. 後處理
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="c1"># 步驟 1：驗證（子類別實作）</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Invalid data&#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"># 步驟 2：前處理（可選覆寫）</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">preprocess</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="c1"># 步驟 3：解析（子類別實作）</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="c1"># 步驟 4：後處理（可選覆寫）</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">postprocess</span><span class="p">(</span><span class="n">result</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">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">30</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證資料（子類別必須實作）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">34</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">data</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">35</span><span class="cl">        <span class="s2">&#34;&#34;&#34;解析資料（子類別必須實作）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">        <span class="k">pass</span>
</span></span><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">preprocess</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="nb">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">39</span><span class="cl">        <span class="s2">&#34;&#34;&#34;前處理（預設不做任何處理）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">        <span class="k">return</span> <span class="n">data</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">postprocess</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">result</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">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">return</span> <span class="n">result</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="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">3</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">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"># 錯誤：無法實例化抽象類別</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">BaseParser</span><span class="p">()</span>  <span class="c1"># TypeError!</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">IncompleteParser</span><span class="p">(</span><span class="n">BaseParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 沒有實作 parse 方法</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">pass</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">parser</span> <span class="o">=</span> <span class="n">IncompleteParser</span><span class="p">()</span>  <span class="c1"># TypeError!</span></span></span></code></pre></div><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">abc</span> <span class="kn">import</span> <span class="n">ABC</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">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="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">JsonParser</span><span class="p">(</span><span class="n">BaseParser</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"># 使用 isinstance 和 issubclass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">JsonParser</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nb">isinstance</span><span class="p">(</span><span class="n">parser</span><span class="p">,</span> <span class="n">BaseParser</span><span class="p">)</span>     <span class="c1"># True</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nb">issubclass</span><span class="p">(</span><span class="n">JsonParser</span><span class="p">,</span> <span class="n">BaseParser</span><span class="p">)</span> <span class="c1"># True</span></span></span></code></pre></div><h2 id="與-protocol-的比較">與 Protocol 的比較</h2>
<p>Python 3.8+ 引入了 <code>Protocol</code>，提供結構化子型別（Structural Subtyping）：</p>
<h3 id="abc名義子型別">ABC（名義子型別）</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">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">Parseable</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># 必須明確繼承</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">JsonParser</span><span class="p">(</span><span class="n">Parseable</span><span class="p">):</span>  <span class="c1"># 必須繼承 Parseable</span>
</span></span><span class="line"><span class="ln">10</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">11</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><h3 id="protocol結構化子型別">Protocol（結構化子型別）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">Parseable</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">def</span> <span class="nf">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"> 5</span><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 不需要繼承，只要有相同的方法</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">JsonParser</span><span class="p">:</span>  <span class="c1"># 不需要繼承</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">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">10</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</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">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">parser</span><span class="p">:</span> <span class="n">Parseable</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="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">&#34;...&#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"># JsonParser 自動符合 Parseable 協議</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">process</span><span class="p">(</span><span class="n">JsonParser</span><span class="p">())</span>  <span class="c1"># OK</span></span></span></code></pre></div><h3 id="何時使用哪個">何時使用哪個？</h3>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>ABC</th>
          <th>Protocol</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>是（3.8+）</td>
      </tr>
      <tr>
          <td>適合場景</td>
          <td>框架設計</td>
          <td>型別提示</td>
      </tr>
  </tbody>
</table>
<h2 id="最佳實踐">最佳實踐</h2>
<h3 id="1-保持介面簡潔">1. 保持介面簡潔</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 好：專注於核心方法</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln"> 4</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"> 5</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 不好：太多抽象方法</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">pass</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">12</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="k">pass</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">def</span> <span class="nf">preprocess</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">pass</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="k">def</span> <span class="nf">postprocess</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">pass</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">pass</span></span></span></code></pre></div><h3 id="2-提供有用的預設實作">2. 提供有用的預設實作</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">3</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">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"># 有預設實作的方法</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">())</span></span></span></code></pre></div><h3 id="3-清晰的文檔">3. 清晰的文檔</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</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">    所有解析器都應繼承此類別並實作 parse 方法。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        class MyParser(BaseParser):
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">            def parse(self, content: str) -&gt; dict:
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">                return {&#34;data&#34;: content}
</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="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">14</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">15</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        解析內容
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">            content: 要解析的字串內容
</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">        Returns:
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">            dict: 解析後的字典
</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">        Raises:
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">            ParseError: 如果解析失敗
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>什麼時候應該使用 ABC，什麼時候使用 Protocol？</li>
<li>抽象方法可以有實作嗎？有什麼用途？</li>
<li>如何測試抽象基類？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>設計一個 <code>BaseValidator</code> 抽象基類，定義驗證器的介面</li>
<li>實作兩個繼承自 <code>BaseValidator</code> 的具體驗證器</li>
<li>使用範本方法模式實作一個多步驟的資料處理流程</li>
</ol>
<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 的本質">Descriptor Protocol 完整指南</a> - 理解 @property 背後的原理</li>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">插件系統設計</a> - 用 ABC 建構可擴展的插件架構</li>
<li><a href="/blog/python-advanced/02-metaprogramming/" data-link-title="模組二：元編程" data-link-desc="深入 Python 的元編程機制，理解框架的實現原理">元編程</a> - ABC 背後的 metaclass 機制</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python/04-oop/class-design/" data-link-title="4.1 類別設計原則" data-link-desc="設計清晰的類別介面">類別設計原則</a></em>
<em>下一章：<a href="/blog/python/04-oop/factory/" data-link-title="4.3 工廠模式" data-link-desc="動態建立物件">工廠模式</a></em></p>
]]></content:encoded></item><item><title>6.3 如何新增語言解析器</title><link>https://tarrragon.github.io/blog/python/06-practical/new-parser/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/06-practical/new-parser/</guid><description>&lt;p>本章示範如何透過繼承抽象基類來新增語言解析器。這是一個完整的實作範例，展示了前面學到的 ABC、工廠模式和型別提示等概念。&lt;/p>
&lt;h2 id="前置知識">前置知識&lt;/h2>
&lt;p>建議先閱讀：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/04-oop/abc/" data-link-title="4.2 抽象基類 ABC" data-link-desc="定義介面契約">4.2 抽象基類 ABC&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/04-oop/factory/" data-link-title="4.3 工廠模式" data-link-desc="動態建立物件">4.3 工廠模式&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/02-type-system/type-hints/" data-link-title="2.1 Type Hints 基礎" data-link-desc="為函式添加型別註解，提升程式碼可讀性">2.1 Type Hints 基礎&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="場景說明">場景說明&lt;/h2>
&lt;p>假設 Hook 系統需要支援新的配置格式（例如 TOML），我們需要：&lt;/p>
&lt;ol>
&lt;li>建立繼承自 &lt;code>BaseParser&lt;/code> 的 &lt;code>TomlParser&lt;/code> 類別&lt;/li>
&lt;li>實作所有抽象方法&lt;/li>
&lt;li>註冊到工廠&lt;/li>
&lt;li>撰寫測試&lt;/li>
&lt;/ol>
&lt;h2 id="步驟-1了解基類介面">步驟 1：了解基類介面&lt;/h2>
&lt;p>首先檢視抽象基類的定義：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># parsers/base.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="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"> 3&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&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">BaseParser&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"> 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="n">encoding&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;utf-8&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encoding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encoding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> &lt;span class="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">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">13&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="s2"> 解析內容
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&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"> content: 要解析的字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&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"> dict: 解析後的字典
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&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"> ParseError: 解析失敗時拋出
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="nd">@abstractmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">bool&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"> 驗證內容格式是否正確
&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"> content: 要驗證的字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&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"> bool: 格式正確返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&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">pass&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="nd">@property&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&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">42&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">file_extensions&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">43&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;支援的檔案副檔名列表&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&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="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="k">def&lt;/span> &lt;span class="nf">parse_file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">48&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;解析檔案&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">49&lt;/span>&lt;span class="cl"> &lt;span class="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">50&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encoding&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="o">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-2實作新解析器">步驟 2：實作新解析器&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="c1"># parsers/toml_parser.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">TOML 解析器
&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">支援 TOML 格式的配置檔案解析。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&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">.base&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseParser&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"># 嘗試導入 toml 模組&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">try&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="kn">import&lt;/span> &lt;span class="nn">tomllib&lt;/span> &lt;span class="c1"># Python 3.11+ 內建&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">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&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">16&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">toml&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">tomllib&lt;/span> &lt;span class="c1"># 第三方套件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">tomllib&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TomlParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseParser&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"> TOML 格式解析器
&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"> 支援 .toml 檔案的解析。
&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">
&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"> Example:
&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"> parser = TomlParser()
&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"> config = parser.parse_file(&amp;#34;config.toml&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"> &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>&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="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">encoding&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;utf-8&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl">&lt;span class="s2"> 初始化 TOML 解析器
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="s2"> encoding: 檔案編碼
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="s2"> Raises:
&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"> ImportError: 如果 toml 模組不可用
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">40&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">tomllib&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">42&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>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;TOML parser requires &amp;#39;tomllib&amp;#39; (Python 3.11+) &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;or &amp;#39;toml&amp;#39; package. Install with: pip install toml&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="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="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">encoding&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>&lt;/span>&lt;span class="line">&lt;span class="ln">48&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">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="s2"> 解析 TOML 內容
&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">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">52&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&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"> content: TOML 格式的字串
&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"> Returns:
&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"> dict: 解析後的字典
&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"> Raises:
&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"> ValueError: 如果 TOML 格式錯誤
&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;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">61&lt;/span>&lt;span class="cl"> &lt;span class="k">try&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="c1"># Python 3.11+ 的 tomllib 需要 bytes&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="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tomllib&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;loads&amp;#39;&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">return&lt;/span> &lt;span class="n">tomllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&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">65&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">66&lt;/span>&lt;span class="cl"> &lt;span class="c1"># tomllib (3.11+) 只接受 bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">67&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">tomllib&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">encode&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">encoding&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">68&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">69&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;Failed to parse TOML: &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 class="kn">from&lt;/span> &lt;span class="nn">e&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">70&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">71&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">bool&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">72&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">73&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證 TOML 格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">74&lt;/span>&lt;span class="cl">&lt;span class="s2">
&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"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">76&lt;/span>&lt;span class="cl">&lt;span class="s2"> content: 要驗證的字串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">77&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">78&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&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"> bool: 格式正確返回 True
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">80&lt;/span>&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">81&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">82&lt;/span>&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&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">83&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">84&lt;/span>&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">ValueError&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>&lt;/span>&lt;span class="line">&lt;span class="ln">87&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">88&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">file_extensions&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">89&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;支援的副檔名&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">90&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;.toml&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-3註冊到工廠">步驟 3：註冊到工廠&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="c1"># parsers/__init__.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="s2">解析器模組
&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="kn">from&lt;/span> &lt;span class="nn">.base&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseParser&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">.factory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ParserFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.json_parser&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">JsonParser&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">from&lt;/span> &lt;span class="nn">.yaml_parser&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">YamlParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="c1"># 註冊內建解析器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="n">ParserFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;.json&amp;#34;&lt;/span>&lt;span class="p">])(&lt;/span>&lt;span class="n">JsonParser&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">ParserFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;yaml&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;.yaml&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;.yml&amp;#34;&lt;/span>&lt;span class="p">])(&lt;/span>&lt;span class="n">YamlParser&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="c1"># 嘗試註冊 TOML 解析器（可選依賴）&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">try&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="kn">from&lt;/span> &lt;span class="nn">.toml_parser&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TomlParser&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">ParserFactory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">register&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;toml&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;.toml&amp;#34;&lt;/span>&lt;span class="p">])(&lt;/span>&lt;span class="n">TomlParser&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">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span> &lt;span class="c1"># TOML 支援不可用&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="n">__all__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;BaseParser&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="s2">&amp;#34;ParserFactory&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;JsonParser&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;YamlParser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">29&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或者使用裝飾器直接在類別定義時註冊：&lt;/p></description><content:encoded><![CDATA[<p>本章示範如何透過繼承抽象基類來新增語言解析器。這是一個完整的實作範例，展示了前面學到的 ABC、工廠模式和型別提示等概念。</p>
<h2 id="前置知識">前置知識</h2>
<p>建議先閱讀：</p>
<ul>
<li><a href="/blog/python/04-oop/abc/" data-link-title="4.2 抽象基類 ABC" data-link-desc="定義介面契約">4.2 抽象基類 ABC</a></li>
<li><a href="/blog/python/04-oop/factory/" data-link-title="4.3 工廠模式" data-link-desc="動態建立物件">4.3 工廠模式</a></li>
<li><a href="/blog/python/02-type-system/type-hints/" data-link-title="2.1 Type Hints 基礎" data-link-desc="為函式添加型別註解，提升程式碼可讀性">2.1 Type Hints 基礎</a></li>
</ul>
<h2 id="場景說明">場景說明</h2>
<p>假設 Hook 系統需要支援新的配置格式（例如 TOML），我們需要：</p>
<ol>
<li>建立繼承自 <code>BaseParser</code> 的 <code>TomlParser</code> 類別</li>
<li>實作所有抽象方法</li>
<li>註冊到工廠</li>
<li>撰寫測試</li>
</ol>
<h2 id="步驟-1了解基類介面">步驟 1：了解基類介面</h2>
<p>首先檢視抽象基類的定義：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># parsers/base.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">BaseParser</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="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="n">encoding</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">encoding</span> <span class="o">=</span> <span class="n">encoding</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">12</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">13</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">        解析內容
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">            content: 要解析的字串
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">            dict: 解析後的字典
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">            ParseError: 解析失敗時拋出
</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="k">pass</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">28</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">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">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">        驗證內容格式是否正確
</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">            content: 要驗證的字串
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        Returns:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">            bool: 格式正確返回 True
</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">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="nd">@property</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="nd">@abstractmethod</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="k">def</span> <span class="nf">file_extensions</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">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="c1"># 共用方法（不是抽象的）</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="s2">&#34;&#34;&#34;解析檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">49</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">50</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">encoding</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="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><h2 id="步驟-2實作新解析器">步驟 2：實作新解析器</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="c1"># parsers/toml_parser.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">TOML 解析器
</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">支援 TOML 格式的配置檔案解析。
</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">typing</span> <span class="kn">import</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">from</span> <span class="nn">.base</span> <span class="kn">import</span> <span class="n">BaseParser</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"># 嘗試導入 toml 模組</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="kn">import</span> <span class="nn">tomllib</span>  <span class="c1"># Python 3.11+ 內建</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="kn">import</span> <span class="nn">toml</span> <span class="k">as</span> <span class="nn">tomllib</span>  <span class="c1"># 第三方套件</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">tomllib</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">class</span> <span class="nc">TomlParser</span><span class="p">(</span><span class="n">BaseParser</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">    TOML 格式解析器
</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">    支援 .toml 檔案的解析。
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        parser = TomlParser()
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">        config = parser.parse_file(&#34;config.toml&#34;)
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">encoding</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">        初始化 TOML 解析器
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">            encoding: 檔案編碼
</span></span></span><span class="line"><span class="ln">37</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="s2">        Raises:
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">            ImportError: 如果 toml 模組不可用
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">        <span class="k">if</span> <span class="n">tomllib</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="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">                <span class="s2">&#34;TOML parser requires &#39;tomllib&#39; (Python 3.11+) &#34;</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">                <span class="s2">&#34;or &#39;toml&#39; package. Install with: pip install toml&#34;</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">encoding</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="k">def</span> <span class="nf">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">49</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="s2">        解析 TOML 內容
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="s2">            content: TOML 格式的字串
</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">            dict: 解析後的字典
</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">        Raises:
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="s2">            ValueError: 如果 TOML 格式錯誤
</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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">            <span class="c1"># Python 3.11+ 的 tomllib 需要 bytes</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">            <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">tomllib</span><span class="p">,</span> <span class="s1">&#39;loads&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">                <span class="k">return</span> <span class="n">tomllib</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">            <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">                <span class="c1"># tomllib (3.11+) 只接受 bytes</span>
</span></span><span class="line"><span class="ln">67</span><span class="cl">                <span class="k">return</span> <span class="n">tomllib</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">content</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">encoding</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="k">except</span> <span class="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">69</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;Failed to parse TOML: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl">    <span class="k">def</span> <span class="nf">validate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl">        <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">73</span><span class="cl"><span class="s2">        驗證 TOML 格式
</span></span></span><span class="line"><span class="ln">74</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">75</span><span class="cl"><span class="s2">        Args:
</span></span></span><span class="line"><span class="ln">76</span><span class="cl"><span class="s2">            content: 要驗證的字串
</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">        Returns:
</span></span></span><span class="line"><span class="ln">79</span><span class="cl"><span class="s2">            bool: 格式正確返回 True
</span></span></span><span class="line"><span class="ln">80</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">81</span><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">82</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</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">True</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="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></span><span class="line"><span class="ln">87</span><span class="cl">    <span class="nd">@property</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">    <span class="k">def</span> <span class="nf">file_extensions</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">89</span><span class="cl">        <span class="s2">&#34;&#34;&#34;支援的副檔名&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">90</span><span class="cl">        <span class="k">return</span> <span class="p">[</span><span class="s2">&#34;.toml&#34;</span><span class="p">]</span></span></span></code></pre></div><h2 id="步驟-3註冊到工廠">步驟 3：註冊到工廠</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="c1"># parsers/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">解析器模組
</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="kn">from</span> <span class="nn">.base</span> <span class="kn">import</span> <span class="n">BaseParser</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">from</span> <span class="nn">.factory</span> <span class="kn">import</span> <span class="n">ParserFactory</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">from</span> <span class="nn">.json_parser</span> <span class="kn">import</span> <span class="n">JsonParser</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">.yaml_parser</span> <span class="kn">import</span> <span class="n">YamlParser</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># 註冊內建解析器</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">ParserFactory</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="s2">&#34;json&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;.json&#34;</span><span class="p">])(</span><span class="n">JsonParser</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">ParserFactory</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="s2">&#34;yaml&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;.yaml&#34;</span><span class="p">,</span> <span class="s2">&#34;.yml&#34;</span><span class="p">])(</span><span class="n">YamlParser</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"># 嘗試註冊 TOML 解析器（可選依賴）</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="kn">from</span> <span class="nn">.toml_parser</span> <span class="kn">import</span> <span class="n">TomlParser</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">ParserFactory</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="s2">&#34;toml&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;.toml&#34;</span><span class="p">])(</span><span class="n">TomlParser</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="k">pass</span>  <span class="c1"># TOML 支援不可用</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">__all__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="s2">&#34;BaseParser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;ParserFactory&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;JsonParser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;YamlParser&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><p>或者使用裝飾器直接在類別定義時註冊：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># parsers/toml_parser.py</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">.factory</span> <span class="kn">import</span> <span class="n">ParserFactory</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kn">from</span> <span class="nn">.base</span> <span class="kn">import</span> <span class="n">BaseParser</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">@ParserFactory.register</span><span class="p">(</span><span class="s2">&#34;toml&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;.toml&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">class</span> <span class="nc">TomlParser</span><span class="p">(</span><span class="n">BaseParser</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="s2">&#34;&#34;&#34;TOML 解析器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="c1"># ... 實作 ...</span></span></span></code></pre></div><h2 id="步驟-4撰寫測試">步驟 4：撰寫測試</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="c1"># tests/test_toml_parser.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">TOML 解析器測試
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="kn">import</span> <span class="nn">unittest</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">patch</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"># 跳過測試如果 toml 不可用</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="kn">from</span> <span class="nn">parsers.toml_parser</span> <span class="kn">import</span> <span class="n">TomlParser</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">HAS_TOML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">HAS_TOML</span> <span class="o">=</span> <span class="kc">False</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">@unittest.skipUnless</span><span class="p">(</span><span class="n">HAS_TOML</span><span class="p">,</span> <span class="s2">&#34;TOML support not available&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="k">class</span> <span class="nc">TestTomlParser</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 TomlParser 類別&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試前準備&#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">parser</span> <span class="o">=</span> <span class="n">TomlParser</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">test_parse_simple_toml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試解析簡單的 TOML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="s2">        [server]
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="s2">        host = &#34;localhost&#34;
</span></span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="s2">        port = 8080
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</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="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;server&#34;</span><span class="p">][</span><span class="s2">&#34;host&#34;</span><span class="p">],</span> <span class="s2">&#34;localhost&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;server&#34;</span><span class="p">][</span><span class="s2">&#34;port&#34;</span><span class="p">],</span> <span class="mi">8080</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="k">def</span> <span class="nf">test_parse_nested_toml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試解析巢狀的 TOML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="s2">        [database]
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="s2">        host = &#34;localhost&#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">        [database.connection]
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="s2">        timeout = 30
</span></span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="s2">        retries = 3
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="s2">        &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">
</span></span><span class="line"><span class="ln">48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;database&#34;</span><span class="p">][</span><span class="s2">&#34;host&#34;</span><span class="p">],</span> <span class="s2">&#34;localhost&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;database&#34;</span><span class="p">][</span><span class="s2">&#34;connection&#34;</span><span class="p">][</span><span class="s2">&#34;timeout&#34;</span><span class="p">],</span> <span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">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">test_validate_valid_toml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試驗證有效的 TOML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="s1">&#39;[section]</span><span class="se">\n</span><span class="s1">key = &#34;value&#34;&#39;</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">assertTrue</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">content</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">def</span> <span class="nf">test_validate_invalid_toml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試驗證無效的 TOML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="s2">&#34;this is not valid TOML [&#34;</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">assertFalse</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">validate</span><span class="p">(</span><span class="n">content</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">test_parse_invalid_raises_error</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">62</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試解析無效 TOML 時拋出錯誤&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">63</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="s2">&#34;invalid [ toml&#34;</span>
</span></span><span class="line"><span class="ln">64</span><span class="cl">        <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">assertRaises</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">65</span><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">66</span><span class="cl">
</span></span><span class="line"><span class="ln">67</span><span class="cl">    <span class="k">def</span> <span class="nf">test_file_extensions</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">68</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試檔案副檔名&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">69</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIn</span><span class="p">(</span><span class="s2">&#34;.toml&#34;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">parser</span><span class="o">.</span><span class="n">file_extensions</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">70</span><span class="cl">
</span></span><span class="line"><span class="ln">71</span><span class="cl"><span class="nd">@unittest.skipUnless</span><span class="p">(</span><span class="n">HAS_TOML</span><span class="p">,</span> <span class="s2">&#34;TOML support not available&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">72</span><span class="cl"><span class="k">class</span> <span class="nc">TestTomlParserFactory</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">73</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 TOML 解析器與工廠的整合&#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="nf">test_factory_creates_toml_parser</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">76</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試工廠可以建立 TOML 解析器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">77</span><span class="cl">        <span class="kn">from</span> <span class="nn">parsers</span> <span class="kn">import</span> <span class="n">ParserFactory</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="n">parser</span> <span class="o">=</span> <span class="n">ParserFactory</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="s2">&#34;toml&#34;</span><span class="p">)</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">assertIsInstance</span><span class="p">(</span><span class="n">parser</span><span class="p">,</span> <span class="n">TomlParser</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">def</span> <span class="nf">test_factory_creates_from_file</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">83</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試工廠根據副檔名建立解析器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">84</span><span class="cl">        <span class="kn">from</span> <span class="nn">parsers</span> <span class="kn">import</span> <span class="n">ParserFactory</span>
</span></span><span class="line"><span class="ln">85</span><span class="cl">
</span></span><span class="line"><span class="ln">86</span><span class="cl">        <span class="n">parser</span> <span class="o">=</span> <span class="n">ParserFactory</span><span class="o">.</span><span class="n">create_from_file</span><span class="p">(</span><span class="s2">&#34;config.toml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">87</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertIsInstance</span><span class="p">(</span><span class="n">parser</span><span class="p">,</span> <span class="n">TomlParser</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">88</span><span class="cl">
</span></span><span class="line"><span class="ln">89</span><span class="cl"><span class="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">90</span><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span></span></span></code></pre></div><h2 id="步驟-5更新文件">步驟 5：更新文件</h2>
<p>在 README 或相關文件中記錄新功能：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="gu">## 支援的配置格式
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">-</span> JSON (.json) - 內建支援
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">-</span> YAML (.yaml, .yml) - 需要 PyYAML
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">-</span> TOML (.toml) - 需要 Python 3.11+ 或 toml 套件
</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="gu">### 安裝 TOML 支援
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s">```bash
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s"></span><span class="c1"># Python 3.11+ 內建支援</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">pip install toml
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s">```</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">parsers</span> <span class="kn">import</span> <span class="n">ParserFactory</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 自動選擇解析器</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">ParserFactory</span><span class="o">.</span><span class="n">create_from_file</span><span class="p">(</span><span class="s2">&#34;config.toml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_file</span><span class="p">(</span><span class="s2">&#34;config.toml&#34;</span><span class="p">)</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl">
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1">## 完整檢查清單</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">新增解析器時的檢查項目</span><span class="err">：</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="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">繼承</span> <span class="err">`</span><span class="n">BaseParser</span><span class="err">`</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">實作所有</span> <span class="err">`</span><span class="nd">@abstractmethod</span><span class="err">`</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="err">`</span><span class="n">parse</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="err">`</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="err">`</span><span class="n">validate</span><span class="p">(</span><span class="n">content</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="err">`</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="err">`</span><span class="n">file_extensions</span><span class="err">`</span> <span class="n">屬性</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">處理可選依賴</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">註冊到</span> <span class="err">`</span><span class="n">ParserFactory</span><span class="err">`</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">撰寫單元測試</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">正常解析</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">錯誤處理</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">驗證功能</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">工廠整合</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">更新文件</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="o">-</span> <span class="p">[</span> <span class="p">]</span> <span class="n">更新</span> <span class="err">`</span><span class="n">__all__</span><span class="err">`</span> <span class="n">匯出</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="c1">## 常見問題</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="c1">### Q: 如果依賴套件不可用怎麼辦？</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="n">在</span> <span class="err">`</span><span class="fm">__init__</span><span class="err">`</span> <span class="n">中檢查並提供清楚的錯誤訊息</span><span class="err">：</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="err">```</span><span class="n">python</span>
</span></span><span class="line"><span class="ln">28</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">29</span><span class="cl">    <span class="k">if</span> <span class="n">required_module</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="s2">&#34;TomlParser requires &#39;toml&#39; package. &#34;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="s2">&#34;Install with: pip install toml&#34;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">        <span class="p">)</span></span></span></code></pre></div><h3 id="q-如何處理不同版本的-api">Q: 如何處理不同版本的 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="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="kn">import</span> <span class="nn">tomllib</span>  <span class="c1"># Python 3.11+</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_toml</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">4</span><span class="cl">        <span class="k">return</span> <span class="n">tomllib</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="kn">import</span> <span class="nn">toml</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="k">def</span> <span class="nf">parse_toml</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">8</span><span class="cl">        <span class="k">return</span> <span class="n">toml</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span></span></span></code></pre></div><h3 id="q-解析錯誤應該拋出什麼異常">Q: 解析錯誤應該拋出什麼異常？</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">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">return</span> <span class="n">toml</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">except</span> <span class="n">toml</span><span class="o">.</span><span class="n">TomlDecodeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Invalid TOML: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼要將原始異常作為 <code>from e</code> 傳遞？</li>
<li>如何設計讓解析器支援串流處理（大檔案）？</li>
<li>如果要支援解析器鏈（先解密再解析），應該如何設計？</li>
</ol>
<h2 id="延伸閱讀進階系列">延伸閱讀（進階系列）</h2>
<ul>
<li><a href="/blog/python-advanced/03-design-patterns/plugin-system/" data-link-title="3.5.4 插件系統設計" data-link-desc="插件架構模式、動態載入模組、entry_points、實際範例">插件系統設計</a> - 更靈活的解析器註冊機制</li>
<li><a href="/blog/python-advanced/02-metaprogramming/case-studies/auto-registration/" data-link-title="案例：自動註冊機制" data-link-desc="用 Metaclass 實現檢查器的自動註冊，消除手動維護註冊表的負擔">自動註冊機制</a> - 用 metaclass 實現自動註冊</li>
<li><a href="/blog/python-advanced/03-design-patterns/case-studies/generic-validator/" data-link-title="案例：泛型驗證器" data-link-desc="用 Generic 和 TypeVar 建立型別安全的通用驗證器">泛型驗證器</a> - 泛型在驗證器設計中的應用</li>
</ul>
<hr>
<p><em>上一章：<a href="/blog/python/06-practical/extend-lib/" data-link-title="6.2 如何擴展共用模組" data-link-desc="為 Hook 系統添加新功能">如何擴展共用模組</a></em>
<em>回到首頁：<a href="/blog/python/" data-link-title="Python 維護工程師實戰指南" data-link-desc="以 Hook 系統為範例的 Python 開發教學">Python 維護工程師實戰指南</a></em></p>
]]></content:encoded></item><item><title>late final 欄位不能用欄位覆蓋 — Dart 欄位的隱藏 getter/setter 機制</title><link>https://tarrragon.github.io/blog/work-log/late-final-%E6%AC%84%E4%BD%8D%E4%B8%8D%E8%83%BD%E7%94%A8%E6%AC%84%E4%BD%8D%E8%A6%86%E8%93%8B-dart-%E6%AC%84%E4%BD%8D%E7%9A%84%E9%9A%B1%E8%97%8F-getter/setter-%E6%A9%9F%E5%88%B6/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/work-log/late-final-%E6%AC%84%E4%BD%8D%E4%B8%8D%E8%83%BD%E7%94%A8%E6%AC%84%E4%BD%8D%E8%A6%86%E8%93%8B-dart-%E6%AC%84%E4%BD%8D%E7%9A%84%E9%9A%B1%E8%97%8F-getter/setter-%E6%A9%9F%E5%88%B6/</guid><description>&lt;h2 id="事件">事件&lt;/h2>
&lt;p>&lt;code>AppService&lt;/code> 宣告了 &lt;code>late final PackageInfo packageInfo;&lt;/code>，在 &lt;code>init()&lt;/code> 中透過 &lt;code>PackageInfo.fromPlatform()&lt;/code> 非同步初始化。測試用的 &lt;code>TestAppService extends AppService&lt;/code> 想跳過平台呼叫，直接給一個固定值：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1">// ❌ analyzer 報錯：overridden_fields
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="n">override&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">late&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="n">PackageInfo&lt;/span> &lt;span class="n">packageInfo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PackageInfo&lt;/span>&lt;span class="p">(...);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Dart analyzer 報 &lt;code>overridden_fields&lt;/code>：欄位覆蓋了從 &lt;code>AppService&lt;/code> 繼承的欄位。&lt;/p>
&lt;h2 id="根因late-final-欄位--getter--setter">根因：late final 欄位 = getter + setter&lt;/h2>
&lt;p>Dart 的每個 instance 欄位在底層都會生成對應的 getter（和非 final 的會生成 setter）。&lt;code>late final&lt;/code> 更特殊——它生成的 getter 包含「是否已初始化」的檢查邏輯，setter 包含「只能寫入一次」的 guard。&lt;/p>
&lt;p>當子類用另一個欄位覆蓋時，記憶體中會有兩個獨立的儲存槽：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>存取來源&lt;/th>
 &lt;th>讀到的 slot&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>子類自己的程式碼&lt;/td>
 &lt;td>子類的 slot（已初始化）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>父類的程式碼（繼承的方法）&lt;/td>
 &lt;td>父類的 slot（未初始化）&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>父類的 &lt;code>init()&lt;/code> 或其他方法如果存取 &lt;code>packageInfo&lt;/code>，會讀到父類那份未初始化的 slot，拋出 &lt;code>LateInitializationError&lt;/code>。這就是為什麼 Dart 不允許用欄位覆蓋欄位。&lt;/p>
&lt;h2 id="修法改用-getter-覆寫">修法：改用 getter 覆寫&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="err">@&lt;/span>&lt;span class="n">override&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">PackageInfo&lt;/span> &lt;span class="kd">get&lt;/span> &lt;span class="n">packageInfo&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="n">PackageInfo&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="nl">appName:&lt;/span> &lt;span class="s1">&amp;#39;UniPos Test&amp;#39;&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="nl">packageName:&lt;/span> &lt;span class="s1">&amp;#39;com.mxkj.unipos.test&amp;#39;&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="nl">version:&lt;/span> &lt;span class="s1">&amp;#39;1.0.0&amp;#39;&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="nl">buildNumber:&lt;/span> &lt;span class="s1">&amp;#39;1&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="nl">buildSignature:&lt;/span> &lt;span class="s1">&amp;#39;test&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="nl">installerStore:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Getter 覆寫只有一個讀取入口——不管父類還是子類的程式碼呼叫 &lt;code>packageInfo&lt;/code>，都走子類的 getter。不會有兩份 slot 的問題。&lt;/p>
&lt;h2 id="通則">通則&lt;/h2>
&lt;p>Dart 中覆寫父類的欄位，一律用 getter（必要時加 setter），不要用欄位。這不只適用於 &lt;code>late final&lt;/code>——所有欄位覆蓋都有同樣的雙 slot 風險，只是 &lt;code>late final&lt;/code> 的症狀最明顯（直接拋 &lt;code>LateInitializationError&lt;/code>），普通欄位的症狀更隱蔽（值不同步）。&lt;/p></description><content:encoded><![CDATA[<h2 id="事件">事件</h2>
<p><code>AppService</code> 宣告了 <code>late final PackageInfo packageInfo;</code>，在 <code>init()</code> 中透過 <code>PackageInfo.fromPlatform()</code> 非同步初始化。測試用的 <code>TestAppService extends AppService</code> 想跳過平台呼叫，直接給一個固定值：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// ❌ analyzer 報錯：overridden_fields
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">late</span> <span class="kd">final</span> <span class="n">PackageInfo</span> <span class="n">packageInfo</span> <span class="o">=</span> <span class="n">PackageInfo</span><span class="p">(...);</span></span></span></code></pre></div><p>Dart analyzer 報 <code>overridden_fields</code>：欄位覆蓋了從 <code>AppService</code> 繼承的欄位。</p>
<h2 id="根因late-final-欄位--getter--setter">根因：late final 欄位 = getter + setter</h2>
<p>Dart 的每個 instance 欄位在底層都會生成對應的 getter（和非 final 的會生成 setter）。<code>late final</code> 更特殊——它生成的 getter 包含「是否已初始化」的檢查邏輯，setter 包含「只能寫入一次」的 guard。</p>
<p>當子類用另一個欄位覆蓋時，記憶體中會有兩個獨立的儲存槽：</p>
<table>
  <thead>
      <tr>
          <th>存取來源</th>
          <th>讀到的 slot</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>子類自己的程式碼</td>
          <td>子類的 slot（已初始化）</td>
      </tr>
      <tr>
          <td>父類的程式碼（繼承的方法）</td>
          <td>父類的 slot（未初始化）</td>
      </tr>
  </tbody>
</table>
<p>父類的 <code>init()</code> 或其他方法如果存取 <code>packageInfo</code>，會讀到父類那份未初始化的 slot，拋出 <code>LateInitializationError</code>。這就是為什麼 Dart 不允許用欄位覆蓋欄位。</p>
<h2 id="修法改用-getter-覆寫">修法：改用 getter 覆寫</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="err">@</span><span class="n">override</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">PackageInfo</span> <span class="kd">get</span> <span class="n">packageInfo</span> <span class="o">=&gt;</span> <span class="n">PackageInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nl">appName:</span> <span class="s1">&#39;UniPos Test&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nl">packageName:</span> <span class="s1">&#39;com.mxkj.unipos.test&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nl">version:</span> <span class="s1">&#39;1.0.0&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nl">buildNumber:</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nl">buildSignature:</span> <span class="s1">&#39;test&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="nl">installerStore:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">);</span></span></span></code></pre></div><p>Getter 覆寫只有一個讀取入口——不管父類還是子類的程式碼呼叫 <code>packageInfo</code>，都走子類的 getter。不會有兩份 slot 的問題。</p>
<h2 id="通則">通則</h2>
<p>Dart 中覆寫父類的欄位，一律用 getter（必要時加 setter），不要用欄位。這不只適用於 <code>late final</code>——所有欄位覆蓋都有同樣的雙 slot 風險，只是 <code>late final</code> 的症狀最明顯（直接拋 <code>LateInitializationError</code>），普通欄位的症狀更隱蔽（值不同步）。</p>
]]></content:encoded></item><item><title>Widget 子類重新宣告 key — 遮蔽父類屬性與 duplicate key 風險</title><link>https://tarrragon.github.io/blog/work-log/widget-%E5%AD%90%E9%A1%9E%E9%87%8D%E6%96%B0%E5%AE%A3%E5%91%8A-key-%E9%81%AE%E8%94%BD%E7%88%B6%E9%A1%9E%E5%B1%AC%E6%80%A7%E8%88%87-duplicate-key-%E9%A2%A8%E9%9A%AA/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/work-log/widget-%E5%AD%90%E9%A1%9E%E9%87%8D%E6%96%B0%E5%AE%A3%E5%91%8A-key-%E9%81%AE%E8%94%BD%E7%88%B6%E9%A1%9E%E5%B1%AC%E6%80%A7%E8%88%87-duplicate-key-%E9%A2%A8%E9%9A%AA/</guid><description>&lt;h2 id="事件">事件&lt;/h2>
&lt;p>測試用的 &lt;code>TestRiveAnimation extends StatelessWidget&lt;/code> 裡宣告了 &lt;code>final Key? key;&lt;/code>，constructor 中透過 &lt;code>super(key: key)&lt;/code> 傳給父類。Dart analyzer 警告 &lt;code>key&lt;/code> overrides an inherited member。&lt;/p>
&lt;p>加了 &lt;code>@override&lt;/code> 可以消除警告，但問題沒有解決——class 裡現在有兩個 &lt;code>key&lt;/code> slot（子類自己的和 &lt;code>Widget&lt;/code> 繼承的），而 &lt;code>build&lt;/code> 方法裡又寫了 &lt;code>Container(key: key)&lt;/code>，把同一個 key 同時掛在 parent widget 和 child widget 上。&lt;/p>
&lt;h2 id="根因">根因&lt;/h2>
&lt;p>&lt;code>Widget&lt;/code> 的 &lt;code>key&lt;/code> 是 &lt;code>final&lt;/code> 屬性，由 constructor 的 &lt;code>super(key:)&lt;/code> 設定。子類重新宣告同名欄位會產生 shadowing：&lt;/p>
&lt;ul>
&lt;li>子類的程式碼（包括 &lt;code>build&lt;/code>）讀到的是子類自己的那份 &lt;code>key&lt;/code>&lt;/li>
&lt;li>父類 &lt;code>Widget&lt;/code> 的框架程式碼讀到的是父類的那份 &lt;code>key&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>兩份值相同（因為 constructor 都有寫入），但語意上是兩個獨立的 slot。更危險的是，如果在 &lt;code>build&lt;/code> 裡把 &lt;code>key&lt;/code> 往下傳給 child，同一棵 widget 子樹會出現兩個相同的 &lt;code>Key&lt;/code> 值，Flutter 在 diff 時可能拋出 duplicate key 錯誤。&lt;/p>
&lt;h2 id="修法">修法&lt;/h2>
&lt;p>不要重新宣告 &lt;code>key&lt;/code>，改用 &lt;code>super.key&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dart" data-lang="dart">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="n">TestRiveAnimation&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">asset&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">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">asset&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">3&lt;/span>&lt;span class="cl"> &lt;span class="k">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// 直接傳給 Widget，不產生新 slot
&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">&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">useArtboardSize&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">5&lt;/span>&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>build&lt;/code> 裡也不要把 widget 自身的 key 再傳給 child——key 是給 framework 用來識別這個 widget 的，不該手動轉發。&lt;/p>
&lt;h2 id="判斷原則">判斷原則&lt;/h2>
&lt;p>在 Flutter 中，&lt;code>key&lt;/code>、&lt;code>hashCode&lt;/code>、&lt;code>runtimeType&lt;/code> 這類從 &lt;code>Widget&lt;/code> / &lt;code>Object&lt;/code> 繼承的屬性，子類永遠不該用欄位覆蓋。如果需要自訂行為，覆寫 getter。&lt;/p></description><content:encoded><![CDATA[<h2 id="事件">事件</h2>
<p>測試用的 <code>TestRiveAnimation extends StatelessWidget</code> 裡宣告了 <code>final Key? key;</code>，constructor 中透過 <code>super(key: key)</code> 傳給父類。Dart analyzer 警告 <code>key</code> overrides an inherited member。</p>
<p>加了 <code>@override</code> 可以消除警告，但問題沒有解決——class 裡現在有兩個 <code>key</code> slot（子類自己的和 <code>Widget</code> 繼承的），而 <code>build</code> 方法裡又寫了 <code>Container(key: key)</code>，把同一個 key 同時掛在 parent widget 和 child widget 上。</p>
<h2 id="根因">根因</h2>
<p><code>Widget</code> 的 <code>key</code> 是 <code>final</code> 屬性，由 constructor 的 <code>super(key:)</code> 設定。子類重新宣告同名欄位會產生 shadowing：</p>
<ul>
<li>子類的程式碼（包括 <code>build</code>）讀到的是子類自己的那份 <code>key</code></li>
<li>父類 <code>Widget</code> 的框架程式碼讀到的是父類的那份 <code>key</code></li>
</ul>
<p>兩份值相同（因為 constructor 都有寫入），但語意上是兩個獨立的 slot。更危險的是，如果在 <code>build</code> 裡把 <code>key</code> 往下傳給 child，同一棵 widget 子樹會出現兩個相同的 <code>Key</code> 值，Flutter 在 diff 時可能拋出 duplicate key 錯誤。</p>
<h2 id="修法">修法</h2>
<p>不要重新宣告 <code>key</code>，改用 <code>super.key</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dart" data-lang="dart"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">const</span> <span class="n">TestRiveAnimation</span><span class="p">.</span><span class="n">asset</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="k">this</span><span class="p">.</span><span class="n">asset</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="k">super</span><span class="p">.</span><span class="n">key</span><span class="p">,</span>           <span class="c1">// 直接傳給 Widget，不產生新 slot
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"></span>  <span class="k">this</span><span class="p">.</span><span class="n">useArtboardSize</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 class="p">});</span></span></span></code></pre></div><p><code>build</code> 裡也不要把 widget 自身的 key 再傳給 child——key 是給 framework 用來識別這個 widget 的，不該手動轉發。</p>
<h2 id="判斷原則">判斷原則</h2>
<p>在 Flutter 中，<code>key</code>、<code>hashCode</code>、<code>runtimeType</code> 這類從 <code>Widget</code> / <code>Object</code> 繼承的屬性，子類永遠不該用欄位覆蓋。如果需要自訂行為，覆寫 getter。</p>
]]></content:encoded></item></channel></rss>