以下的測試環境
Python: 3.8.3
基礎介紹
- package (套件/包) : 資料夾,含有
__init__.py - module (模塊) : 檔案
- import 的方式有兩種 : 絕對路徑 / 相對路徑
- sys.modules : 是個 dictionary,用於存放已經 import 過的 modules
- sys.path : 是個 list,用於搜尋 import module 的各種路徑
import 流程
雖然 import 的方式有兩種 : 絕對路徑 / 相對路徑
但是 import 的流程是相同的
import xxxModule
- 檢查
xxxModule是否存在於sys.modules - 若存在,則直接從
sys.modules取出使用即可 - 若不存在,則依據 import 的方式來搜尋
xxxModule.py的檔案位置 - 接著生成
xxxModule - 再來放入
sys.modules - 最後執行
xxxModule.py裡面的 source code (以剛生成的xxxModule作為 scope 來執行)
syntax 比較
1 | |
絕對路徑
有了以上的概念後,接著我們利用範例來實際操作下 (範例下載)
為求簡單,這邊 import 的方式都先使用絕對路徑
基礎練習 1
執行 D:\hochun\example\python_absolute_import>python app1.py
1 | |
1 | |
1 | |
輸出
1 | |
說明
from packageA.packageB import moduleB- 檢查
packageA/packageB/moduleB是否存在於sys.modules - 發現沒有,所以依據 import 的方式來搜尋
packageA.py/packageB.py/moduleB.py的檔案位置 - 此處用的是絕對路徑,所以會利用
sys.path來尋找檔案位置 - 有看到
sys.path[0]就是根目錄嗎 ? 就是因為這個路徑,才找的到packageA.py/packageB.py/moduleB.py - 如果在
sys.path中都找不到的話,就會出現ModuleNotFoundError
- 檢查
from packageA import moduleA- 由於
packageA已存在於sys.modules,所以不會執行packageA.py - 但是
moduleA還不存在於sys.modules,所以會依據 import 的方式來搜尋moduleA.py的檔案位置 - 此處用的是絕對路徑,所以會利用
sys.path來尋找檔案位置
- 由於
import packageA- 經過上面的說明,很清楚知道 import 同樣的 package or module,只要
sys.modules中還存在,就不會執行第二次
- 經過上面的說明,很清楚知道 import 同樣的 package or module,只要
print(packageA)- 有注意到嗎 ? 輸出的結果是一個名叫
packageA的modulefrom__init__.py
- 有注意到嗎 ? 輸出的結果是一個名叫
基礎練習 2
執行 D:\hochun\example\python_absolute_import>python app2.py
1 | |
輸出
1 | |
觀察
- import 之後,或是宣告一個變數之後,我們可以在
dir()中看到增加的名稱
基礎練習 3
執行 D:\hochun\example\python_absolute_import>python app3_1.py
1 | |
1 | |
1 | |
輸出
1 | |
觀察
- 不論在哪隻 module 中,
import sys後的sys是指向相同的記憶體位置 - 所以,不論在哪隻 module 中,我們常用的
sys.modules/sys.path也都會指向相同的記憶體位置
基礎練習 4
執行 D:\hochun\example\python_absolute_import>python app4_1.py
1 | |
1 | |
1 | |
輸出
1 | |
觀察
app4_1.py依賴於app4_2.py- 想想看如果情況變成兩隻 module 互相依賴,那該怎麼辦 ? (別擔心,待下面範例解釋)
基礎練習 5
執行 D:\hochun\example\python_absolute_import>python app5_1.py
1 | |
1 | |
1 | |
輸出
1 | |
1 | |
1 | |
觀察
app5_1.py/app5_2.py互相依賴- 依據
app5_1.py不同的寫法 (A) / (B) / (C),輸出結果也不同 - (A) 不會報錯
- (B) 報錯
AttributeError,因為在app5_2.py中,lastName = 'kang'寫在import app5_1之後 - (C) 報錯
ImportError,同理 (B) - 那如果將
app5_2.py中的lastName = 'kang'寫在import app5_1之前,是不是就不會報錯了呢 ? (留給大家 try 看看)
相對路徑
複習下,在import 流程中有提到,雖然 import 的方式有兩種,但是 import 的流程是相同的
前面學習完了 import 的流程與 import 方式之一的絕對路徑
接下來,讓我們把相對路徑也一併搞定吧 ! (範例下載)
1 | |
1 | |
進階練習 1
執行 D:\hochun\example\python_relative_import\level1\level2>python app1.py
1 | |
輸出
1 | |
觀察
- 當下路徑為
D:\hochun\example\python_relative_import\level1\level2 sys.path.append('../..'),增加上上層路徑到sys.pathsys.path = sys.path[1:],刪除sys.path[0](當層路徑)
坑
- 這個範例其實還是絕對路徑,所以尋找 module 會利用
sys.path - 若改為執行
D:\hochun\example\python_relative_import>python level1/level2/app1.py,則會報錯ModuleNotFoundError: No module named 'utils',因為我們修改了sys.path,進而造成在sys.path中找不到 moduleutils,所以才會報錯
進階練習 2-1
執行 D:\hochun\example\python_relative_import\level1\level2>python app2.py
1 | |
輸出
1 | |
觀察
- 當下路徑為
D:\hochun\example\python_relative_import\level1\level2 __name__為__main____package__為Nonefrom ..utils import tool為 import 方式的相對路徑
坑
- 若 import 方式為相對路徑,則利用的不是
sys.path,而是__name__/__package__ - 因為
__package__為None,這被視為最上層路徑,所以無法再用from ..utils import tool,即便改成from .utils import tool也一樣會報錯 - 換句話說,若 module 中有寫到相對路徑,則不能直接下
python指令去 run 該程式,除非使用python -m(如下)
進階練習 2-2
執行 D:\hochun\example\python_relative_import>python -m level1.level2.app2
輸出
1 | |
觀察
- 當下路徑為
D:\hochun\example\python_relative_import python -m後面跟的是level1.level2.app2而非level1/level2/app2.py__package__為level1.level2,因為如此 import 方式的相對路徑才能做到相對的作用
經由上述解釋後,現在的你應該能說出以下兩者的差異吧 !
D:\hochun\example\python_relative_import>python -m level1.level2.app2D:\hochun\example\python_relative_import>python level1/level2/app2.py
進階練習 3
1 | |
最後這個練習就讓大家動手玩玩看囉