IanChenAboutPosts

Python 的 package 與 module

avatar奕安Mar 13, 2018

這篇是閱讀 python3 官方文件-Module 並進行測試後的一些理解 文中也會提供我認為的使用方法以及實際在專案中遇到的問題跟解決方式。

文章會先從 Modlue(模組)下手,再依序提到 Pacakge 的概念。其次再比較兩者在 import 時的差別。

Module (模組)

在 python 專案的組建架構中,模組可以說是最小的層級。任何一個以 .py 為副檔名的 python 程式碼都被視為模組。 比如說以下的 hello.py 就是一個簡單的模組。在這種情況下,也可以說有一個模組叫做 hello

#hello.py
def say_hi():
    print('Hello, World!')
 
if __name__ == '__main__':
    say_hi()

if __name__ == "__main__"

在剛剛的例子裡面,有寫到 if __name__ == '__main__' 這個判斷式,這是 python interpreter 用來分辨這個模組是被直接執行還是被其他模組匯入(import)的重要機制。python interpreter 在開始執行之後就會初始化 __name__ 這個特別的環境變數,當我們直接執行某個模組的時候,__name__ 變數就會被設為__main__這個字串值。

python hello.py
# --- 所謂的直接執行,就是這個意思。

如果想知道當前 interpreter 有哪些環境變數的話,可以在直接呼叫 dir() 函式,__name__ 變數就跑出來了。

python interpreter

匯入既有模組

一般來說,會把 python 程式碼存在檔案中就是為了重複使用這些程式碼,也就是把一些函式包含在 hello 這個模組中。

mod import

假設現在同一個資料夾下的另一個檔案 another.py中想要使用 hello.py 中的函式,我們可以將 hello.py 模組匯入(import)。

# another.py
import hello
if __name__ == '__main__':
    hello.say_hi()

官方文件中有提到,為了提升效率,不管專案中有幾個檔案匯入同個模組,任何模組都只會被執行一次。此外,在匯入模組的時候,interpreter 會把模組裡面的句子都先執行過一次,除非是初始化的片段,不然我都會把他包在另外的函式裡面,避免副作用(side effect) 產生。

python 也支援不同的 import 模式,可以直接查看 官方文件 來進一步了解。

Package(套件)

在一個專案架構中,除了可以依據功能將不同函式放在同一個模組中。也可以用同樣的依據將相似的模組放在同一個資料夾中。python 將套件作為資料夾架構的強化版本。在官方文件中的定義,任何含有 __init__.py的資料夾就稱為一個套件。__init__.py可以是全空的文件,也可以定義額外內容來產生額外作用。詳見 官方文件

我想以目前正在開發的簡單小專案為例解釋套件的用法。在 Github 上面,我有一個放置自己設定檔案的 repo,並在其中附上了一個支援多平台的快速安裝小專案,命名為 autosetup,以下是資料夾的架構。

+ /autosetup
    + /installers
        + \_\_init\_\_.py
        + viminstaller.py
        + pyinstaller.py
    + \_\_init\_\_.py
    + \_\_main\_\_.py
    + setup.py
    + config.py
    + util.py

setup.py是專案中的主程式,他會分別呼叫installers資料夾中的不同安裝腳本。而那些安裝腳本會分別從util.py匯入常用函式。所有的程式都會從 config.py 中匯入一些使用者設定。

依據剛剛的定義,這個大資料夾中其實包含了兩個套件,分別是 autosetupinstallers,而且因為 installers套件是存在於autosetup套件下,所以可以將 installers 稱為 autosetup子套件(subpackage)

python 提供絕對(absolute)相對(relative)兩種不同的匯入方法。簡單來說。絕對的說法就是尋找一個固定的參考點來描述物體位置,而相對是透過另一個會變動的參考來描述物體位置,假設我想要描述交通大學的位址。

  • 絕對路徑的描述邏輯: 新竹市大學路 1001 號
  • 相對路徑的描述邏輯: 就...清華大學店隔壁的學校

兩者的差別就只是參照的角度不一樣,更多的觀念可以查看 鳥哥的 linux 私房菜

在絕對路徑中,python interpreter 會預先偵測目前最上層的套件名稱。在存取時都是以最上層開始一層層往下解析名稱。而相對路徑則是以當前檔案進行存取,.代表當前目錄,在這一外每多一個點都是往上一層套件的意思。可以使用 ..util, ...util等等...,要注意一下..跟後面的套件名稱不能分開。

1. 從子套件中匯入

  • setup.py 匯入 installer.pyconfig.py (匯入子套件的模組)
#setup.py
from .installers import pyinstaller #相對路徑匯入
from autosetup.installer import pyinstaller #絕對路徑匯入

2. 從上層套件中匯入

  • pyinstaller.py 匯入 util.pyinstall_program 函式
#pyinstaller.py
from ..util import install_program #相對路徑匯入
from autosetup import install_program #絕對路徑匯入

__main__.py

把一個專案打包成套件之後,或許也會想要有一個屬於專案的程式執行點,讓整個專案能夠簡單運行起來,就如同模組是可以被執行的,套件其實也是可以執行的。在模組裡面可以使用 if __name__ == '__main__'來達到效果,對應到套件中就是 __main__.py了。當我們執行一套件時,python interpreter 會去尋找套件目錄下的 __main__.py,並執行它,所以只要把對應的程式碼寫在 __main__.py 之中就可以了。

#__main__.py
from . import setup
if __name__ ==  '__main__':
    setup.main()

匯入機制解說

python 的匯入機制其實十分簡單,但對新手來說卻存在一些小陷阱。 稍微注意一下可以發現,我把 relative/absolute import 專門寫在了 package 的標題下方。依據我自己的理解方式,匯入的機制分別依據模組跟套件有兩種不同規則。

  • 在模組中

    只能透過 sys.path 中有指定的目錄來進行匯入,所以如果單純想要匯入上層目錄的模組,就必須先使用 sys.path.append('..')之後才能直接匯入。

  • 在套件中 可以使用 相對/絕對 的方式來進行其他模組或套件的存取。

使用心得

我認為模組跟套件是 python 語言非常核心的概念之一。在 python 語言中,如果是想要達到包裝或是命名空間的效果的話,不一定要使用 class 方法,可以視情況採用套件來達到目的。