python 包詳解
包
包是一種管理 Python 模塊命名空間的形式,采用"點模塊名稱"。
比如一個模塊的名稱是 A.B, 那麽他表示一個包 A中的子模塊 B 。
就好像使用模塊的時候,你不用擔心不同模塊之間的全局變量相互影響一樣,采用點模塊名稱這種形式也不用擔心不同庫之間的模塊重名的情況。
這樣不同的作者都可以提供 NumPy 模塊,或者是 Python 圖形庫。
不妨假設你想設計一套統一處理聲音文件和數據的模塊(或者稱之為一個"包")。
現存很多種不同的音頻文件格式(基本上都是通過後綴名區分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一組不斷增加的模塊,用來在不同的格式之間轉換。
並且針對這些音頻數據,還有很多不同的操作(比如混音,添加回聲,增加均衡器功能,創建人造立體聲效果),所以你還需要一組怎麽也寫不完的模塊來處理這些操作。
這裏給出了一種可能的包結構(在分層的文件系統中):
sound/ 頂層包
__init__.py 初始化 sound 包
formats/ 文件格式轉換子包
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ 聲音效果子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ filters 子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
在導入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。
目錄只有包含一個叫做 __init__.py 的文件才會被認作是一個包,主要是為了避免一些濫俗的名字(比如叫做 string)不小心的影響搜索路徑中的有效模塊。
最簡單的情況,放一個空的 :file:__init__.py就可以了。當然這個文件中也可以包含一些初始化代碼或者為(將在後面介紹的) __all__變量賦值。
用戶可以每次只導入一個包裏面的特定模塊,比如:
import sound.effects.echo
這將會導入子模塊:sound.effects.echo。 他必須使用全名去訪問:
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
還有一種導入子模塊的方法是:
from sound.effects import echo
這同樣會導入子模塊: echo,並且他不需要那些冗長的前綴,所以他可以這樣使用:
echo.echofilter(input, output, delay=0.7, atten=4)
還有一種變化就是直接導入一個函數或者變量:
from sound.effects.echo import echofilter
同樣的,這種方法會導入子模塊: echo,並且可以直接使用他的 echofilter() 函數:
echofilter(input, output, delay=0.7, atten=4)
註意當使用from package import item這種形式的時候,對應的item既可以是包裏面的子模塊(子包),或者包裏面定義的其他名稱,比如函數,類或者變量。
import語法會首先把item當作一個包定義的名稱,如果沒找到,再試圖按照一個模塊去導入。如果還沒找到,恭喜,一個:exc:ImportError 異常被拋出了。
反之,如果使用形如import item.subitem.subsubitem這種導入形式,除了最後一項,都必須是包,而最後一項則可以是模塊或者是包,但是不可以是類,函數或者變量的名字。
從一個包中導入*
設想一下,如果我們使用 from sound.effects import *會發生什麽?
Python 會進入文件系統,找到這個包裏面所有的子模塊,一個一個的把它們都導入進來。
但是很不幸,這個方法在 Windows平臺上工作的就不是非常好,因為Windows是一個大小寫不區分的系統。
在這類平臺上,沒有人敢擔保一個叫做 ECHO.py 的文件導入為模塊 echo 還是 Echo 甚至 ECHO。
(例如,Windows 95就很討厭的把每一個文件的首字母大寫顯示)而且 DOS 的 8+3 命名規則對長模塊名稱的處理會把問題搞得更糾結。
為了解決這個問題,只能煩勞包作者提供一個精確的包的索引了。
導入語句遵循如下規則:如果包定義文件 __init__.py 存在一個叫做 __all__ 的列表變量,那麽在使用 from package import * 的時候就把這個列表中的所有名字作為包內容導入。
作為包的作者,可別忘了在更新包之後保證 __all__ 也更新了啊。你說我就不這麽做,我就不使用導入*這種用法,好吧,沒問題,誰讓你是老板呢。這裏有一個例子,在:file:sounds/effects/__init__.py中包含如下代碼:
__all__ = ["echo", "surround", "reverse"]
這表示當你使用from sound.effects import *這種用法時,你只會導入包裏面這三個子模塊。
如果 __all__ 真的沒有定義,那麽使用from sound.effects import *這種語法的時候,就不會導入包 sound.effects 裏的任何子模塊。他只是把包sound.effects和它裏面定義的所有內容導入進來(可能運行__init__.py裏定義的初始化代碼)。
這會把 __init__.py 裏面定義的所有名字導入進來。並且他不會破壞掉我們在這句話之前導入的所有明確指定的模塊。看下這部分代碼:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *
這個例子中,在執行from...import前,包sound.effects中的echo和surround模塊都被導入到當前的命名空間中了。(當然如果定義了__all__就更沒問題了)
通常我們並不主張使用*這種方法來導入模塊,因為這種方法經常會導致代碼的可讀性降低。不過這樣倒的確是可以省去不少敲鍵的功夫,而且一些模塊都設計成了只能通過特定的方法導入。
記住,使用from Package import specific_submodule這種方法永遠不會有錯。事實上,這也是推薦的方法。除非是你要導入的子模塊有可能和其他包的子模塊重名。
如果在結構中包是一個子包(比如這個例子中對於包sound來說),而你又想導入兄弟包(同級別的包)你就得使用導入絕對的路徑來導入。比如,如果模塊sound.filters.vocoder 要使用包sound.effects中的模塊echo,你就要寫成 from sound.effects import echo。
from . import echo
from .. import formats
from ..filters import equalizer
無論是隱式的還是顯式的相對導入都是從當前模塊開始的。主模塊的名字永遠是"__main__",一個Python應用程序的主模塊,應當總是使用絕對路徑引用。
包還提供一個額外的屬性__path__。這是一個目錄列表,裏面每一個包含的目錄都有為這個包服務的__init__.py,你得在其他__init__.py被執行前定義哦。可以修改這個變量,用來影響包含在包裏面的模塊和子包。
這個功能並不常用,一般用來擴展包裏面的模塊。
python 包詳解