本文 AI 產出,尚未審核
Python 模組與套件(Modules & Packages)
主題:__name__ == '__main__'
簡介
在 Python 中,每一個檔案都是一個模組(module),而模組之間的關係常常透過 import 來建立。當我們直接執行一個 Python 檔案時,Python 會自動為該檔案設定一個特殊變數 __name__,其值為字串 '__main__'。如果該檔案是被其他模組匯入的,__name__ 則會是檔案的模組名稱(不含 .py 副檔名)。
利用 if __name__ == '__main__': 這段判斷式,我們可以**區分「直接執行」與「被匯入」**兩種情況,從而讓程式碼在不同情境下有不同的行為。這是撰寫可重用、可測試、易維護的 Python 程式的基礎技巧,也是每位 Python 開發者必須熟悉的概念。
核心概念
1. __name 變數的來源
- 直接執行:
python myscript.py→myscript.py的__name__為'__main__'。 - 被匯入:
import myscript→ 在myscript.py中,__name__為'myscript'(模組名稱)。
小提醒:
__name__是 Python 內建的全域變數,不需要自行宣告,只要在程式中直接使用即可。
2. 為什麼要使用 if __name__ == '__main__':
| 情境 | 常見需求 | 使用 if __name__ == '__main__': 的好處 |
|---|---|---|
| 腳本測試 | 想快速驗證模組功能 | 測試程式碼只在直接執行時跑,匯入時不會干擾 |
| 範例說明 | 撰寫教學或範例程式 | 讀者可以直接執行範例,同時模組仍能被其他程式使用 |
| CLI 工具 | 建立命令列介面 | 只在作為主程式時解析參數,作為函式庫時不會觸發 CLI |
3. 基本範例
# hello.py
def greet(name: str) -> str:
"""回傳問候語"""
return f"Hello, {name}!"
# 只有在直接執行此檔案時才會跑下面的程式
if __name__ == '__main__':
# 這裡可以放測試或示範程式
print(greet("World"))
執行 python hello.py 會印出 Hello, World!;而如果在其他檔案中 import hello,則不會自動執行 print,只會提供 greet 函式供使用。
4. 多個模組互相引用的情況
# a.py
def func_a():
print("Function A")
if __name__ == '__main__':
from b import func_b
func_b() # 只在直接執行 a.py 時呼叫 b 的功能
# b.py
def func_b():
print("Function B")
if __name__ == '__main__':
from a import func_a
func_a() # 只在直接執行 b.py 時呼叫 a 的功能
說明:兩個檔案互相 import,但因為 if __name__ == '__main__' 的保護,不會產生循環 import,除非同時直接執行兩個檔案。
5. 結合 argparse 建立 CLI
# calc.py
import argparse
def add(a: int, b: int) -> int:
"""回傳兩數相加的結果"""
return a + b
def sub(a: int, b: int) -> int:
"""回傳兩數相減的結果"""
return a - b
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="簡易計算機")
parser.add_argument('op', choices=['add', 'sub'], help='操作類型')
parser.add_argument('x', type=int, help='第一個數字')
parser.add_argument('y', type=int, help='第二個數字')
args = parser.parse_args()
if args.op == 'add':
print(add(args.x, args.y))
else:
print(sub(args.x, args.y))
使用方式:
$ python calc.py add 3 5
8
$ python calc.py sub 10 4
6
當 calc.py 被其他模組匯入時,add、sub 仍可直接呼叫,而不會執行 argparse 的解析程式。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記加 if __name__ == '__main__': |
測試程式碼會在匯入時自動執行,造成副作用(例如寫檔、印出訊息) | 務必 把測試或示範程式碼包在此判斷式內 |
在判斷式內使用相對匯入 (from .module import ...) |
直接執行時相對匯入會失敗(因為沒有包的上下文) | 改用絕對匯入或在 if 內改寫為 import 的方式 |
把大量程式碼寫在 if 區塊內 |
會讓模組的可讀性下降,且不易單元測試 | 將主要邏輯抽成函式,在 if 中只呼叫這些函式 |
在 if 區塊內直接呼叫 sys.exit() |
匯入模組時會意外終止程式 | 使用 argparse 或自行檢查參數,避免在匯入時觸發退出 |
最佳實踐
- 保持
if __name__ == '__main__':只負責「入口」- 把功能實作放在函式或類別中,讓其他模組可以直接使用。
- 使用
main()函式
這樣做的好處是def main() -> None: # 解析參數、呼叫主要功能 ... if __name__ == '__main__': main()main()可以被單元測試框架直接呼叫。 - 加入型別註解與 docstring
- 讓 IDE、型別檢查工具(mypy)以及使用者都能快速瞭解函式的行為。
- 避免在模組層級執行 I/O
- 讀寫檔案、網路請求等操作應該放在
main()或其他函式內,以免匯入時產生不必要的 I/O。
- 讀寫檔案、網路請求等操作應該放在
實際應用場景
| 場景 | 為什麼需要 __name__ == '__main__' |
範例簡述 |
|---|---|---|
| 資料前處理腳本 | 團隊成員會在不同步驟匯入共用的資料清理函式 | preprocess.py 定義 clean(df);在 if 區塊內寫入 if __name__ == '__main__': 讀取 CSV、呼叫 clean、輸出結果 |
| 機器學習模型訓練 | 同一套程式同時提供「訓練」與「推論」兩種功能 | train.py 包含 train_model()、predict();if 區塊決定是跑訓練流程還是僅載入模型 |
| CLI 工具套件 | 需要提供命令列介面,同時讓套件能作為函式庫被其他程式使用 | 如前面的 calc.py,使用 argparse 只在直接執行時解析參數 |
| 自動化測試 | 測試檔案會匯入被測試的模組,避免測試程式碼干擾測試環境 | test_util.py 只在 if 區塊內執行手動測試,正式測試時由 pytest 呼叫模組內函式 |
| 多層套件入口 | 大型專案可能有 __init__.py 作為套件入口,仍需要執行特定腳本 |
mypackage/__main__.py 讓 python -m mypackage 成為可執行指令,內部仍使用 if __name__ == '__main__': 來啟動主流程 |
總結
__name__是 Python 為每個模組自動設定的全域變數,直接執行時為'__main__',匯入時為模組名稱。- 使用
if __name__ == '__main__':可以將執行入口與函式庫功能分離,讓程式碼在不同情境下表現一致且安全。 - 最佳實踐:把主要邏輯寫成
main()或其他可重用的函式,僅在if區塊內呼叫;避免在模組層級執行 I/O 或產生副作用。 - 這個技巧在 腳本測試、CLI 工具、資料前處理、機器學習工作流 等多種實務場景中都相當重要,是撰寫乾淨、可維護 Python 專案的基礎。
掌握 __name__ == '__main__' 的用法,你就能在寫模組時同時兼顧 可直接執行的便利 與 作為套件被重用的彈性,讓你的 Python 程式更專業、更可靠。祝你寫程式愉快!