Python的包匯入機制
Background
在Python的大型專案中,一般都會用到模組包來組織檔案層次,其中當一個目錄內含有__init__ . py檔案時,就可以視該目錄為一個模組包。
當在模組包中使用import語句的時候,不同的語法會導致不同的模組搜尋匯入方式,常見的匯入方式如下:
- 絕對匯入(absolute import)
- 顯式相對匯入(explicit relative import)
- 隱式相對匯入(implicit relative import)
需要注意的是,這些匯入方式都是對於模組包而言,對於一般的模組還是從sys.path搜尋入手。
Prerequisite
Python指令碼執行的方式?
- Python指令碼執行分為兩種方式:一種是作為top level script執行,另一種則是作為被匯入的包模組執行。
當使用python命令直接執行一個py檔案的時候,該檔案就是以top level script方式執行,此時檔案的__name__屬性則為__main__。
# test.py print __name__ # output __main__
當檔案使用包模組的方式執行的時候,檔案的__name__屬性則為模組的路徑(從top level script的目錄開始),包模組的例子目錄結構如下:
├── main.py └── pac ├── __init__.py
筆者將直接執行main.py檔案,程式碼如下:
## main.py from pac import moduleA ## moduleA.py print __name__ ## output pac.moduleA
這兩種執行方式還有不同的地方是,當使用top level script的方式執行的時候是不會生成位元組碼的(即.pyc檔案),而通過包模組的方式則會生成位元組碼。
Relative Import And Absolute Import
假設如下的import語句:
import string
這個string是當前目錄下的string模組呢,還是在標準庫的string模組呢?在早期的Python中,當使用import語句的時候,都會優先尋找目錄內的模組,因此這就是隱式相對匯入。
但是在有同名模組的情況下,如果還想引用標準庫中的string模組那該怎麼辦?因此Python實現了絕對匯入,在絕對匯入的模式下,當使用import string的時候,就會優先搜尋當前目錄以外的模組。絕對匯入模式是Python3預設採取的包匯入方式,其實這種方式在Python2.5及以上版本就已經實現,要想使用只需加上:
from __future__ import absolute_import
關於隱式相對匯入於絕對匯入的例子如下:
包結構:
├── main.py
└── pac
├── __init__.py
├── __init__.pyc
├── explicit_import.py
├── explicit_import.pyc
├── implicit_import.py
├── implicit_import.pyc
├── string.py
└── string.pyc
程式碼如下:
# main.py
from pac import implicit_import
from pac import explicit_import
# explicit_import.py
from __future__ import absolute_import
import string
print string.digits
# implicit_import.py
import string
print string.digits
# string.py
digits = '2333'
## output
2333(relative import)
0123456789(absolute import)
絕對匯入還有一種使用方法,比如在explicit_import.py中可以通過:
from pac.implicit_import import *
來引用implicit_import檔案中的變數。
explicit relative import
雖然絕對匯入能夠完成相對匯入的所有功能,但是顯式的相對匯入也是可以接受的。當使用.語法的時候就是使用相對匯入:
# 匯入當前目錄下的string模組
# right
from . import string
# wrong
import .string
至於下面的匯入方法錯誤的原因,這是因為Python語法不支援的緣故。
同時值得注意的是,顯式的相對匯入是根據模組的__name__屬性來確定相對位置的,因此如果是在top level script中,顯式相對匯入並不能使用,會報出如下錯誤:
ValueError: Attempted relative import in non-package
當然,在PEP 366 – Main module explicit relative imports中,也給出了在Python中執行非包內的模組(作為top level指令碼執行)使用顯示相對匯入的方法:在執行python命令時加上-m選項,此時就會啟用模組的__package__屬性。