天天看點

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被執行前定義哦。可以修改這個變量,用來影響包含在包裡面的子產品和子包。

這個功能并不常用,一般用來擴充包裡面的子產品。