Python Import 淺談:script 與 module 的差異
在學習 Python 專案結構時,常會遇到一個讓人困惑的問題:
為什麼從某個檔案執行時可以正常 import,換個方式就報錯了?
這背後的關鍵差異,在於你是以「script」還是「module」的方式執行 Python 檔案。
🧱 基本目錄架構
假設我們的專案結構如下:
myproject/
├── main.py
└── app/
├── __init__.py
├── core.py
└── utils.py
core.py
裡面引用了utils.py
# core.py
from app import utils
這看起來是個合理的絕對 import,照理說應該可以用 python app/core.py
執行吧?
實際上會報錯:
ModuleNotFoundError: No module named 'app'
🤔 script 與 module 的定義是什麼?
-
script(腳本):
- 指的是你直接用
python xxx.py
執行的檔案 - 它會成為
__main__
模組,也就是程式的進入點 - 它沒有包在任何 package 裡,也就無法使用相對 import
- 指的是你直接用
-
module(模組):
- 指的是 Python 中可以被 import 的 .py 檔案
- 可以透過
import
或from xxx import yyy
引用 - 用
python -m package.module
方式執行時,模組有正確的 package 上下文
兩者最大的差異在於:script 是執行入口、module 是被載入的單元。當你想讓某個檔案作為 script 直接執行時,它可能會因為沒處於正確的 package 環境而出錯。
⚠️ 為什麼 python app/core.py
會錯?
這是因為 Python 在執行時,會把你執行的 檔案所在的資料夾 當作第一個 sys.path[0]
,也就是 Python 搜尋模組的起點。
執行方式 | sys.path[0] 設定值 |
---|---|
python app/core.py |
myproject/app |
python -m app.core |
myproject (根目錄) |
當你在 myproject
根目錄執行:
python app/core.py
Python 會把 app/core.py
所在的 app/
資料夾作為起點,這時 from app import utils
就會變成找 app/app/utils.py
,結果找不到就報錯。
此外因為 core.py
是 script 模式,沒有包裹在 package 中,也不能使用 from . import utils
相對 import。
✅ 正確做法:以 module 方式執行
使用 -m
可以指定「從 package 中執行」:
cd myproject
python -m app.core
這樣 Python 就會把 myproject
加入 sys.path
,才能正確找到 app.utils
模組,並允許相對 import。
📌 script vs module 差異整理
執行方式 | 模式 | sys.path[0] 起點 | 絕對 import 是否成功? | 相對 import 是否成功? |
---|---|---|---|---|
python app/core.py |
script 模式 | app/ |
❌ 失敗 | ❌ 無 parent package |
python -m app.core |
module 模式 | myproject/ |
✅ 成功 | ✅ 成功 |
✅ 最佳實踐
- 統一從根目錄啟動專案:
- 使用
main.py
作為 entry point,避免直接執行模組內部的 py 檔案
- 使用
# main.py
from app.core import run
if __name__ == "__main__":
run()
- 避免直接執行子模組的 script:
python app/core.py
易出錯- 請改用
python -m app.core
📚 延伸補充:為什麼 -m
可以正確 import?
使用 -m
時,Python 會把指定 module 所在 package 的上一層目錄加入 sys.path
,這讓整個 package 可以正確地相對或絕對 import。
這也符合開發大型專案的需求,讓每個 module 可以互相引用而不報錯。
希望這篇可以幫你釐清 script
與 module
的差異,也知道為什麼在子目錄中就算寫絕對 import 也還是會錯!