Python 模組與套件:相對與絕對匯入
簡介
在 Python 專案中,模組與套件是組織程式碼的基礎。隨著專案規模擴大,檔案結構會變得越來越複雜,如何正確且易維護地匯入其他程式碼就成了每位開發者必須面對的課題。
相對匯入(relative import)與絕對匯入(absolute import)提供了兩套不同的路徑定位方式。掌握它們的差異與使用時機,能讓你的程式碼 更易讀、可重用,同時避免常見的 ImportError、循環匯入等問題。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整介紹相對與絕對匯入的使用方式,讓你在日常開發與團隊協作中游刃有餘。
核心概念
1. 絕對匯入(Absolute Import)
絕對匯入以 專案根目錄(或已加入 sys.path 的路徑) 為起點,直接寫出完整的模組路徑。
# 假設專案結構如下
my_project/
│
├─ app/
│ ├─ __init__.py
│ ├─ config.py
│ └─ main.py
│
└─ utils/
├─ __init__.py
└─ helpers.py
在 app/main.py 中匯入 utils/helpers.py:
# main.py
from utils.helpers import greet
def run():
greet("Alice")
if __name__ == "__main__":
run()
特點
- 可讀性高:一眼就能看出匯入的來源。
- 不受檔案相對位置影響:即使
main.py移動到其他子目錄,只要utils在 Python 搜尋路徑內,匯入仍然有效。
2. 相對匯入(Relative Import)
相對匯入以 目前模組所在的套件 為基準,使用點(.)表示層級:
.→ 同一層..→ 上一層...→ 上上層,以此類推
同樣的範例,改用相對匯入:
# app/main.py
from ..utils.helpers import greet # 兩個點表示往上回到 my_project,再進入 utils
def run():
greet("Bob")
if __name__ == "__main__":
run()
注意:相對匯入只能在 套件內部 使用(即檔案所在目錄必須含有
__init__.py),否則會拋出ImportError: attempted relative import beyond top-level package。
3. 為什麼同時存在兩種方式?
| 需求 | 建議使用 |
|---|---|
| 跨套件、跨層級的明確匯入 | 絕對匯入 |
| 同一套件內部、頻繁重構的模組 | 相對匯入 |
| 想保持匯入路徑與檔案結構同步 | 相對匯入 |
4. sys.path 與執行入口的影響
Python 會在 sys.path 中搜尋模組。當直接以 python app/main.py 執行時,app 會被視為 腳本目錄,而非套件的一部份,導致相對匯入失效。解決方法:
# 使用 -m 參數讓 Python 把專案當作套件執行
python -m app.main
這樣 app 會被視為套件,.. 的相對匯入就能正常工作。
程式碼範例
範例 1:基本的絕對匯入
# utils/math_ops.py
def add(a, b):
return a + b
def mul(a, b):
return a * b
# app/calc.py
from utils.math_ops import add, mul
print(add(3, 5)) # 8
print(mul(4, 7)) # 28
說明:
calc.py與math_ops.py位於不同套件,使用絕對匯入最直觀。
範例 2:同層套件的相對匯入
# app/services/__init__.py
# (空檔案,讓 Python 把 services 當作套件)
# app/services/logger.py
def log(msg: str):
print(f"[LOG] {msg}")
# app/services/processor.py
from .logger import log # 同層相對匯入
def process(data):
log(f"Processing {data}")
# ... 其他邏輯
說明:使用
.表示「同一層」的logger,若未來services被搬到別的父目錄,匯入路徑仍然正確。
範例 3:跨層級相對匯入與 __main__ 執行
# my_project/__init__.py
# (空檔案)
# my_project/core/__init__.py
# (空檔案)
# my_project/core/base.py
class Base:
def greet(self):
print("Hello from Base")
# my_project/app/__init__.py
# (空檔案)
# my_project/app/main.py
from ..core.base import Base # 往上兩層到 core,再匯入 Base
class App(Base):
def run(self):
self.greet()
print("App is running")
if __name__ == "__main__":
# 必須使用 -m 方式執行,否則相對匯入會失敗
App().run()
執行方式:
python -m my_project.app.main
說明:
..表示「上一層」,..core.base正好對應到my_project/core/base.py。
範例 4:避免循環匯入的技巧
# package/a.py
from .b import func_b # 直接匯入會造成循環
def func_a():
print("A")
func_b()
# package/b.py
def func_b():
print("B")
解法:將共用的函式抽到第三個模組,或在函式內部延遲匯入。
# package/a.py
def func_a():
from .b import func_b # 延遲匯入,僅在執行時才載入
print("A")
func_b()
範例 5:使用 importlib 動態匯入(進階)
# dynamic_loader.py
import importlib
def load_module(module_path: str):
"""根據字串動態匯入模組,支援相對與絕對路徑"""
return importlib.import_module(module_path)
# 例如
mod = load_module('utils.helpers')
mod.greet('Charlie')
說明:在插件化或測試框架中,常需要根據字串決定要匯入哪個模組,
importlib提供了彈性的方式。
常見陷阱與最佳實踐
| 陷阱 | 可能原因 | 解決方案 |
|---|---|---|
ImportError: attempted relative import beyond top-level package |
直接以腳本方式 (python file.py) 執行相對匯入 |
使用 python -m package.module,或將入口腳本放在專案根目錄之外 |
循環匯入 (ImportError: cannot import name ...) |
A 模組匯入 B,B 又匯入 A | 把共用程式碼抽到第三個模組;或在函式內部延遲匯入 |
路徑在 sys.path 中找不到 |
執行環境的工作目錄不同於專案根目錄 | 在 PYTHONPATH 中加入專案根目錄,或在程式開頭 sys.path.append(os.path.abspath('..'))(僅作為臨時方案) |
| 使用絕對匯入時,專案名稱與第三方套件衝突 | 例如自建 requests 套件 |
盡量避免使用與 PyPI 套件相同的名稱,或改用相對匯入降低衝突風險 |
最佳實踐
- 首選絕對匯入:在大型專案中,絕對匯入的可讀性與維護性較佳。
- 相對匯入僅限套件內部:當模組間耦合度很高且頻繁重構時,使用相對匯入可減少路徑更改的成本。
- 統一執行入口:使用
python -m或建立__main__.py,讓相對匯入在任何環境下都能正常工作。 - 避免在
__init__.py中執行大量程式:過多的匯入會拖慢套件載入速度,且易產生循環匯入。 - 加入型別註解與文件字串:讓 IDE 能正確解析匯入路徑,提升開發效率。
實際應用場景
微服務或插件架構
在插件目錄下,每個插件都是獨立的套件。主程式使用importlib或相對匯入載入插件,例如plugins.plugin_a.main。相對匯入讓插件內部的子模組不必關心外部路徑。資料科學專案的模組化
常見的專案會把data/,models/,utils/分別放在不同套件中。分析腳本(scripts/)使用絕對匯入from data.loader import load_csv,而models套件內部則用相對匯入共享模型基礎類別。測試框架
tests/目錄下的測試檔案往往需要匯入專案的模組。使用絕對匯入可以避免測試檔案因相對位置變動而失效,同時在pytest的conftest.py中加入根目錄到PYTHONPATH。CLI 工具的入口
在cli/__main__.py中,通常會寫from .core import run(相對匯入),確保即使使用python -m cli執行,內部結構仍保持一致。
總結
相對匯入與絕對匯入是 Python 內建的兩套模組定位機制,各有適用情境。
- 絕對匯入 提供最直觀、跨套件的引用方式,適合大型、穩定的專案結構。
- 相對匯入 則在同一套件內部、頻繁重構或想保持匯入路徑與目錄同步時顯得更靈活。
在實務開發中,先以絕對匯入為主,必要時再使用相對匯入;同時注意執行入口、sys.path 與循環匯入等常見問題,遵循「統一入口、最小耦合」的原則,就能寫出結構清晰、易維護的 Python 程式碼。祝你在模組與套件的世界裡玩得開心!