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 檔案
    • 可以透過 importfrom 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/ ✅ 成功 ✅ 成功

✅ 最佳實踐

  1. 統一從根目錄啟動專案
    • 使用 main.py 作為 entry point,避免直接執行模組內部的 py 檔案
# main.py
from app.core import run

if __name__ == "__main__":
    run()
  1. 避免直接執行子模組的 script
    • python app/core.py 易出錯
    • 請改用 python -m app.core

📚 延伸補充:為什麼 -m 可以正確 import?

使用 -m 時,Python 會把指定 module 所在 package 的上一層目錄加入 sys.path,這讓整個 package 可以正確地相對或絕對 import。

這也符合開發大型專案的需求,讓每個 module 可以互相引用而不報錯。


希望這篇可以幫你釐清 scriptmodule 的差異,也知道為什麼在子目錄中就算寫絕對 import 也還是會錯!