與單獨的模塊類似,模塊包對應的是一個目錄(而模塊是對應的一個.py文件)。而包導入就是把計算機上的某個目錄當成爲一個命名空間,該目錄下的模塊名都是該命名空間中的屬性。
包導入基礎
在import語句中,以“.”號分割的目錄作爲導入模塊。
import dir1.dir2.dir3.mod
在from語句中也是一樣:
from dir1.dir2.dir3 import mod
這些命令中的“點號”,都是對應於機器上目錄層次的路徑。你可以通過dir1/dir2/dir3來查找該路徑下有一個文件mod.py。
當然這個路徑是基於之前介紹的搜索路徑的。也就是說dir1目錄是在Python程序的搜索路徑下,否則會無法找到該模塊。
__init__.py包文件
包導入的路徑中的每個目錄內都必須有__init__.py這個文件。
具體來說在這樣的目錄 dir0/dir1/dir2/dir3/mod.py下,在Python中執行import語句: import dir1.dir2.dir3.mod 。那麼在dir1,dir2,dir3目錄下都要有__init__.py文件。
總結規則如下:
- 出現在import語句之中的目錄下,都應該有__init__.py文件
- dir0目錄下可以沒有這個文件,如果有也會被忽略。
- dir0目錄必須在Python的“搜索路徑”下
__init__.py可以包含Python程序代碼,就像Python普通文件一樣。它更像是Python包模塊的聲明,也可以完全是空的,可以用來明確導入的目錄,而不至於混亂。更多情況下__init__.py文件扮演模塊包初始化鉤子、替目錄產生模塊命名空間以及使用目錄導入時實現from*行爲的角色
- 包的初始化
在Python首次導入某個目錄或者包模塊時,就會執行該目錄下的__init__.py文件(import語句中出現的目錄下的init文件都會按順序被執行),可以用該文件來初始化文件,連接數據庫等一些列初始化操作。 - 模塊命名空間的初始化
包模塊導入後,也會產生命名空間,而不是隻是被導入的模塊會有命名空間,包模塊中的變量名就可以通過init文件來賦值。這類文件提供了包模塊需要的命名空間。 - from… import *語句的行爲
這是一個高級功能,你可以在__init__文件中定義__all__列表來定義當使用from * 語句來導入模塊時,需要導出什麼。from * 語句默認情況下,不會導入改包模塊下的子模塊,只會加載init文件定義的變量名,包括該文件明確聲明瞭需要導入的模塊。
實例
$ tree dir2
dir2
├── dir3
│ ├── __init__.py
│ └── mod.py
└── __init__.py
1 directory, 3 files
上面是目錄結構,在dir2目錄下的init文件中定義了一個dir2變量,而在dir3文件中定義了dir3變量:
$ cat dir2/__init__.py
dir2=222
print("dir2 init")
$ cat dir2/dir3/__init__.py
dir3=333
print("dir3 init")
$ cat dir2/dir3/mod.py
x=444
print("hello package")
>>> import dir2.dir3.mod
dir2 init #執行了dir2目錄下的init文件
dir3 init #同理
hello package
>>>
導入包模塊時,爲了使用變量的方便都會使用from語句,因爲使用import語句時,要調用包內變量你必須得要聲明模塊包(命名空間)。另外,如果需要當如兩個目錄下的同名程序時,只能使用import語句,以引入模塊包來區分他們的命名空間。
>>> dir2.dir3.dir3
333
>>> from dir2 import dir2
>>> dir2 #可以不用聲明命名空間直接調用
222
>>> from dir2.dir3 import dir3
>>> dir3
333
>>>
當實際需要包導入的場合,就是解決有多個同名程序文件安裝到同一個機器上時,所引發的模糊性。
相對導入
如果頂層的運行文件在一個模塊包的內部,這時就可以相對導入包內的相關模塊。這樣可以避免硬編碼帶來的移植問題。相對導入只適用於我們本章中已經學習過的包目錄中的文件中的導入。
在Python2.6中,包模塊中的文件執行導入時仍然是先從相對再絕對的搜索路徑順序,而在python3中是默認先到搜索路徑中搜索模塊(絕對導入),再到相對的包目錄下搜索模塊(相對導入),如果想要python2版本與3版本保持一致,就使用: from __future__ import absolute_import
$ tree dir2
dir2
├── __init__.py
├── main.py
└── string.py
1 directory, 5 files
$ cat dir2/main.py
import string
$ cat dir2/string.py
print("hahaha")
$ python
Python 2.7.12 (default, Nov 20 2017, 18:23:56)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dir2.main
dir2 init
hahaha
>>> dir2.main.string #執行的是相對導入,導入的是同目錄下的string
<module 'dir2.string' from 'dir2/string.pyc'>
>>>
$ cat dir2/main.py
from __future__ import absolute_import #修改main程序,加上該語句
import string
$ python
Python 2.7.12 (default, Nov 20 2017, 18:23:56)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dir2.main
dir2 init
>>> dir2.main.string #此時是絕對導入,導入的是標準庫中的模塊。
<module 'string' from '/usr/lib/python2.7/string.pyc'>
>>>
$ cat dir2/import_relativity.py
from . import string #這時執行相對導入
$ python
Python 2.7.12 (default, Nov 20 2017, 18:23:56)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dir2.import_relativity
dir2 init
hahaha
>>> dir2.import_relativity.string #相對導入,表示當前目錄下的模塊。
<module 'dir2.string' from 'dir2/string.pyc'>
相對導入需要注意的幾點:
- 相對導入只適用於包內導入。也就是隻適用於位於包內的文件中使用import語句。如果不是作用於一個包內的一個模塊文件中,相對導入將會不起作用(會報錯)。在Python3版本中,如果沒有明確的在模塊包中用from執行相對導入,那麼會跳過包路徑進行查找,同理相對導入只會在規定的包目錄中查找模塊,不會進行絕對導入。
- 相對導入只是適用於from語句。
- 術語有些含糊,相對與絕對可能會讓人感到迷惑,絕對導入就是從搜索路徑中開始查找模塊,而相對導入時從同目錄同模塊包下查找模塊。