<?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>Modules on Tarragon</title><link>https://tarrragon.github.io/blog/tags/modules/</link><description>Recent content in Modules on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 22 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/modules/index.xml" rel="self" type="application/rss+xml"/><item><title>模組一：Python 基礎概念</title><link>https://tarrragon.github.io/blog/python/01-basics/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/</guid><description>&lt;p>本模組帶你快速回顧 Python 的核心概念。如果你已經有其他程式語言經驗，這些內容能幫助你理解 Python 的「思考方式」，以及程式如何從單一 script 長成多檔案與 package。&lt;/p>
&lt;h2 id="章節列表">章節列表&lt;/h2>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>章節&lt;/th>
 &lt;th>主題&lt;/th>
 &lt;th>關鍵收穫&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">1.1&lt;/a>&lt;/td>
 &lt;td>Python 哲學與設計理念&lt;/td>
 &lt;td>理解「Pythonic」的真正含義&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">1.2&lt;/a>&lt;/td>
 &lt;td>從單一 script 到多檔案專案&lt;/td>
 &lt;td>理解 &lt;code>.py&lt;/code> module、package、執行方式與測試結構如何逐步出現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.3&lt;/a>&lt;/td>
 &lt;td>模組與套件組織&lt;/td>
 &lt;td>掌握 &lt;code>__init__.py&lt;/code> 的作用&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">1.4&lt;/a>&lt;/td>
 &lt;td>導入機制與路徑管理&lt;/td>
 &lt;td>解決「找不到模組」的困擾&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h2 id="實際範例來源">實際範例來源&lt;/h2>
&lt;p>本模組的範例主要來自：&lt;/p>
&lt;ul>
&lt;li>&lt;code>.claude/lib/__init__.py&lt;/code> - 模組初始化範例&lt;/li>
&lt;li>Hook 腳本的導入結構&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>後續補寫提示：&lt;code>1.2 從單一 script 到多檔案專案&lt;/code> 應改用中立範例，避免依賴特定 Hook 系統背景。這一章負責補上初學者從單檔到 package 的過渡；&lt;code>1.3&lt;/code> 和 &lt;code>1.4&lt;/code> 再處理既有專案中的 module、package 與 import 問題。&lt;/p>&lt;/blockquote>
&lt;h2 id="預備知識">預備知識&lt;/h2>
&lt;ul>
&lt;li>基本程式設計概念（變數、函式、迴圈）&lt;/li>
&lt;li>對任一程式語言有基礎了解&lt;/li>
&lt;/ul>
&lt;h2 id="學習時間">學習時間&lt;/h2>
&lt;p>預計 45-60 分鐘&lt;/p></description><content:encoded><![CDATA[<p>本模組帶你快速回顧 Python 的核心概念。如果你已經有其他程式語言經驗，這些內容能幫助你理解 Python 的「思考方式」，以及程式如何從單一 script 長成多檔案與 package。</p>
<h2 id="章節列表">章節列表</h2>
<table>
  <thead>
      <tr>
          <th>章節</th>
          <th>主題</th>
          <th>關鍵收穫</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="/blog/python/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">1.1</a></td>
          <td>Python 哲學與設計理念</td>
          <td>理解「Pythonic」的真正含義</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">1.2</a></td>
          <td>從單一 script 到多檔案專案</td>
          <td>理解 <code>.py</code> module、package、執行方式與測試結構如何逐步出現</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.3</a></td>
          <td>模組與套件組織</td>
          <td>掌握 <code>__init__.py</code> 的作用</td>
      </tr>
      <tr>
          <td><a href="/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">1.4</a></td>
          <td>導入機制與路徑管理</td>
          <td>解決「找不到模組」的困擾</td>
      </tr>
  </tbody>
</table>
<h2 id="實際範例來源">實際範例來源</h2>
<p>本模組的範例主要來自：</p>
<ul>
<li><code>.claude/lib/__init__.py</code> - 模組初始化範例</li>
<li>Hook 腳本的導入結構</li>
</ul>
<blockquote>
<p>後續補寫提示：<code>1.2 從單一 script 到多檔案專案</code> 應改用中立範例，避免依賴特定 Hook 系統背景。這一章負責補上初學者從單檔到 package 的過渡；<code>1.3</code> 和 <code>1.4</code> 再處理既有專案中的 module、package 與 import 問題。</p></blockquote>
<h2 id="預備知識">預備知識</h2>
<ul>
<li>基本程式設計概念（變數、函式、迴圈）</li>
<li>對任一程式語言有基礎了解</li>
</ul>
<h2 id="學習時間">學習時間</h2>
<p>預計 45-60 分鐘</p>
]]></content:encoded></item><item><title>1.2 從單一 script 到多檔案專案</title><link>https://tarrragon.github.io/blog/python/01-basics/script-to-package/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/script-to-package/</guid><description>&lt;p>Python 程式變大的第一個斷點通常是執行方式與 import 邊界，而非物件導向或架構分層。初學者常從一個 &lt;code>script.py&lt;/code> 開始，接著拆出 helper module，最後才整理成 package；每一步都會改變程式如何被執行、如何 import，以及測試如何找到程式碼。&lt;/p>
&lt;blockquote>
&lt;p>撰寫提示：本章先保留大綱，詳細內容之後補。補寫時請使用中立範例，例如 &lt;code>notify.py&lt;/code>、&lt;code>config.py&lt;/code>、&lt;code>parser.py&lt;/code>、&lt;code>service.py&lt;/code>，避免綁定特定專案或 Hook 系統細節。&lt;/p>&lt;/blockquote>
&lt;h2 id="本章目標">本章目標&lt;/h2>
&lt;p>學完本章後，你將能夠：&lt;/p>
&lt;ol>
&lt;li>判斷何時保留單一 script&lt;/li>
&lt;li>理解一個 &lt;code>.py&lt;/code> 檔案就是一個 module&lt;/li>
&lt;li>分辨「同層多檔案」與「package」的差異&lt;/li>
&lt;li>看懂 &lt;code>python file.py&lt;/code> 與 &lt;code>python -m package.module&lt;/code> 的差異&lt;/li>
&lt;li>判斷何時需要 &lt;code>__init__.py&lt;/code>、&lt;code>pyproject.toml&lt;/code> 或 &lt;code>src/&lt;/code> layout&lt;/li>
&lt;/ol>
&lt;h2 id="章節大綱">章節大綱&lt;/h2>
&lt;h3 id="1-單一-script-是合理起點">1. 單一 script 是合理起點&lt;/h3>
&lt;p>核心原則：小工具與實驗程式可以先從單一 &lt;code>.py&lt;/code> 檔案開始。這個階段的重點是讓流程清楚，不是急著拆資料夾。&lt;/p>
&lt;p>後續補寫範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">notify.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>if __name__ == &amp;quot;__main__&amp;quot;&lt;/code> 的基本用途。&lt;/li>
&lt;li>函式先集中在同一檔案，避免過早拆分。&lt;/li>
&lt;li>當檔案開始同時包含 CLI、設定、解析、業務規則時，再考慮拆檔。&lt;/li>
&lt;/ul>
&lt;h3 id="2-拆成同層-module">2. 拆成同層 module&lt;/h3>
&lt;p>核心原則：Python 的每個 &lt;code>.py&lt;/code> 檔案都是 module。同層拆檔可以降低單檔負擔，但 import 會受到目前執行位置與 &lt;code>sys.path&lt;/code> 影響。&lt;/p>
&lt;p>後續補寫範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── config.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── parser.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── service.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>import config&lt;/code> 與 &lt;code>from config import load_config&lt;/code>。&lt;/li>
&lt;li>從專案根目錄執行與從其他目錄執行的差異。&lt;/li>
&lt;li>同層 module 適合小型工具，但不一定適合長期擴張。&lt;/li>
&lt;/ul>
&lt;h3 id="3-整理成-package">3. 整理成 package&lt;/h3>
&lt;p>核心原則：package 是一組 module 的命名空間。當多個 module 共同形成一個概念，就可以用資料夾與 &lt;code>__init__.py&lt;/code> 整理成 package。&lt;/p>
&lt;p>後續補寫範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify_app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">│ ├── config.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">│ ├── parser.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">│ └── service.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">└── main.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>__init__.py&lt;/code> 的角色：初始化、公開 API、package 標記。&lt;/li>
&lt;li>package 內部使用 absolute import 或 relative import 的取捨。&lt;/li>
&lt;li>不要把所有名稱都重新 export 到 &lt;code>__init__.py&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="4-執行方式會影響-import">4. 執行方式會影響 import&lt;/h3>
&lt;p>核心原則：Python 的 import 行為和執行方式密切相關。&lt;code>python main.py&lt;/code>、&lt;code>python -m package.module&lt;/code>、測試工具與安裝後執行，可能會看到不同的 module search path。&lt;/p>
&lt;p>後續補寫範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">python main.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">python -m notify_app.cli
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">python -m pytest&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>__name__&lt;/code> 與 &lt;code>__package__&lt;/code> 的差異。&lt;/li>
&lt;li>為什麼 package 內相對 import 在直接執行檔案時容易失敗。&lt;/li>
&lt;li>CLI 入口和 library module 最好分開。&lt;/li>
&lt;/ul>
&lt;h3 id="5-測試會推動專案結構">5. 測試會推動專案結構&lt;/h3>
&lt;p>核心原則：當程式需要測試時，專案結構必須讓測試穩定 import 目標程式碼。測試是拆分 module 與 package 的重要壓力來源。&lt;/p>
&lt;p>後續補寫範例：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">notify/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── notify_app/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">│ └── service.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">└── tests/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> └── test_service.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>應補充重點：&lt;/p>
&lt;ul>
&lt;li>&lt;code>tests/&lt;/code> 和 package 的相對位置。&lt;/li>
&lt;li>為什麼測試常揭露 import 路徑設計不穩。&lt;/li>
&lt;li>小型專案可以先維持簡單，大型或可發布專案再導入 &lt;code>pyproject.toml&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="6-何時進入可安裝專案與-src-layout">6. 何時進入可安裝專案與 &lt;code>src/&lt;/code> layout&lt;/h3>
&lt;p>核心原則：&lt;code>pyproject.toml&lt;/code> 與 &lt;code>src/&lt;/code> layout 是正式專案管理工具，不是所有初學程式的起點。當專案需要被安裝、發布、由多個工具穩定執行時，再導入這些結構。&lt;/p></description><content:encoded><![CDATA[<p>Python 程式變大的第一個斷點通常是執行方式與 import 邊界，而非物件導向或架構分層。初學者常從一個 <code>script.py</code> 開始，接著拆出 helper module，最後才整理成 package；每一步都會改變程式如何被執行、如何 import，以及測試如何找到程式碼。</p>
<blockquote>
<p>撰寫提示：本章先保留大綱，詳細內容之後補。補寫時請使用中立範例，例如 <code>notify.py</code>、<code>config.py</code>、<code>parser.py</code>、<code>service.py</code>，避免綁定特定專案或 Hook 系統細節。</p></blockquote>
<h2 id="本章目標">本章目標</h2>
<p>學完本章後，你將能夠：</p>
<ol>
<li>判斷何時保留單一 script</li>
<li>理解一個 <code>.py</code> 檔案就是一個 module</li>
<li>分辨「同層多檔案」與「package」的差異</li>
<li>看懂 <code>python file.py</code> 與 <code>python -m package.module</code> 的差異</li>
<li>判斷何時需要 <code>__init__.py</code>、<code>pyproject.toml</code> 或 <code>src/</code> layout</li>
</ol>
<h2 id="章節大綱">章節大綱</h2>
<h3 id="1-單一-script-是合理起點">1. 單一 script 是合理起點</h3>
<p>核心原則：小工具與實驗程式可以先從單一 <code>.py</code> 檔案開始。這個階段的重點是讓流程清楚，不是急著拆資料夾。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">notify.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>if __name__ == &quot;__main__&quot;</code> 的基本用途。</li>
<li>函式先集中在同一檔案，避免過早拆分。</li>
<li>當檔案開始同時包含 CLI、設定、解析、業務規則時，再考慮拆檔。</li>
</ul>
<h3 id="2-拆成同層-module">2. 拆成同層 module</h3>
<p>核心原則：Python 的每個 <code>.py</code> 檔案都是 module。同層拆檔可以降低單檔負擔，但 import 會受到目前執行位置與 <code>sys.path</code> 影響。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify.py
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── config.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── parser.py
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>import config</code> 與 <code>from config import load_config</code>。</li>
<li>從專案根目錄執行與從其他目錄執行的差異。</li>
<li>同層 module 適合小型工具，但不一定適合長期擴張。</li>
</ul>
<h3 id="3-整理成-package">3. 整理成 package</h3>
<p>核心原則：package 是一組 module 的命名空間。當多個 module 共同形成一個概念，就可以用資料夾與 <code>__init__.py</code> 整理成 package。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify_app/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   ├── __init__.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   ├── config.py
</span></span><span class="line"><span class="ln">5</span><span class="cl">│   ├── parser.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">│   └── service.py
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── main.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>__init__.py</code> 的角色：初始化、公開 API、package 標記。</li>
<li>package 內部使用 absolute import 或 relative import 的取捨。</li>
<li>不要把所有名稱都重新 export 到 <code>__init__.py</code>。</li>
</ul>
<h3 id="4-執行方式會影響-import">4. 執行方式會影響 import</h3>
<p>核心原則：Python 的 import 行為和執行方式密切相關。<code>python main.py</code>、<code>python -m package.module</code>、測試工具與安裝後執行，可能會看到不同的 module search path。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">python main.py
</span></span><span class="line"><span class="ln">2</span><span class="cl">python -m notify_app.cli
</span></span><span class="line"><span class="ln">3</span><span class="cl">python -m pytest</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>__name__</code> 與 <code>__package__</code> 的差異。</li>
<li>為什麼 package 內相對 import 在直接執行檔案時容易失敗。</li>
<li>CLI 入口和 library module 最好分開。</li>
</ul>
<h3 id="5-測試會推動專案結構">5. 測試會推動專案結構</h3>
<p>核心原則：當程式需要測試時，專案結構必須讓測試穩定 import 目標程式碼。測試是拆分 module 與 package 的重要壓力來源。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── notify_app/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   ├── __init__.py
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── service.py
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">6</span><span class="cl">    └── test_service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>tests/</code> 和 package 的相對位置。</li>
<li>為什麼測試常揭露 import 路徑設計不穩。</li>
<li>小型專案可以先維持簡單，大型或可發布專案再導入 <code>pyproject.toml</code>。</li>
</ul>
<h3 id="6-何時進入可安裝專案與-src-layout">6. 何時進入可安裝專案與 <code>src/</code> layout</h3>
<p>核心原則：<code>pyproject.toml</code> 與 <code>src/</code> layout 是正式專案管理工具，不是所有初學程式的起點。當專案需要被安裝、發布、由多個工具穩定執行時，再導入這些結構。</p>
<p>後續補寫範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">notify/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── pyproject.toml
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── src/
</span></span><span class="line"><span class="ln">4</span><span class="cl">│   └── notify_app/
</span></span><span class="line"><span class="ln">5</span><span class="cl">│       ├── __init__.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">│       └── service.py
</span></span><span class="line"><span class="ln">7</span><span class="cl">└── tests/
</span></span><span class="line"><span class="ln">8</span><span class="cl">    └── test_service.py</span></span></code></pre></div><p>應補充重點：</p>
<ul>
<li><code>src/</code> layout 防止「剛好從目前目錄 import 成功」的假象。</li>
<li>editable install 的用途。</li>
<li>進階打包內容應連到 <code>python-advanced/07-packaging/</code>。</li>
</ul>
<h2 id="後續補寫時的比較提示">後續補寫時的比較提示</h2>
<p>Python 和 Go 在這個主題上的差異應明確說清楚：</p>
<table>
  <thead>
      <tr>
          <th>主題</th>
          <th>Python</th>
          <th>Go</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>最小單位</td>
          <td><code>.py</code> module</td>
          <td>package</td>
      </tr>
      <tr>
          <td>單檔起點</td>
          <td>任意 script</td>
          <td><code>package main</code> + <code>func main()</code></td>
      </tr>
      <tr>
          <td>可見性</td>
          <td><code>_name</code> 慣例，runtime 不強制</td>
          <td>大小寫由編譯器強制</td>
      </tr>
      <tr>
          <td>import 問題</td>
          <td>受 <code>sys.path</code>、執行方式、安裝方式影響</td>
          <td>受 <code>go.mod</code>、module path、package 邊界影響</td>
      </tr>
      <tr>
          <td>循環依賴</td>
          <td>runtime 才可能爆 partially initialized module</td>
          <td>編譯期拒絕 import cycle</td>
      </tr>
      <tr>
          <td>正式專案</td>
          <td><code>pyproject.toml</code>、package、<code>src/</code> layout</td>
          <td><code>go.mod</code>、package、<code>cmd/</code>、<code>internal/</code></td>
      </tr>
  </tbody>
</table>
<h2 id="本章不處理">本章不處理</h2>
<ul>
<li>不展開 packaging 與發布流程。</li>
<li>不深入 dependency management。</li>
<li>不討論大型框架專案結構。</li>
<li>不把 <code>src/</code> layout 當成所有專案的預設起點。</li>
</ul>
<h2 id="和-python-教材的關係">和 Python 教材的關係</h2>
<p>這一章承接的是 module、package 與測試路徑；如果你要先回看 Python 教材，可以讀：</p>
<ul>
<li><a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">Python：modules</a></li>
<li><a href="/blog/python-advanced/07-packaging/" data-link-title="模組七：打包與發布" data-link-desc="學習現代 Python 套件的打包與發布流程">Python 進階：packaging 與 distribution</a></li>
<li><a href="/blog/python/05-error-testing/" data-link-title="模組五：錯誤處理與測試" data-link-desc="穩健程式碼的基石：異常處理和單元測試">Python：測試基礎</a></li>
<li><a href="/blog/python/05-error-testing/mock/" data-link-title="5.4 Mock 與測試隔離" data-link-desc="隔離外部依賴">Python 進階：error handling 與 mock</a></li>
</ul>
<h2 id="小結">小結</h2>
<p>Python 程式的成長路線通常是 script、同層 module、package、可安裝專案。這條路線的核心是執行方式、import 邊界與測試方式逐步穩定。</p>
]]></content:encoded></item><item><title>6.2 如何擴展共用模組</title><link>https://tarrragon.github.io/blog/python/06-practical/extend-lib/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/06-practical/extend-lib/</guid><description>&lt;p>本章介紹如何為 &lt;code>.claude/lib/&lt;/code> 共用程式庫添加新功能。這是維護和擴展 Hook 系統的關鍵技能。&lt;/p>
&lt;h2 id="前置知識">前置知識&lt;/h2>
&lt;p>建議先閱讀：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.2 模組與套件組織&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;li>&lt;a href="https://tarrragon.github.io/blog/python/04-oop/class-design/" data-link-title="4.1 類別設計原則" data-link-desc="設計清晰的類別介面">4.1 類別設計原則&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/python/05-error-testing/unittest/" data-link-title="5.3 unittest 基礎" data-link-desc="撰寫第一個單元測試">5.3 unittest 基礎&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="共用模組架構">共用模組架構&lt;/h2>
&lt;p>目前的 &lt;code>.claude/lib/&lt;/code> 結構：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">.claude/lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── __init__.py # 模組初始化與匯出
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── git_utils.py # Git 操作工具
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── hook_logging.py # 日誌系統
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── hook_io.py # 輸入輸出處理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">├── config_loader.py # 配置載入器
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">├── hook_validator.py # Hook 驗證器
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">└── markdown_link_checker.py # Markdown 連結檢查&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-1規劃新模組">步驟 1：規劃新模組&lt;/h2>
&lt;h3 id="決定放置位置">決定放置位置&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>情況&lt;/th>
 &lt;th>建議&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>新的獨立功能&lt;/td>
 &lt;td>建立新檔案&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴展現有功能&lt;/td>
 &lt;td>修改現有檔案&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>工具函式&lt;/td>
 &lt;td>加入相關現有模組&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&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="c1"># 思考要提供什麼功能給使用者&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 函式：簡單操作&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_yaml&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"> 5&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"> 6&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"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 類別：複雜操作或需要狀態&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">YamlValidator&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;YAML 驗證器&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">strict&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&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">14&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">15&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">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="n">ValidationResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;驗證內容&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="k">pass&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-2實作新模組">步驟 2：實作新模組&lt;/h2>
&lt;h3 id="範例建立-yaml-工具模組">範例：建立 YAML 工具模組&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1"># .claude/lib/yaml_utils.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">YAML 工具模組
&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">提供 YAML 檔案的讀取、驗證和處理功能。
&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">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"> 9&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&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">dataclasses&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 嘗試導入 yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 13&lt;/span>&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 14&lt;/span>&lt;span class="cl"> &lt;span class="kn">import&lt;/span> &lt;span class="nn">yaml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 15&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 16&lt;/span>&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">ImportError&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 17&lt;/span>&lt;span class="cl"> &lt;span class="n">HAS_YAML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 19&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 20&lt;/span>&lt;span class="cl">&lt;span class="nd">@dataclass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 21&lt;/span>&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">YamlResult&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 22&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;YAML 處理結果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 23&lt;/span>&lt;span class="cl"> &lt;span class="n">success&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 24&lt;/span>&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">dict&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 25&lt;/span>&lt;span class="cl"> &lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 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>&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">load_yaml&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="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 class="o">-&amp;gt;&lt;/span> &lt;span class="n">YamlResult&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"> 載入 YAML 檔案
&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"> path: 檔案路徑
&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"> encoding: 檔案編碼
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 35&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 36&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&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"> YamlResult: 載入結果
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 38&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 39&lt;/span>&lt;span class="cl">&lt;span class="s2"> Example:
&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"> result = load_yaml(&amp;#34;config.yaml&amp;#34;)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 41&lt;/span>&lt;span class="cl">&lt;span class="s2"> if result.success:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 42&lt;/span>&lt;span class="cl">&lt;span class="s2"> print(result.data)
&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"> else:
&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"> print(f&amp;#34;錯誤: &lt;/span>&lt;span class="si">{result.error}&lt;/span>&lt;span class="s2">&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="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 46&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">HAS_YAML&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 47&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">YamlResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 48&lt;/span>&lt;span class="cl"> &lt;span class="n">success&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"> 49&lt;/span>&lt;span class="cl"> &lt;span class="n">error&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;PyYAML 未安裝。請執行: pip install pyyaml&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="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 51&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 52&lt;/span>&lt;span class="cl"> &lt;span class="n">file_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 53&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 54&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">exists&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 55&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">YamlResult&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 56&lt;/span>&lt;span class="cl"> &lt;span class="n">success&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"> 57&lt;/span>&lt;span class="cl"> &lt;span class="n">error&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;檔案不存在: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 58&lt;/span>&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 59&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 60&lt;/span>&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 61&lt;/span>&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">encoding&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 62&lt;/span>&lt;span class="cl"> &lt;span class="n">data&lt;/span> &lt;span class="o">=&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"> 63&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">YamlResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">data&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"> 64&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="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"> 65&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">YamlResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">success&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;YAML 解析錯誤: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 66&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 67&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 68&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">validate_yaml&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"> 69&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"> 70&lt;/span>&lt;span class="cl">&lt;span class="s2"> 驗證 YAML 格式
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 71&lt;/span>&lt;span class="cl">&lt;span class="s2">
&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"> Args:
&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"> content: YAML 內容字串
&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"> Returns:
&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"> bool: 格式正確返回 True
&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"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 78&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">HAS_YAML&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 79&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"> 80&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 81&lt;/span>&lt;span class="cl"> &lt;span class="k">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="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"> 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="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"> 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>&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">merge_yaml_configs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">configs&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"> 89&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"> 90&lt;/span>&lt;span class="cl">&lt;span class="s2"> 合併多個 YAML 配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 91&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 92&lt;/span>&lt;span class="cl">&lt;span class="s2"> 後面的配置會覆蓋前面的。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 93&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 94&lt;/span>&lt;span class="cl">&lt;span class="s2"> Args:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 95&lt;/span>&lt;span class="cl">&lt;span class="s2"> *configs: 要合併的配置字典
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 96&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 97&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"> 98&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"> 99&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">100&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">101&lt;/span>&lt;span class="cl">&lt;span class="s2"> base = {&amp;#34;a&amp;#34;: 1, &amp;#34;b&amp;#34;: 2}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">102&lt;/span>&lt;span class="cl">&lt;span class="s2"> override = {&amp;#34;b&amp;#34;: 3, &amp;#34;c&amp;#34;: 4}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">103&lt;/span>&lt;span class="cl">&lt;span class="s2"> result = merge_yaml_configs(base, override)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">104&lt;/span>&lt;span class="cl">&lt;span class="s2"> # result = {&amp;#34;a&amp;#34;: 1, &amp;#34;b&amp;#34;: 3, &amp;#34;c&amp;#34;: 4}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">105&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">106&lt;/span>&lt;span class="cl"> &lt;span class="n">result&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">107&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">config&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">configs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">108&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">109&lt;/span>&lt;span class="cl"> &lt;span class="n">_deep_merge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">110&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">111&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">112&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">113&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">_deep_merge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">override&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">114&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;深度合併字典（就地修改 base）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">115&lt;/span>&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">value&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">override&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">116&lt;/span>&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">117&lt;/span>&lt;span class="cl"> &lt;span class="n">key&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">base&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">118&lt;/span>&lt;span class="cl"> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">119&lt;/span>&lt;span class="cl"> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">120&lt;/span>&lt;span class="cl"> &lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">121&lt;/span>&lt;span class="cl"> &lt;span class="n">_deep_merge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">122&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">123&lt;/span>&lt;span class="cl"> &lt;span class="n">base&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">value&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="步驟-3更新-__init__py">步驟 3：更新 &lt;code>__init__.py&lt;/code>&lt;/h2>
&lt;p>在 &lt;code>__init__.py&lt;/code> 中註冊新模組：&lt;/p></description><content:encoded><![CDATA[<p>本章介紹如何為 <code>.claude/lib/</code> 共用程式庫添加新功能。這是維護和擴展 Hook 系統的關鍵技能。</p>
<h2 id="前置知識">前置知識</h2>
<p>建議先閱讀：</p>
<ul>
<li><a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">1.2 模組與套件組織</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>
<li><a href="/blog/python/04-oop/class-design/" data-link-title="4.1 類別設計原則" data-link-desc="設計清晰的類別介面">4.1 類別設計原則</a></li>
<li><a href="/blog/python/05-error-testing/unittest/" data-link-title="5.3 unittest 基礎" data-link-desc="撰寫第一個單元測試">5.3 unittest 基礎</a></li>
</ul>
<h2 id="共用模組架構">共用模組架構</h2>
<p>目前的 <code>.claude/lib/</code> 結構：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">.claude/lib/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── __init__.py          # 模組初始化與匯出
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── git_utils.py         # Git 操作工具
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── hook_logging.py      # 日誌系統
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── hook_io.py           # 輸入輸出處理
</span></span><span class="line"><span class="ln">6</span><span class="cl">├── config_loader.py     # 配置載入器
</span></span><span class="line"><span class="ln">7</span><span class="cl">├── hook_validator.py    # Hook 驗證器
</span></span><span class="line"><span class="ln">8</span><span class="cl">└── markdown_link_checker.py  # Markdown 連結檢查</span></span></code></pre></div><h2 id="步驟-1規劃新模組">步驟 1：規劃新模組</h2>
<h3 id="決定放置位置">決定放置位置</h3>
<table>
  <thead>
      <tr>
          <th>情況</th>
          <th>建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>新的獨立功能</td>
          <td>建立新檔案</td>
      </tr>
      <tr>
          <td>擴展現有功能</td>
          <td>修改現有檔案</td>
      </tr>
      <tr>
          <td>工具函式</td>
          <td>加入相關現有模組</td>
      </tr>
  </tbody>
</table>
<h3 id="設計介面">設計介面</h3>
<p>在寫程式碼之前，先規劃公開介面：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 思考要提供什麼功能給使用者</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 函式：簡單操作</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">validate_yaml</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"> 5</span><span class="cl">    <span class="s2">&#34;&#34;&#34;驗證 YAML 格式&#34;&#34;&#34;</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">YamlValidator</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="s2">&#34;&#34;&#34;YAML 驗證器&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">strict</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="s2">&#34;&#34;&#34;初始化&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="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="n">ValidationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="s2">&#34;&#34;&#34;驗證內容&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">pass</span></span></span></code></pre></div><h2 id="步驟-2實作新模組">步驟 2：實作新模組</h2>
<h3 id="範例建立-yaml-工具模組">範例：建立 YAML 工具模組</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"># .claude/lib/yaml_utils.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">YAML 工具模組
</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">提供 YAML 檔案的讀取、驗證和處理功能。
</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">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Any</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl">
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="c1"># 嘗試導入 yaml</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 14</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl"><span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">
</span></span><span class="line"><span class="ln"> 19</span><span class="cl">
</span></span><span class="line"><span class="ln"> 20</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl"><span class="k">class</span> <span class="nc">YamlResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">    <span class="s2">&#34;&#34;&#34;YAML 處理結果&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="n">success</span><span class="p">:</span> <span class="nb">bool</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">    <span class="n">data</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">dict</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">
</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">load_yaml</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="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 class="o">-&gt;</span> <span class="n">YamlResult</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">    載入 YAML 檔案
</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">        path: 檔案路徑
</span></span></span><span class="line"><span class="ln"> 34</span><span class="cl"><span class="s2">        encoding: 檔案編碼
</span></span></span><span class="line"><span class="ln"> 35</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 36</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl"><span class="s2">        YamlResult: 載入結果
</span></span></span><span class="line"><span class="ln"> 38</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 39</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln"> 40</span><span class="cl"><span class="s2">        result = load_yaml(&#34;config.yaml&#34;)
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl"><span class="s2">        if result.success:
</span></span></span><span class="line"><span class="ln"> 42</span><span class="cl"><span class="s2">            print(result.data)
</span></span></span><span class="line"><span class="ln"> 43</span><span class="cl"><span class="s2">        else:
</span></span></span><span class="line"><span class="ln"> 44</span><span class="cl"><span class="s2">            print(f&#34;錯誤: </span><span class="si">{result.error}</span><span class="s2">&#34;)
</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="k">if</span> <span class="ow">not</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">        <span class="k">return</span> <span class="n">YamlResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">            <span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 49</span><span class="cl">            <span class="n">error</span><span class="o">=</span><span class="s2">&#34;PyYAML 未安裝。請執行: pip install pyyaml&#34;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">    <span class="n">file_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">file_path</span><span class="o">.</span><span class="n">exists</span><span class="p">():</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">        <span class="k">return</span> <span class="n">YamlResult</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">            <span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">            <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;檔案不存在: </span><span class="si">{</span><span class="n">path</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">file_path</span><span class="o">.</span><span class="n">read_text</span><span class="p">(</span><span class="n">encoding</span><span class="o">=</span><span class="n">encoding</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="n">data</span> <span class="o">=</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"> 63</span><span class="cl">        <span class="k">return</span> <span class="n">YamlResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span> <span class="ow">or</span> <span class="p">{})</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="k">except</span> <span class="n">yaml</span><span class="o">.</span><span class="n">YAMLError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">        <span class="k">return</span> <span class="n">YamlResult</span><span class="p">(</span><span class="n">success</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">error</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;YAML 解析錯誤: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">
</span></span><span class="line"><span class="ln"> 67</span><span class="cl">
</span></span><span class="line"><span class="ln"> 68</span><span class="cl"><span class="k">def</span> <span class="nf">validate_yaml</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"> 69</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 70</span><span class="cl"><span class="s2">    驗證 YAML 格式
</span></span></span><span class="line"><span class="ln"> 71</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 72</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 73</span><span class="cl"><span class="s2">        content: YAML 內容字串
</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">    Returns:
</span></span></span><span class="line"><span class="ln"> 76</span><span class="cl"><span class="s2">        bool: 格式正確返回 True
</span></span></span><span class="line"><span class="ln"> 77</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">        <span class="k">return</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 82</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"> 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="n">yaml</span><span class="o">.</span><span class="n">YAMLError</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></span><span class="line"><span class="ln"> 88</span><span class="cl"><span class="k">def</span> <span class="nf">merge_yaml_configs</span><span class="p">(</span><span class="o">*</span><span class="n">configs</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"> 89</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 90</span><span class="cl"><span class="s2">    合併多個 YAML 配置
</span></span></span><span class="line"><span class="ln"> 91</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 92</span><span class="cl"><span class="s2">    後面的配置會覆蓋前面的。
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 94</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 95</span><span class="cl"><span class="s2">        *configs: 要合併的配置字典
</span></span></span><span class="line"><span class="ln"> 96</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 97</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 98</span><span class="cl"><span class="s2">        dict: 合併後的配置
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">100</span><span class="cl"><span class="s2">    Example:
</span></span></span><span class="line"><span class="ln">101</span><span class="cl"><span class="s2">        base = {&#34;a&#34;: 1, &#34;b&#34;: 2}
</span></span></span><span class="line"><span class="ln">102</span><span class="cl"><span class="s2">        override = {&#34;b&#34;: 3, &#34;c&#34;: 4}
</span></span></span><span class="line"><span class="ln">103</span><span class="cl"><span class="s2">        result = merge_yaml_configs(base, override)
</span></span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="s2">        # result = {&#34;a&#34;: 1, &#34;b&#34;: 3, &#34;c&#34;: 4}
</span></span></span><span class="line"><span class="ln">105</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="ln">107</span><span class="cl">    <span class="k">for</span> <span class="n">config</span> <span class="ow">in</span> <span class="n">configs</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">        <span class="k">if</span> <span class="n">config</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">109</span><span class="cl">            <span class="n">_deep_merge</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">    <span class="k">return</span> <span class="n">result</span>
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</span><span class="cl">
</span></span><span class="line"><span class="ln">113</span><span class="cl"><span class="k">def</span> <span class="nf">_deep_merge</span><span class="p">(</span><span class="n">base</span><span class="p">:</span> <span class="nb">dict</span><span class="p">,</span> <span class="n">override</span><span class="p">:</span> <span class="nb">dict</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">114</span><span class="cl">    <span class="s2">&#34;&#34;&#34;深度合併字典（就地修改 base）&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">115</span><span class="cl">    <span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">override</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">116</span><span class="cl">        <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">117</span><span class="cl">            <span class="n">key</span> <span class="ow">in</span> <span class="n">base</span>
</span></span><span class="line"><span class="ln">118</span><span class="cl">            <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">base</span><span class="p">[</span><span class="n">key</span><span class="p">],</span> <span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">119</span><span class="cl">            <span class="ow">and</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">120</span><span class="cl">        <span class="p">):</span>
</span></span><span class="line"><span class="ln">121</span><span class="cl">            <span class="n">_deep_merge</span><span class="p">(</span><span class="n">base</span><span class="p">[</span><span class="n">key</span><span class="p">],</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">122</span><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">123</span><span class="cl">            <span class="n">base</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span></span></span></code></pre></div><h2 id="步驟-3更新-__init__py">步驟 3：更新 <code>__init__.py</code></h2>
<p>在 <code>__init__.py</code> 中註冊新模組：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># .claude/lib/__init__.py</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Claude Hooks 共用程式庫
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">&#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="c1"># 現有匯入</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="c1"># 新增：YAML 工具</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="kn">from</span> <span class="nn">.yaml_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">load_yaml</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">validate_yaml</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">merge_yaml_configs</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="n">YamlResult</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="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="c1"># 現有匯出</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="s2">&#34;run_git_command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="s2">&#34;get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="s2">&#34;setup_hook_logging&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="s2">&#34;read_hook_input&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="s2">&#34;write_hook_output&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="c1"># 新增</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="s2">&#34;load_yaml&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="s2">&#34;validate_yaml&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="s2">&#34;merge_yaml_configs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="s2">&#34;YamlResult&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.29.0&#34;</span>  <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/lib/test_yaml_utils.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">YAML 工具模組測試
</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 class="p">,</span> <span class="n">mock_open</span>
</span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="c1"># 添加 lib 目錄到路徑</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;.claude&#34;</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 13</span><span class="cl">
</span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="kn">from</span> <span class="nn">yaml_utils</span> <span class="kn">import</span> <span class="n">load_yaml</span><span class="p">,</span> <span class="n">validate_yaml</span><span class="p">,</span> <span class="n">merge_yaml_configs</span><span class="p">,</span> <span class="n">YamlResult</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></span><span class="line"><span class="ln"> 17</span><span class="cl"><span class="k">class</span> <span class="nc">TestLoadYaml</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;測試 load_yaml 函式&#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">test_load_valid_yaml</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;測試載入有效的 YAML 檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="n">yaml_content</span> <span class="o">=</span> <span class="s2">&#34;key: value</span><span class="se">\n</span><span class="s2">list:</span><span class="se">\n</span><span class="s2">  - item1</span><span class="se">\n</span><span class="s2">  - item2&#34;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.exists&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">            <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.read_text&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="n">yaml_content</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 26</span><span class="cl">                <span class="n">result</span> <span class="o">=</span> <span class="n">load_yaml</span><span class="p">(</span><span class="s2">&#34;test.yaml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 29</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="o">.</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;key&#34;</span><span class="p">],</span> <span class="s2">&#34;value&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;list&#34;</span><span class="p">]),</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 31</span><span class="cl">
</span></span><span class="line"><span class="ln"> 32</span><span class="cl">    <span class="k">def</span> <span class="nf">test_load_nonexistent_file</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試載入不存在的檔案&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.exists&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">False</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">            <span class="n">result</span> <span class="o">=</span> <span class="n">load_yaml</span><span class="p">(</span><span class="s2">&#34;nonexistent.yaml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">
</span></span><span class="line"><span class="ln"> 37</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 38</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;不存在&#34;</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="k">def</span> <span class="nf">test_load_invalid_yaml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 41</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試載入無效的 YAML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">        <span class="n">invalid_content</span> <span class="o">=</span> <span class="s2">&#34;key: [invalid yaml&#34;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">        <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.exists&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">            <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s2">&#34;pathlib.Path.read_text&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="n">invalid_content</span><span class="p">):</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="n">load_yaml</span><span class="p">(</span><span class="s2">&#34;invalid.yaml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">success</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 49</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;解析錯誤&#34;</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">error</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">
</span></span><span class="line"><span class="ln"> 52</span><span class="cl"><span class="k">class</span> <span class="nc">TestValidateYaml</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"> 53</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 validate_yaml 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 54</span><span class="cl">
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="k">def</span> <span class="nf">test_valid_yaml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試有效的 YAML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">validate_yaml</span><span class="p">(</span><span class="s2">&#34;key: value&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">validate_yaml</span><span class="p">(</span><span class="s2">&#34;list:</span><span class="se">\n</span><span class="s2">  - a</span><span class="se">\n</span><span class="s2">  - b&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 59</span><span class="cl">
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="k">def</span> <span class="nf">test_invalid_yaml</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試無效的 YAML&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">validate_yaml</span><span class="p">(</span><span class="s2">&#34;key: [unclosed&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertFalse</span><span class="p">(</span><span class="n">validate_yaml</span><span class="p">(</span><span class="s2">&#34;  bad indent</span><span class="se">\n</span><span class="s2">key: value&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl"><span class="k">class</span> <span class="nc">TestMergeYamlConfigs</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"> 67</span><span class="cl">    <span class="s2">&#34;&#34;&#34;測試 merge_yaml_configs 函式&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">    <span class="k">def</span> <span class="nf">test_simple_merge</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試簡單合併&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="n">override</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span> <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">merge_yaml_configs</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">override</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="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;a&#34;</span><span class="p">],</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;b&#34;</span><span class="p">],</span> <span class="mi">3</span><span class="p">)</span>  <span class="c1"># 被覆蓋</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s2">&#34;c&#34;</span><span class="p">],</span> <span class="mi">4</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">    <span class="k">def</span> <span class="nf">test_deep_merge</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試深度合併&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 82</span><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">            <span class="s2">&#34;database&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">                <span class="s2">&#34;host&#34;</span><span class="p">:</span> <span class="s2">&#34;localhost&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">                <span class="s2">&#34;port&#34;</span><span class="p">:</span> <span class="mi">5432</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="n">override</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">            <span class="s2">&#34;database&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">                <span class="s2">&#34;port&#34;</span><span class="p">:</span> <span class="mi">3306</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">                <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;mydb&#34;</span>
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">merge_yaml_configs</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">override</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">        <span class="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"> 98</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;port&#34;</span><span class="p">],</span> <span class="mi">3306</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 99</span><span class="cl">        <span class="bp">self</span><span class="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;name&#34;</span><span class="p">],</span> <span class="s2">&#34;mydb&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">
</span></span><span class="line"><span class="ln">101</span><span class="cl">    <span class="k">def</span> <span class="nf">test_multiple_configs</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="s2">&#34;&#34;&#34;測試多個配置合併&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">        <span class="n">config1</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl">        <span class="n">config2</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">105</span><span class="cl">        <span class="n">config3</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">106</span><span class="cl">
</span></span><span class="line"><span class="ln">107</span><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">merge_yaml_configs</span><span class="p">(</span><span class="n">config1</span><span class="p">,</span> <span class="n">config2</span><span class="p">,</span> <span class="n">config3</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">108</span><span class="cl">
</span></span><span class="line"><span class="ln">109</span><span class="cl">        <span class="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="p">{</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;c&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">110</span><span class="cl">
</span></span><span class="line"><span class="ln">111</span><span class="cl">
</span></span><span class="line"><span class="ln">112</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">113</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>
<h3 id="在模組文檔中說明">在模組文檔中說明</h3>
<p>在模組開頭加入詳細的 docstring：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">YAML 工具模組
</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">提供 YAML 檔案的讀取、驗證和處理功能。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">主要功能:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">- load_yaml: 載入 YAML 檔案
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">- validate_yaml: 驗證 YAML 格式
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">- merge_yaml_configs: 合併配置
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">依賴:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">- PyYAML (可選，但建議安裝)
</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">    from lib.yaml_utils import load_yaml, validate_yaml
</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">    result = load_yaml(&#34;config.yaml&#34;)
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="s2">    if result.success:
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="s2">        config = result.data
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="s2">    # 驗證格式
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="s2">    is_valid = validate_yaml(content)
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="s2">版本: 0.29.0
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span></span></span></code></pre></div><h2 id="擴展現有模組">擴展現有模組</h2>
<h3 id="範例為-git_utils-添加新功能">範例：為 git_utils 添加新功能</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"># 在 .claude/lib/git_utils.py 中添加</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">get_uncommitted_changes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    取得未提交的變更檔案列表
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">        list[str]: 變更檔案的路徑列表
</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">    Example:
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">        changes = get_uncommitted_changes()
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        if changes:
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s2">            print(f&#34;有 {len(changes)} 個未提交的變更&#34;)
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;status&#34;</span><span class="p">,</span> <span class="s2">&#34;--porcelain&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">success</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="k">return</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">output</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">            <span class="c1"># 格式: &#34;XY filename&#34; 或 &#34;XY filename -&gt; newname&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">            <span class="n">parts</span> <span class="o">=</span> <span class="n">line</span><span class="p">[</span><span class="mi">3</span><span class="p">:]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34; -&gt; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">            <span class="n">files</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">parts</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="k">return</span> <span class="n">files</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">
</span></span><span class="line"><span class="ln">29</span><span class="cl">
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">def</span> <span class="nf">has_staged_changes</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="s2">    檢查是否有已暫存的變更
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="s2">        bool: 有暫存變更返回 True
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;diff&#34;</span><span class="p">,</span> <span class="s2">&#34;--cached&#34;</span><span class="p">,</span> <span class="s2">&#34;--name-only&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="k">return</span> <span class="n">success</span> <span class="ow">and</span> <span class="nb">bool</span><span class="p">(</span><span class="n">output</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span></span></span></code></pre></div><p>然後在 <code>__init__.py</code> 中更新匯出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># 新增</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">get_uncommitted_changes</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">has_staged_changes</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="c1"># ... 現有匯出 ...</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="s2">&#34;get_uncommitted_changes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="s2">&#34;has_staged_changes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="p">]</span></span></span></code></pre></div><h2 id="設計原則">設計原則</h2>
<h3 id="1-單一職責">1. 單一職責</h3>
<p>每個模組專注一個領域：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 好：專注於 Git 操作</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># git_utils.py</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">def</span> <span class="nf">get_current_branch</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">def</span> <span class="nf">run_git_command</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">def</span> <span class="nf">is_protected_branch</span><span class="p">():</span> <span class="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="c1"># utils.py</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">get_current_branch</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">validate_yaml</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">send_notification</span><span class="p">():</span> <span class="o">...</span></span></span></code></pre></div><h3 id="2-統一的返回值模式">2. 統一的返回值模式</h3>
<p>使用一致的返回值設計：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 簡單操作：返回 (bool, str)</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">def</span> <span class="nf">run_command</span><span class="p">(</span><span class="n">cmd</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">bool</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;&#34;&#34;返回 (成功與否, 輸出或錯誤訊息)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="k">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"># 複雜操作：返回 dataclass</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">class</span> <span class="nc">OperationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">success</span><span class="p">:</span> <span class="nb">bool</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">data</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Any</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">error</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">def</span> <span class="nf">complex_operation</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">OperationResult</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="k">pass</span></span></span></code></pre></div><h3 id="3-優雅的依賴處理">3. 優雅的依賴處理</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 處理可選依賴</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="ln"> 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="n">HAS_YAML</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">def</span> <span class="nf">validate_yaml</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"> 9</span><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">HAS_YAML</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="k">raise</span> <span class="ne">ImportError</span><span class="p">(</span><span class="s2">&#34;需要安裝 PyYAML: pip install pyyaml&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># ...</span></span></span></code></pre></div><h3 id="4-完整的文檔字串">4. 完整的文檔字串</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">load_config</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">    載入配置檔案
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">        name: 配置名稱（不含副檔名）
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">        dict: 配置內容
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="s2">    Raises:
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="s2">        FileNotFoundError: 配置檔案不存在
</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">    Example:
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s2">        config = load_config(&#34;agents&#34;)
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s2">        print(config[&#34;known_agents&#34;])
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="s2">    &#34;&#34;&#34;</span></span></span></code></pre></div><h2 id="完整檢查清單">完整檢查清單</h2>
<p>擴展共用模組時的檢查項目：</p>
<ul>
<li><input disabled="" type="checkbox"> 設計清晰的公開介面</li>
<li><input disabled="" type="checkbox"> 使用 Type Hints</li>
<li><input disabled="" type="checkbox"> 撰寫完整的 docstring</li>
<li><input disabled="" type="checkbox"> 處理可選依賴</li>
<li><input disabled="" type="checkbox"> 統一返回值模式</li>
<li><input disabled="" type="checkbox"> 更新 <code>__init__.py</code></li>
<li><input disabled="" type="checkbox"> 更新 <code>__all__</code> 匯出</li>
<li><input disabled="" type="checkbox"> 更新版本號</li>
<li><input disabled="" type="checkbox"> 撰寫單元測試</li>
<li><input disabled="" type="checkbox"> 測試與現有 Hook 的整合</li>
</ul>
<h2 id="思考題">思考題</h2>
<ol>
<li>什麼時候應該建立新模組，什麼時候應該擴展現有模組？</li>
<li>如何設計 API 使其易於測試？</li>
<li><code>__all__</code> 的作用是什麼？為什麼要維護它？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/06-practical/new-hook/" data-link-title="6.1 如何新增一個 Hook" data-link-desc="完整的 Hook 開發流程">如何新增一個 Hook</a></em>
<em>下一章：<a href="/blog/python/06-practical/new-parser/" data-link-title="6.3 如何新增語言解析器" data-link-desc="繼承 ABC 實作新解析器">如何新增語言解析器</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>1.3 模組與套件組織</title><link>https://tarrragon.github.io/blog/python/01-basics/modules/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/modules/</guid><description>&lt;p>Python 的模組系統是組織程式碼的基礎。理解模組如何運作，是維護和擴展 Hook 系統的關鍵。&lt;/p>
&lt;blockquote>
&lt;p>承接提示：如果你還不熟悉程式如何從單一 &lt;code>.py&lt;/code> 檔案拆成多個 module，請先閱讀 &lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案&lt;/a>。本章聚焦在已經出現 package 後，如何理解 module、&lt;code>__init__.py&lt;/code> 與公開 API。&lt;/p>&lt;/blockquote>
&lt;h2 id="基本概念">基本概念&lt;/h2>
&lt;h3 id="模組module">模組（Module）&lt;/h3>
&lt;p>一個 &lt;code>.py&lt;/code> 檔案就是一個模組。例如 &lt;code>git_utils.py&lt;/code> 就是 &lt;code>git_utils&lt;/code> 模組。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># git_utils.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="c1"># 可以被其他檔案導入&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="套件package">套件（Package）&lt;/h3>
&lt;p>包含 &lt;code>__init__.py&lt;/code> 的目錄就是一個套件。套件可以包含多個模組。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">.claude/lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── __init__.py # 使 lib 成為套件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">├── git_utils.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">├── hook_io.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">├── hook_logging.py # 模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">└── config_loader.py # 模組&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="__init__py-的作用">&lt;code>__init__.py&lt;/code> 的作用&lt;/h2>
&lt;p>&lt;code>__init__.py&lt;/code> 是套件的初始化檔案，它在套件被導入時執行。&lt;/p>
&lt;h3 id="實際範例hook-系統的-__init__py">實際範例：Hook 系統的 &lt;code>__init__.py&lt;/code>&lt;/h3>
&lt;p>來自 &lt;code>.claude/lib/__init__.py&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">Claude Hooks 共用程式庫
&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">提供 Hook 腳本共用的工具函式，消除程式碼重複。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="s2">模組結構:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="s2">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="s2">- hook_logging: Hook 日誌系統
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="s2">- hook_io: Hook 輸入輸出處理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 從子模組導入並重新匯出&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl"> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl"> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl"> &lt;span class="n">get_project_root&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl"> &lt;span class="n">get_worktree_list&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl"> &lt;span class="n">is_protected_branch&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="n">is_allowed_branch&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="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="kn">from&lt;/span> &lt;span class="nn">.hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&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="kn">from&lt;/span> &lt;span class="nn">.hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> &lt;span class="n">read_hook_input&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> &lt;span class="n">write_hook_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl"> &lt;span class="n">create_pretooluse_output&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">28&lt;/span>&lt;span class="cl"> &lt;span class="n">create_posttooluse_output&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;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="kn">from&lt;/span> &lt;span class="nn">.config_loader&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">32&lt;/span>&lt;span class="cl"> &lt;span class="n">load_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">33&lt;/span>&lt;span class="cl"> &lt;span class="n">load_agents_config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">34&lt;/span>&lt;span class="cl"> &lt;span class="n">load_quality_rules&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">35&lt;/span>&lt;span class="cl"> &lt;span class="n">clear_config_cache&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">36&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">37&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">38&lt;/span>&lt;span class="cl">&lt;span class="c1"># 定義公開 API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">39&lt;/span>&lt;span class="cl">&lt;span class="n">__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">40&lt;/span>&lt;span class="cl"> &lt;span class="c1"># git_utils&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">41&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;run_git_command&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">42&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;get_current_branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">43&lt;/span>&lt;span class="cl"> &lt;span class="c1"># ... 省略其他&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">44&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">45&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">46&lt;/span>&lt;span class="cl">&lt;span class="n">__version__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;0.28.0&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="__init__py-的三個主要功能">&lt;code>__init__.py&lt;/code> 的三個主要功能&lt;/h3>
&lt;h4 id="1-宣告套件身份">1. 宣告套件身份&lt;/h4>
&lt;p>空的 &lt;code>__init__.py&lt;/code> 也能讓目錄成為套件：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># lib/__init__.py（最簡形式）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="c1"># 即使是空檔案，也使 lib 成為套件&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="2-定義公開-api">2. 定義公開 API&lt;/h4>
&lt;p>透過 &lt;code>__all__&lt;/code> 列表控制 &lt;code>from package import *&lt;/code> 的行為：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="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">2&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;run_git_command&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;get_current_branch&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> &lt;span class="s2">&amp;#34;setup_hook_logging&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 當使用者執行 from lib import * 時&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="c1"># 只會導入 __all__ 中列出的名稱&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="3-簡化導入路徑">3. 簡化導入路徑&lt;/h4>
&lt;p>使用者可以直接從套件導入，而不需要知道子模組：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 有 __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="kn">from&lt;/span> &lt;span class="nn">lib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">setup_hook_logging&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 沒有 __init__.py 的重新匯出：冗長&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lib.git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lib.hook_logging&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup_hook_logging&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="相對導入與絕對導入">相對導入與絕對導入&lt;/h2>
&lt;h3 id="相對導入使用-">相對導入（使用 &lt;code>.&lt;/code>）&lt;/h3>
&lt;p>在套件內部使用相對導入：&lt;/p></description><content:encoded><![CDATA[<p>Python 的模組系統是組織程式碼的基礎。理解模組如何運作，是維護和擴展 Hook 系統的關鍵。</p>
<blockquote>
<p>承接提示：如果你還不熟悉程式如何從單一 <code>.py</code> 檔案拆成多個 module，請先閱讀 <a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案</a>。本章聚焦在已經出現 package 後，如何理解 module、<code>__init__.py</code> 與公開 API。</p></blockquote>
<h2 id="基本概念">基本概念</h2>
<h3 id="模組module">模組（Module）</h3>
<p>一個 <code>.py</code> 檔案就是一個模組。例如 <code>git_utils.py</code> 就是 <code>git_utils</code> 模組。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># git_utils.py 是一個模組</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 可以被其他檔案導入</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span></span></span></code></pre></div><h3 id="套件package">套件（Package）</h3>
<p>包含 <code>__init__.py</code> 的目錄就是一個套件。套件可以包含多個模組。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">.claude/lib/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── __init__.py      # 使 lib 成為套件
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── git_utils.py     # 模組
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── hook_io.py       # 模組
</span></span><span class="line"><span class="ln">5</span><span class="cl">├── hook_logging.py  # 模組
</span></span><span class="line"><span class="ln">6</span><span class="cl">└── config_loader.py # 模組</span></span></code></pre></div><h2 id="__init__py-的作用"><code>__init__.py</code> 的作用</h2>
<p><code>__init__.py</code> 是套件的初始化檔案，它在套件被導入時執行。</p>
<h3 id="實際範例hook-系統的-__init__py">實際範例：Hook 系統的 <code>__init__.py</code></h3>
<p>來自 <code>.claude/lib/__init__.py</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">Claude Hooks 共用程式庫
</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">提供 Hook 腳本共用的工具函式，消除程式碼重複。
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">模組結構:
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s2">- git_utils: Git 操作工具（分支、worktree、專案根目錄）
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s2">- hook_logging: Hook 日誌系統
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s2">- hook_io: Hook 輸入輸出處理
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 從子模組導入並重新匯出</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="kn">from</span> <span class="nn">.hook_io</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">read_hook_input</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">write_hook_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">create_pretooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="n">create_posttooluse_output</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="kn">from</span> <span class="nn">.config_loader</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="n">load_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="n">load_agents_config</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="n">load_quality_rules</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">    <span class="n">clear_config_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">
</span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="c1"># 定義公開 API</span>
</span></span><span class="line"><span class="ln">39</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">40</span><span class="cl">    <span class="c1"># git_utils</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="s2">&#34;run_git_command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="s2">&#34;get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="c1"># ... 省略其他</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">
</span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.28.0&#34;</span></span></span></code></pre></div><h3 id="__init__py-的三個主要功能"><code>__init__.py</code> 的三個主要功能</h3>
<h4 id="1-宣告套件身份">1. 宣告套件身份</h4>
<p>空的 <code>__init__.py</code> 也能讓目錄成為套件：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># lib/__init__.py（最簡形式）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># 即使是空檔案，也使 lib 成為套件</span></span></span></code></pre></div><h4 id="2-定義公開-api">2. 定義公開 API</h4>
<p>透過 <code>__all__</code> 列表控制 <code>from package import *</code> 的行為：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;run_git_command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s2">&#34;get_current_branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="s2">&#34;setup_hook_logging&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># 當使用者執行 from lib import * 時</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 只會導入 __all__ 中列出的名稱</span></span></span></code></pre></div><h4 id="3-簡化導入路徑">3. 簡化導入路徑</h4>
<p>使用者可以直接從套件導入，而不需要知道子模組：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 有 __init__.py 的重新匯出：簡潔</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">setup_hook_logging</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 沒有 __init__.py 的重新匯出：冗長</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span></span></span></code></pre></div><h2 id="相對導入與絕對導入">相對導入與絕對導入</h2>
<h3 id="相對導入使用-">相對導入（使用 <code>.</code>）</h3>
<p>在套件內部使用相對導入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在 lib/config_loader.py 中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">.git_utils</span> <span class="kn">import</span> <span class="n">get_project_root</span>  <span class="c1"># 同級模組</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">hook_io</span>                     <span class="c1"># 導入整個模組</span></span></span></code></pre></div><h3 id="絕對導入">絕對導入</h3>
<p>從套件外部或在腳本中使用絕對導入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在 .claude/hooks/some_hook.py 中</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>  <span class="c1"># 絕對導入</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h2 id="套件版本管理">套件版本管理</h2>
<p>在 <code>__init__.py</code> 中定義版本號是常見做法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">__version__</span> <span class="o">=</span> <span class="s2">&#34;0.28.0&#34;</span></span></span></code></pre></div><p>這樣使用者可以查詢版本：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">lib</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">__version__</span><span class="p">)</span>  <span class="c1"># &#34;0.28.0&#34;</span></span></span></code></pre></div><h2 id="模組載入順序">模組載入順序</h2>
<p>Python 搜尋模組的順序：</p>
<ol>
<li>內建模組</li>
<li><code>sys.path[0]</code>（腳本所在目錄）</li>
<li><code>PYTHONPATH</code> 環境變數</li>
<li>標準庫</li>
<li>site-packages（第三方套件）</li>
</ol>
<h3 id="實際範例hook-腳本的路徑設定">實際範例：Hook 腳本的路徑設定</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;Branch Verify Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 將 lib 目錄加入搜尋路徑</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 現在可以導入 lib 中的模組</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span></span></span></code></pre></div><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"># 不好：a.py 和 b.py 互相導入</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"># a.py</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">from</span> <span class="nn">b</span> <span class="kn">import</span> <span class="n">func_b</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># b.py</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kn">from</span> <span class="nn">a</span> <span class="kn">import</span> <span class="n">func_a</span>  <span class="c1"># 循環導入！</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 好：重構為第三個模組</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># common.py</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">shared_function</span><span class="p">():</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># a.py</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">common</span> <span class="kn">import</span> <span class="n">shared_function</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># b.py</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kn">from</span> <span class="nn">common</span> <span class="kn">import</span> <span class="n">shared_function</span></span></span></code></pre></div><h3 id="2-延遲導入">2. 延遲導入</h3>
<p>對於可選依賴或避免循環導入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">load_yaml_config</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="c1"># 只在需要時才導入</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln">5</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="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">        <span class="c1"># 備案方案</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">        <span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="o">...</span><span class="p">)</span></span></span></code></pre></div><h3 id="3-清晰的模組邊界">3. 清晰的模組邊界</h3>
<p>每個模組應該有單一、明確的職責：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">lib/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── git_utils.py      # Git 操作（單一職責）
</span></span><span class="line"><span class="ln">3</span><span class="cl">├── hook_io.py        # 輸入輸出處理（單一職責）
</span></span><span class="line"><span class="ln">4</span><span class="cl">├── hook_logging.py   # 日誌系統（單一職責）
</span></span><span class="line"><span class="ln">5</span><span class="cl">└── config_loader.py  # 配置載入（單一職責）</span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼 <code>__init__.py</code> 使用 <code>from .git_utils import ...</code> 而不是 <code>from git_utils import ...</code>？</li>
<li>Hook 腳本中的 <code>sys.path.insert(0, ...)</code> 為什麼使用 <code>0</code> 作為索引？</li>
<li><code>__all__</code> 列表的作用是什麼？如果不定義會怎樣？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<p>閱讀 <code>.claude/lib/__init__.py</code>，回答以下問題：</p>
<ol>
<li>這個套件匯出了多少個公開函式？</li>
<li>套件的版本號是多少？</li>
<li>如果要新增一個 <code>utils.py</code> 模組並匯出 <code>format_message</code> 函式，需要修改哪些地方？</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/01-basics/philosophy/" data-link-title="1.1 Python 哲學與設計理念" data-link-desc="理解 Python 的核心設計原則">Python 哲學與設計理念</a></em>
<em>下一章：<a href="/blog/python/01-basics/imports/" data-link-title="1.4 導入機制與路徑管理" data-link-desc="解決模組找不到的問題">導入機制與路徑管理</a></em></p>
]]></content:encoded></item><item><title>1.4 導入機制與路徑管理</title><link>https://tarrragon.github.io/blog/python/01-basics/imports/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/python/01-basics/imports/</guid><description>&lt;p>「ModuleNotFoundError」是 Python 開發者最常遇到的錯誤之一。理解導入機制可以幫助你快速解決這類問題。&lt;/p>
&lt;blockquote>
&lt;p>承接提示：import 問題通常是 script、module、package 與執行位置共同造成的結果，而非單一語法問題。如果還不確定這幾個概念的差異，請先閱讀 &lt;a href="https://tarrragon.github.io/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案&lt;/a>。&lt;/p>&lt;/blockquote>
&lt;h2 id="模組搜尋路徑">模組搜尋路徑&lt;/h2>
&lt;p>Python 使用 &lt;code>sys.path&lt;/code> 列表來搜尋模組。你可以查看當前的搜尋路徑：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">path&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>典型輸出：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">/current/script/directory # 腳本所在目錄
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">/usr/local/lib/python3.11 # 標準庫
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">/usr/local/lib/python3.11/site-packages # 第三方套件&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="hook-腳本的導入問題">Hook 腳本的導入問題&lt;/h2>
&lt;h3 id="問題情境">問題情境&lt;/h3>
&lt;p>Hook 腳本位於 &lt;code>.claude/hooks/&lt;/code>，共用模組位於 &lt;code>.claude/lib/&lt;/code>：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">.claude/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">├── hooks/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">│ └── branch-verify-hook.py # 需要導入 lib 的模組
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">└── lib/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> ├── __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl"> └── git_utils.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果直接在 Hook 腳本中寫 &lt;code>from git_utils import ...&lt;/code>，會得到 &lt;code>ModuleNotFoundError&lt;/code>。&lt;/p>
&lt;h3 id="解決方案">解決方案&lt;/h3>
&lt;p>在導入前將 lib 目錄加入搜尋路徑：&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="ch">#!/usr/bin/env python3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Branch Verify Hook&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">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"> 6&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="c1"># 計算 lib 目錄的路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="c1"># __file__ = .claude/hooks/branch-verify-hook.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># parent = .claude/hooks/&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"># parent.parent = .claude/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">&lt;span class="c1"># parent.parent / &amp;#34;lib&amp;#34; = .claude/lib/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="n">lib_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="c1"># 插入到搜尋路徑的最前面&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">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lib_path&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"># 現在可以導入了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">git_utils&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_current_branch&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">hook_io&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">read_hook_input&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="為什麼用-insert0--而不是-append">為什麼用 &lt;code>insert(0, ...)&lt;/code> 而不是 &lt;code>append(...)&lt;/code>？&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 優先搜尋我們的模組（推薦）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lib_path&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 最後才搜尋我們的模組（可能被標準庫覆蓋）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n">sys&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lib_path&lt;/span>&lt;span class="p">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你的模組名稱與標準庫衝突（例如 &lt;code>email.py&lt;/code>），使用 &lt;code>append&lt;/code> 會導致導入標準庫而非你的模組。&lt;/p>
&lt;h2 id="使用-path-物件">使用 Path 物件&lt;/h2>
&lt;h3 id="path-的基本操作">Path 的基本操作&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">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"> 2&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="c1"># 取得目前檔案的路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n">current_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 取得父目錄&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="n">parent_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">current_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="c1"># 組合路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n">lib_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parent_dir&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="s2">&amp;#34;lib&amp;#34;&lt;/span> &lt;span class="c1"># 使用 / 運算子&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">&lt;span class="c1"># 轉換為字串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="n">lib_path_str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lib_path&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="路徑解析的陷阱">路徑解析的陷阱&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># __file__ 可能是相對路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 可能是 &amp;#34;./hooks/my_hook.py&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 轉換為絕對路徑&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n">absolute_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">resolve&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">absolute_path&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># &amp;#34;/home/user/project/.claude/hooks/my_hook.py&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="環境變數方式">環境變數方式&lt;/h2>
&lt;p>另一種方法是使用 &lt;code>PYTHONPATH&lt;/code> 環境變數：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在 shell 中設定&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">:/path/to/.claude/lib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 然後執行腳本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">python .claude/hooks/my_hook.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="專案根目錄的取得">專案根目錄的取得&lt;/h2>
&lt;p>Hook 系統中經常需要取得專案根目錄：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_project_root&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">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"> 獲取專案根目錄（git 倉庫根目錄）
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="s2"> Returns:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="s2"> str: 專案根目錄路徑
&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="n">success&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">run_git_command&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s2">&amp;#34;rev-parse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--show-toplevel&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">success&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getcwd&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>使用範例：&lt;/p></description><content:encoded><![CDATA[<p>「ModuleNotFoundError」是 Python 開發者最常遇到的錯誤之一。理解導入機制可以幫助你快速解決這類問題。</p>
<blockquote>
<p>承接提示：import 問題通常是 script、module、package 與執行位置共同造成的結果，而非單一語法問題。如果還不確定這幾個概念的差異，請先閱讀 <a href="/blog/python/01-basics/script-to-package/" data-link-title="1.2 從單一 script 到多檔案專案" data-link-desc="理解 Python 程式如何從單一 .py 檔案長成 module、package 與可測試專案">從單一 script 到多檔案專案</a>。</p></blockquote>
<h2 id="模組搜尋路徑">模組搜尋路徑</h2>
<p>Python 使用 <code>sys.path</code> 列表來搜尋模組。你可以查看當前的搜尋路徑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span></span></code></pre></div><p>典型輸出：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">/current/script/directory     # 腳本所在目錄
</span></span><span class="line"><span class="ln">2</span><span class="cl">/usr/local/lib/python3.11     # 標準庫
</span></span><span class="line"><span class="ln">3</span><span class="cl">/usr/local/lib/python3.11/site-packages  # 第三方套件</span></span></code></pre></div><h2 id="hook-腳本的導入問題">Hook 腳本的導入問題</h2>
<h3 id="問題情境">問題情境</h3>
<p>Hook 腳本位於 <code>.claude/hooks/</code>，共用模組位於 <code>.claude/lib/</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">.claude/
</span></span><span class="line"><span class="ln">2</span><span class="cl">├── hooks/
</span></span><span class="line"><span class="ln">3</span><span class="cl">│   └── branch-verify-hook.py    # 需要導入 lib 的模組
</span></span><span class="line"><span class="ln">4</span><span class="cl">└── lib/
</span></span><span class="line"><span class="ln">5</span><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="ln">6</span><span class="cl">    └── git_utils.py</span></span></code></pre></div><p>如果直接在 Hook 腳本中寫 <code>from git_utils import ...</code>，會得到 <code>ModuleNotFoundError</code>。</p>
<h3 id="解決方案">解決方案</h3>
<p>在導入前將 lib 目錄加入搜尋路徑：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;Branch Verify Hook&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 5</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"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 計算 lib 目錄的路徑</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># __file__ = .claude/hooks/branch-verify-hook.py</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># parent = .claude/hooks/</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># parent.parent = .claude/</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># parent.parent / &#34;lib&#34; = .claude/lib/</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">lib_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 插入到搜尋路徑的最前面</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># 現在可以導入了</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h3 id="為什麼用-insert0--而不是-append">為什麼用 <code>insert(0, ...)</code> 而不是 <code>append(...)</code>？</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 優先搜尋我們的模組（推薦）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 最後才搜尋我們的模組（可能被標準庫覆蓋）</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">lib_path</span><span class="p">))</span></span></span></code></pre></div><p>如果你的模組名稱與標準庫衝突（例如 <code>email.py</code>），使用 <code>append</code> 會導致導入標準庫而非你的模組。</p>
<h2 id="使用-path-物件">使用 Path 物件</h2>
<h3 id="path-的基本操作">Path 的基本操作</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">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 取得目前檔案的路徑</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">current_file</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 取得父目錄</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">parent_dir</span> <span class="o">=</span> <span class="n">current_file</span><span class="o">.</span><span class="n">parent</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 組合路徑</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">lib_path</span> <span class="o">=</span> <span class="n">parent_dir</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span>  <span class="c1"># 使用 / 運算子</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># 轉換為字串</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">lib_path_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">lib_path</span><span class="p">)</span></span></span></code></pre></div><h3 id="路徑解析的陷阱">路徑解析的陷阱</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># __file__ 可能是相對路徑</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>  <span class="c1"># 可能是 &#34;./hooks/my_hook.py&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 轉換為絕對路徑</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">absolute_path</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">resolve</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">)</span>  <span class="c1"># &#34;/home/user/project/.claude/hooks/my_hook.py&#34;</span></span></span></code></pre></div><h2 id="環境變數方式">環境變數方式</h2>
<p>另一種方法是使用 <code>PYTHONPATH</code> 環境變數：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在 shell 中設定</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">export</span> <span class="nv">PYTHONPATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PYTHONPATH</span><span class="si">}</span><span class="s2">:/path/to/.claude/lib&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 然後執行腳本</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">python .claude/hooks/my_hook.py</span></span></code></pre></div><h2 id="專案根目錄的取得">專案根目錄的取得</h2>
<p>Hook 系統中經常需要取得專案根目錄：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">def</span> <span class="nf">get_project_root</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s2">    獲取專案根目錄（git 倉庫根目錄）
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">    Returns:
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="s2">        str: 專案根目錄路徑
</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="n">success</span><span class="p">,</span> <span class="n">output</span> <span class="o">=</span> <span class="n">run_git_command</span><span class="p">([</span><span class="s2">&#34;rev-parse&#34;</span><span class="p">,</span> <span class="s2">&#34;--show-toplevel&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">    <span class="k">return</span> <span class="n">output</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span></span></span></code></pre></div><p>使用範例：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">root</span> <span class="o">=</span> <span class="n">get_project_root</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">config_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="s2">&#34;.claude&#34;</span><span class="p">,</span> <span class="s2">&#34;config.json&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="常見錯誤與解決">常見錯誤與解決</h2>
<h3 id="錯誤-1-modulenotfounderror">錯誤 1: ModuleNotFoundError</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">ModuleNotFoundError: No module named &#39;git_utils&#39;</span></span></code></pre></div><p><strong>解決方案</strong>：確認 <code>sys.path.insert()</code> 在導入語句之前。</p>
<h3 id="錯誤-2-相對導入錯誤">錯誤 2: 相對導入錯誤</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">ImportError: attempted relative import with no known parent package</span></span></code></pre></div><p><strong>原因</strong>：在腳本中使用相對導入。</p>
<p><strong>解決方案</strong>：在腳本中使用絕對導入，相對導入只用於套件內部。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 錯誤：在腳本中使用相對導入</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">.lib</span> <span class="kn">import</span> <span class="n">git_utils</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 正確：使用絕對導入</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kn">from</span> <span class="nn">lib</span> <span class="kn">import</span> <span class="n">git_utils</span></span></span></code></pre></div><h3 id="錯誤-3-循環導入">錯誤 3: 循環導入</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">ImportError: cannot import name &#39;xxx&#39; from partially initialized module</span></span></code></pre></div><p><strong>解決方案</strong>：重構程式碼，避免模組間互相依賴。</p>
<h2 id="導入風格指南">導入風格指南</h2>
<h3 id="導入順序pep-8">導入順序（PEP 8）</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 1. 標準庫</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># 2. 第三方套件</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 3. 本地模組</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span></span></span></code></pre></div><h3 id="避免-import-">避免 <code>import *</code></h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 不推薦</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="o">*</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 推薦</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h3 id="長導入的換行">長導入的換行</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">lib.git_utils</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">run_git_command</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">get_current_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">get_project_root</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">get_worktree_list</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">is_protected_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">is_allowed_branch</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="p">)</span></span></span></code></pre></div><h2 id="實際範例完整的-hook-腳本開頭">實際範例：完整的 Hook 腳本開頭</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="s2">Branch Verify Hook
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="s2">驗證當前分支是否適合進行編輯操作。
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="c1"># ===== 標準庫導入 =====</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="c1"># ===== 路徑設定 =====</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c1"># 將 lib 目錄加入搜尋路徑</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span><span class="o">.</span><span class="n">parent</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="s2">&#34;lib&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1"># ===== 本地模組導入 =====</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="kn">from</span> <span class="nn">git_utils</span> <span class="kn">import</span> <span class="n">get_current_branch</span><span class="p">,</span> <span class="n">is_protected_branch</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_io</span> <span class="kn">import</span> <span class="n">read_hook_input</span><span class="p">,</span> <span class="n">write_hook_output</span><span class="p">,</span> <span class="n">create_pretooluse_output</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="kn">from</span> <span class="nn">hook_logging</span> <span class="kn">import</span> <span class="n">setup_hook_logging</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># ===== 初始化 =====</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">logger</span> <span class="o">=</span> <span class="n">setup_hook_logging</span><span class="p">(</span><span class="s2">&#34;branch-verify&#34;</span><span class="p">)</span></span></span></code></pre></div><h2 id="思考題">思考題</h2>
<ol>
<li>為什麼不把 lib 目錄加入 <code>PYTHONPATH</code> 環境變數，而要在每個腳本中設定 <code>sys.path</code>？</li>
<li><code>Path(__file__).resolve()</code> 和 <code>Path(__file__)</code> 有什麼區別？</li>
<li>如何驗證一個路徑是否已經在 <code>sys.path</code> 中？</li>
</ol>
<h2 id="實作練習">實作練習</h2>
<ol>
<li>寫一個函式，列出 <code>sys.path</code> 中所有存在的目錄</li>
<li>建立一個簡單的模組，然後在另一個檔案中使用兩種不同的方式導入它</li>
</ol>
<hr>
<p><em>上一章：<a href="/blog/python/01-basics/modules/" data-link-title="1.3 模組與套件組織" data-link-desc="理解 Python 的模組系統和套件結構">模組與套件組織</a></em>
<em>下一模組：<a href="/blog/python/02-type-system/" data-link-title="模組二：型別系統" data-link-desc="現代 Python 的型別提示與資料結構">型別系統</a></em></p>
]]></content:encoded></item></channel></rss>