Python學習入門(19)—模塊(三)

(本號正在連續推出以Python官網文檔為主線的系統學習Python的系列文章或視頻,感興趣的朋友們歡迎搜索關注。在這裡學習Python事半功倍!本文及後續文章如無特別聲明均以Windows平臺作為演示平臺,Python版本為:3.8.1)


【本篇繼上篇繼續講解"模塊"部分的內容】

試想在實際編程過程中,如果只是簡單地在某些目錄直接創建模塊,則隨著程序功能的增加創建的模塊會越來越多,勢必會增加模塊命名及管理的複雜性。再加之對第三方模塊的引用,必然會使情況越來越糟,而且很難避免使用的第三方模塊名與自己創建的模塊名不發生衝突。實際上Python使用包來管理模塊的,上述情況在Python中是可以避免的。Python中的包是以"點分割的模塊名稱"的形式來管理模塊名稱空間。例如,模塊名A.B指定子模塊B在名為A的包內。就像使用模塊可以讓不同模塊的作者不必擔心彼此的全局變量名稱的衝突問題一樣,點分割的模塊名稱的使用使得像NumPy或Pillow的這樣多模塊包的作者不必擔心彼此模塊名稱間的衝突問題。

包的創建

在Python中包是通過文件目錄實現的:各級包名對應於文件系統的各級目錄名。假設我們希望設計一組模塊(包)來統一處理聲音文件和聲音數據。由於許多不同的聲音文件格式(通常由它們的擴展名來識別,例如:.wav、.aiff、.au),我們除了可能需要創建和維護不斷增加的用於在各種文件格式之間進行轉換的模塊集合外,還可能會希望對聲音數據執行許多不同的操作(例如混合、添加echo、應用均衡器函數、創建人工立體聲效果)。因此需要不斷地編寫增加的模塊來執行這些操作。下面就是這個包的一個可能的結構(用一個分層的文件系統來表示):

<code>sound/
__init__.py #頂層包
formats/ #初始化頂層包
__init__.py #用於文件格式轉換的子包
wavread.py
wavwrite.py
aiffwrite.py
auread.py
auwrite.py
...
effects/ #用於音效的子包
__init__.py
echo.py
surround.py
reverse.py
...
filters/ #用於過濾器的子包
__init__.py
equalizer.py
vocoder.py
karaoke.py
... /<code>

【示例】包的結構

】Python將包含有__init__.py文件的目錄視為包。這可以防止某個目錄無意中隱藏稍後在模塊搜索路徑上出現的同名模塊。在最簡單的情況下,__init__ .py可以只是一個空文件,但是它也可以包含用於初始化包的代碼,或者設置稍後要講到的__all__變量。

中模塊的導入和使用

用戶可以從包中單獨導入模塊,例如: importsound.effects.echo,這將加載子模塊sound.effects.echo。引用該模塊中定義的函數時必須使用全名(假設echofilter為該模塊中定義的函數):sound.effects.echo.echofilter(input,output,delay=0.7,atten=4)。

也可以這樣導入上述子模塊: fromsound.effectsimportecho,這也會加載子模塊echo,與前者不同的是可以在沒有包前綴的情況下使用此模塊:echo.echofilter(input,output,delay=0.7,atten=4)。

另一種是直接導入所需的函數或變量:fromsound.effects.echoimportechofilter。同樣這也將加載子模塊echo,但可直接使用函數echofilter():echofilter(input,output,delay=0.7,atten=4)

注意:1. 當使用from package import item的形式導入時,item可以是包的子模塊或子包,也可以是包的__init__.py文件中定義的其他名字,比如函數、類或變量。import語句首先測試item是否在包中定義,如果不是,則假定它是一個模塊,並嘗試加載它。如果沒有找到它,就會引發一個ImportError異常。2. 當使用import item.subitem的形式導入時,除最後一項外其他每項必須是一個包。最後一項可以是模塊或包,但不能是在前一項中定義的類、函數或變量。

從包中導入 *

當用戶使用 fromsound.effectsimport*會發生什麼?理想情況下,希望包sound.effects下的子包會被全部導入。如果這樣的話,可能會花費很長時間,而且導入子模塊可能會產生副作用,這種副作用只有在顯式導入這個(些)子模塊時才會出現。

對於包的作者來說,解決這些問題的唯一方法是提供包的顯式索引。import語句使用以下約定:如果一個包的__init__ .py代碼定義了名為__all__的列表,則此列表定義了當遇到 from package import * 時應該導入的模塊名稱。當包的新版本發佈時,由包作者負責更新這個列表。包的作者如果認為沒有人會從他們的包中導入*,也可不使用這個方法。文件sound/effects/_ init_ .py可以包含以下代碼:__all__=["echo","surround","reverse"],這樣 fromsound.effectsimport*就只會導入這三個子模塊。

實際上如果沒有定義__all__,from sound.effects import * 也不會從sound.effect包中導入所有子模塊到當前命名空間,它只能確保sound.effects包被導入(可能運行在_init_ .py中定義的任何初始化代碼),然後導入包中定義的所有名字。這些名字包括任何由__init__.py定義的(以及子模塊顯式加載的)名字,還包括前面的import語句顯式加載的包的所有子模塊的名字。考慮這段代碼:

<code>import sound.effects.echo
import sound.effects.surround
from sound.effects import */<code>

在這個例子中,echo和surround模塊被導入到當前空間中,這是由於它們是在sound.effect包中定義的並且執行from……import前已被導入。

包內引用

當包被構造成子包結構時(如示例中的sound包),可以使用絕對導入來引用兄弟包中的模塊。例如模塊sound.filters.vocoder需要使用sound.effects包中的echo模塊,可以使用 fromsound.effectsimportecho

也可以使用形如 from module import name 的相對導入語句來導入。這種導入使用前導點來指示相關導入中涉及的當前包和父包。例如從surround模塊引用其它模塊可以使用:

<code>from . import echo
from .. import formats
from ..filters import equalizer/<code>


注意:相對導入是基於當前模塊名稱的。由於主模塊的名稱總是“__main__”,因此作為Python應用程序的主模塊的模塊必須始終使用絕對導入其他兄弟包中的模塊。

包的__path__屬性

包還支持另一特殊的屬性:__path__。在__init__.py被執行之前,這個變量被初始化為包含當前包的__init__.py所在目錄的列表。這個變量是可以修改的,如果修改了,後續對包中包含的模塊和子包的搜索會受影響。此功能雖然通常不需要,但可以使用它擴展包中的模塊集。

【結束】


分享到:


相關文章: