以下的測試環境
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
的module
from__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.path
sys.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__
為None
from ..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.app2
D:\hochun\example\python_relative_import>python level1/level2/app2.py
進階練習 3
1 |
|
最後這個練習就讓大家動手玩玩看囉