Python import 簡易教學

李松錡
7 min readApr 12, 2018

--

前幾天看到一個 python 教學網站發出的廣告信,當中有一個 topic 是 “why Python import is so sxxk”,這讓我想起曾經有稍微搞懂但後來又忘記的 Python import…

Pyhon import 其實主要分成兩種:

  • Absolute imoprt
  • Explicit relative import

也就是一個是根據絕對路徑 import 的,一個是根據相對路徑 impot 的。但 Python 的 import 是根據 module 來分的,所以弄來弄去會讓人感到十分混亂。

Module

Python 的 package 其實視為一個資料夾包起來的所有東西,但是這個資料夾裡面一定要有一個檔案 __init__.py (內容是空的也沒關係)(更新: python 3.3 以後不強制要有 __init__.py 檔案),Python 在解譯的時候才會把這個資料夾視為一個 package,而裡面的每一個檔案都是一個 module。

所以我們可以先做一個最簡單的 example 來看看:

import_example
├── m1
│ ├── __init__.py
│ └── m1.py
└── main.py

其中檔案內容如下:

如此一來我們就可以在 import_example 的位置執行:

import_example $ python3 main.py
I am foo in m1

就可以正常執行。在 main.py 裡面的 import 語句還可以稍微做一些解說, from m1.m1 import foo

這其中 m1.m1 的部分,第一個 m1 指的是這個 package,你也可以想成就是指 m1 這個資料夾。第二個 m1 指的則是這個 package 中的 module 的 m1 檔案。最後一個 foo 則是我們要從檔案中 import 的 funcion, class 或變數。

看完這個最簡單的範例以後,接下來我們要處理更進階的範例,其檔案結構如下:

import_example
├── m1
│ ├── __init__.py
│ ├── m1.py
│ └── m2
│ ├── __init__.py
│ └── m2.py
└── main.py

然後每個檔案的內容:

我們一樣在 import_example 的位置執行:

import_example $ python3 main.py
I am foo in m1
I am bar in m2

一樣可以正常執行,但這次的 import 怎麼看起來有點不太對勁。在 main.py 裡面的 import 語句沒有變,規則也跟前面的範例一模一樣,但在 m1.py 裡面的 import 語句看起來似乎有點怪怪的,為什麼是 m1.m2.m2? 為什麼多了一個 m1呢?這就是 import 不好懂的地方了

Absolute Import

還記得我們執行 python3 main.py的位置嗎?我們所在的位置是 import_example 這個最上層的資料夾,而 python 在搜尋 module 的時候,除了系統預設的 library 路徑外,還會把當前執行的位置也加入搜尋路徑,因此 main.py 才能夠搜尋到 m1 這個 module。在知道要 import m1/m1.py 後,Python 也會去解譯 m1/m1.py 這個檔案,但此時我們的 module 搜索路徑並不會增加,也因此,如果我們在 m1.py 裡面的 import 語句如果改成 from m2.m2 import bar ,則此時 Python 會找不到 m2 這個 package 和他底下的 module (除非你在之前的搜索路徑中也有 m2 這個 package,例如 import_example/m2)。

像這種 import 的宣告方式,是從整個專案的 root 開始指定完整的搜尋 module 路徑,就是 absolute import。在 PEP8 中就有描述到推薦使用這種方式:

Absolute imports are recommended, as they are usually more readable and tend to be better behaved (or at least give better error messages) if the import system is incorrectly configured (such as when a directory inside a package ends up on sys.path)

但用這種方式,可想而知,萬一我們的專案結構非常複雜,可能專案做一做要改結構,少掉一層資料夾,或是不得已要把裡面的 module 拷貝給別人用,那真的會是一場惡夢,因此 PEP8 也有提到

However, explicit relative imports are an acceptable alternative to absolute imports, especially when dealing with complex package layouts where using absolute imports would be unnecessarily verbose

Explicit Relative Import

PEP8 中推薦的方式是用 explicit relative import 來解決上面那個問題,只要把上面範例中 m1.py 的 import 語句做小小的修改即可: from .m2.m2 import bar 就可以了。我們可以看到本來的 m1 可以不必寫,取而代之的是一個 . 。除了可以用 . 代替現在的這個位置以外,還可以搜尋上一層,例如下面這個範例,其檔案結構如下

import_example
├── m1
│ ├── __init__.py
│ ├── config
│ │ ├── __init__.py
│ │ └── config.py
│ └── m2
│ ├── __init__.py
│ └── m2.py
└── main.py

當中每個檔案的程式如下:

那麼究竟是 absolute import 還是 explicit relative import 好呢?兩者各有其利弊, absolute import 在整個 import 過程會非常好理解,尤其是專案結構複雜時,會比 relative 更好理解,但缺點就是專案結構不好修改;而 relative 則在專案結構調整的時候會更加省事,可是卻不利於事後 trace code 的理解。PEP8 本身是比較推薦用 absolute import 的,因此如果在專案結構並不複雜或你並不怎麼會調整專案結構的時候,可以優先考量使用 absolute import,而如果專案結構較為複雜,或還在開發階段常常需要調整結構時,則考量使用 relative import

最後還有一個和 import 較為相關的主題,就是如果有時候需要單獨執行某一個 package 中的一個 py 檔,例如做簡單的 test,或是試試看某個 function 怎麼用等等時,我們前面也看到,不論你用 absolute import 或 explicit relative import,如果直接用 python m1/m1.py 這樣的方式執行,都很有可能會有 import 路徑不正確的狀況 (因為在 module 內的 import 都是從專案的根開始找,而不論你執行 python m1/m1.py 或 cd 到資料夾內執行 python m1.py,搜尋的路徑都不是專案的根部),所以 python 還有提供一個 -m 的選項,然後我們就可以這樣單獨執行 module 內的 py 檔:

import_example $ python3 -m m1.m1

想必大家直接用類比的方式,就可以知道 -m 後面要怎麼解讀了, m1.m1 中的第一個 m1 就是這個 package, 第二個 m1 則是該 package 中的 m1 module py 檔

--

--