文章目錄
- 6.1.1 把模組作為指令碼執行
- 6.1.2 模組搜尋路徑
- 6.1.3 “編譯”Python檔案
- 6.4.1 從一個包裡匯入*
- 6.4.2 內建包的引用
- 多個目錄裡的包
如果你從Python解譯器中退出,並且再次進入,你會發現你以前定義的函數和變數都已經丟失了。所以,如果你想寫一個在某種程度上更長的程式,使用一個文字編輯器來準備解譯器的輸入會使情況有所好轉,並且使用檔案代替輸入來執行它。這就是被熟知的建立一個指令碼。隨著你的程式變的更長時,你或許想把它分割成幾個檔案,這樣便於維護。你或許想在幾個程式裡面使用一個常用的函數,而不用把它的定義拷貝到每一個程式裡面。
為了支援這些,Python有一個方式來把定義放到一個檔案裡,並且在一個指令碼裡或解譯器的一個互動執行個體中使用它們。這樣的一個檔案叫做模組,一個模組裡面的定義可以被匯入到其它模組裡面或主模組裡面(在頂級以計算機方式執行的指令碼裡面訪問的變數集合)。
一個模組就是一個包含Python定義和語句的檔案。檔案名稱就是模組名加上尾碼.py。在一個模組裡面,模組的名字(一個字串)可以使用全域變數__name__獲得。例如,使用你喜愛的文字編輯器在當前的目錄裡面來建立一個fibo.py的檔案,包含一些內容:
然後進入Python解譯器,使用下面的命令匯入這個模組:
這樣做並沒有把直接定義在fibo中的函數名稱寫入到當前符號表裡,只是把模組fibo的名字寫到了那裡。
可以使用模組名稱來訪問函數:
如果你打算經常使用一個函數,你可以把它賦給一個本地的名稱:
6.1 更多有關模組
模組可以包含可執行語句和函數定義。這些語句是打算用來初始化模組的。這些語句只有在模組第一次被匯入到其它地方的時候會被執行。
每一個模組有它自己的私人符號表,它被定義在模組裡的所有函數用作全域符號表。因此,一個模組的設計者可以在模組裡使用全域變數而不用擔心會和使用者的全域變數產出意外的衝突。換句話來說,如果你知道你正在幹什麼,你可以觸及模組裡面的全域變數,使用和以前引用模組函數相同的標記法,modname.itemname。
模組可以引入其它模組。把所有的匯入語句放到模組的開始是一種習慣,但不要求非得這樣做。被匯入模組的名稱被放入到引入模組的全域符號表裡。
有一個匯入語句的變體可以把一個模組裡的名稱直接匯入到另一個模組的符號表裡面。例如:
這樣不會引入模組的名稱,這些匯入的內容被放到本地符號表(所以在樣本中,fibo沒有定義)。
甚至由一個變體可以引入一個模組定義的所有名稱:
這將引入所有除了以底線開頭的名稱。大多數情況,Python程式員不使用這樣的功能,因為它引入了一個不知道的名稱集合到解譯器裡,可能會隱藏掉你已經定義的一些東西。
注意,一般來說,從一個模組或包裡面引入所有的名稱在實踐中是不被贊成的,因為它經常造成可讀性差的代碼。然而,在互動式會話裡可以使用它來節省輸入。
注意,出於效率的原因,在一個會話裡,每個模組只會被引入一次。如果你改變了你的模組,你必須重新啟動解譯器,或者如果你想互動測試的僅僅是一個模組,可以使用imp.reload(),例如:import imp; imp.reload(modulename)。
6.1.1 把模組作為指令碼執行
當你以下面方式執行Python模組時:
模組裡面的代碼將會被執行,就好像你引入它一樣,但是會把__name__設定為__main__。那就意味著把這些代碼添加到模組的尾部:
你可以把檔案當作指令碼和可匯入模組來使用,因為解析命令列的代碼只有在模組作為主檔案執行時才運行:
如果模組被匯入,代碼不執行:
這通常用來要麼給模組提供一個方便的使用者介面,或為了測試(把模組作為指令碼運行,來執行一個測試單元)。
6.1.2 模組搜尋路徑
當一個名為spam的模組被匯入時,解譯器首先搜尋具有這個名稱的內建模組。如果沒有找到,然後在sys.path這個變數指定的目錄列表裡面搜尋一個名為spam.py的檔案。sys.path從這些地方被初始化:
- 包含輸入指令碼的目錄(或目前的目錄)。
- PYTHONPATH(一個目錄名稱的列表,和shell變數PATH的文法一樣)。
- 取決於安裝時的預設值。
在初始化後,Python程式可以修改sys.path。包含正在啟動並執行指令碼的目錄被放到了搜尋路徑的開始處,在標準庫路徑前面。這意味著那個目錄裡面的指令碼會被載入而不是庫目錄裡面的同名模組。這是一個錯誤除非替換被打算。
6.1.3 “編譯”Python檔案
作為一個對那些使用了許多標準模組的短的程式的啟動時間的重要加速,如果在發現spam.py的目錄裡面已經存在一個叫spam.pyc的檔案,這被假定包含模組spam的一個已按位元組編譯的版本。用來建立spam.pyc的spam.py的版本修改時間被記錄在spam.pyc裡面,並且.pyc檔案會被忽略如果這兩個時間不匹配。
通常,你不需要做任何事情來建立spam.pyc檔案。無論什麼時候spam.py檔案被成功的編譯,將會進行一次把編譯好的版本寫入到spam.pyc的嘗試。如果這次嘗試失敗的話也不算是錯誤;如果由於任何原因這個檔案沒有被寫完,結果spam.pyc檔案被認為是非法的,並且在以後忽略它。spam.pyc檔案的內容是平台獨立的,所以一個Python模組目錄可以被不同架構的機器共用。
一些對專家的提示:
- 當Python的解譯器以-O的標誌被調用時,將產生最佳化的代碼並儲存在.pyo檔案裡面。最佳化器現在協助不了太多;它僅僅移除assert語句。當-O使用時,所有的位元組碼都被最佳化;.pyc檔案被忽略和.py檔案被編譯成最佳化的位元組碼。
- 傳遞兩個-O標誌到Python的解譯器(-OO)將使位元組碼編譯器來執行最佳化,這種最佳化在一些特殊的情況下會導致程式出故障。當前只有文檔字串從位元組碼中被移除,產生更加壓縮的.pyo檔案。因為一些程式或許依賴使這些可用,你應該只有在你知道自己在做什麼時再使用這個選項。
- 一個程式從.pyc或.pyo檔案讀入並不比從.py檔案讀入啟動並執行快很多;關於.pyc或.pyo檔案惟一快的事情是它們被載入的速度。
- 在命令列使用指令碼名稱運行指令碼時,指令碼的位元組碼從不寫入.pyc或.pyo檔案。因此,把一個指令碼的大多數代碼移到一個模組裡面,產生一個較小的啟動指令碼並引入那個模組,可以減少一個指令碼的啟動時間。也可以在命令列直接命名一個.pyc或.pyo檔案。
- 在同一個模組裡面有一個spam.pyc檔案(或spam.pyo當-O使用時)而沒有一個spam.py檔案也是可能的。這可以用於以對逆向工程師有一定難度的形式來發布Python程式碼程式庫。
- compileall模組能為一個目錄裡面的所有模組建立.pyc檔案(或.pyo檔案當-O使用時)。
6.2 標準模組
Python有一個標準模組的庫,在一個單獨的文檔中描述,Python庫參考。一些模組被內建到解譯器中;它們提供的訪問操作不是語言的核心部分但是仍然被內建其中,要麼是為了效率或提供訪問作業系統原始的內容如系統調用。這些模組集是一個配置選項,它們也取決於底層的平台。例如,winreg模組只有Windows系統提供。一個特殊的模組應受到一些關註:sys,它被內建到每一個Python解譯器中。變數sys.ps1和sys.ps2定義了用作主要和第二命令提示字元的字串:
這兩個變數只定義在解譯器處於互動模式時。
變數sys.path是一個字串列表,決定瞭解釋器的模組搜尋路徑。它使用環境變數PYTHONPATH的值進行初始化為一個預設的路徑,或從一個內建預設值如果PYTHONPATH沒有設定。你可以使用標準的列表操作修改它:
6.3 dir()函數
內建的函數dir()用來找出一個模組都定義了那些名稱。它返回一個已排序的字串列表:
沒有參數的話,dir()列出當前你已經定義的名稱:
注意,它列出所有類型的名稱:變數,模組,函數等。
dir()並不列出內建的函數和變數的名稱。如果你想列出那些名稱,它們都定義在標準的模組buildins裡面:
6.4 包
包是使用點模組名稱來構建Python模組命名空間的一種方式。例如,模組名稱A.B表明一個名稱為B的子模組在一個名稱為A的包裡面。就像模組的使用使不同模組的作者不用再擔心彼此的全域變數名稱,點模組名稱的使用使多模組包(像NumPy或Python鏡像庫)的作者不用再擔心彼此的模組名稱。
假定你想設計一些模組來統一的處理音效檔和聲音資料。有許多不同格式的音效檔,所以或許你需要建立和維護一個持續增長的模組集合在多種不同格式的檔案之間進行轉換。或許也想在聲音資料上執行多種不同的操作,所以除此之外你將寫一個永遠沒有頭的模組流來執行這些操作。這是一個可能的包結構:
當引入這個包,Python通過sys.path上的目錄來搜尋尋找包子目錄。
__init__.py檔案是必須的,它使Python把這些目錄作為包含包來對待;這樣就阻止了稍後發生在模組搜尋路徑上的一個具有普通名稱的目錄無意中隱藏了合法模組。在最簡單的情況下,__init__.py可能僅是一個空的檔案,但是它也能為包執行初始化代碼或設定__all__變數,稍後描述。
包的使用者可以從包裡面單個的匯入模組,例如:
這載入子模組sound.effects.echo。必須使用全名來引用它:
一個匯入子模組的可選方式是:
這也載入子模組echo,並且使它不帶包首碼也可以使用,所以它能按如下方式使用:
另一種變體是用來直接匯入期望的函數或變數:
載入子模組echo,並且使它的函數echofilter()直接可以使用:
注意,當使用from package import item時,item要麼是包的子模組(或子包),或者是包裡定義的一些其它名稱,像函數,類或者變數。import語句首先測試item是否定義在包裡;如果沒有,就假定它是一個模組並且嘗試去載入它。如果沒有成功的找到它,一個ImportError異常被激發。
反之,當使用像import item.subitem.subsubitem這樣的文法時,除了最後一項的其它項都必須是一個包;最後一項可以是一個模組或一個包,但不能是定義在前一項裡面的一個類或函數或變數。
6.4.1 從一個包裡匯入*
當使用者寫下from sound.effects import *是會發生什嗎?理論上講,一個人希望這以某種方式走出檔案系統,找出哪些子模出現在塊包裡存,並且全部匯入它們。這可能花費較長的時間,並且正在匯入的子模組可能有不希望的副作用,這個副作用應該只有在這個子模組被顯式匯入時才發生。
唯一的解決方案就是包的作者提供一個顯示的包的索引。import語句使用下面的約定:如果一個包的__init__.py代碼定義了一個名為__all__的列表,它被認為是應該匯入的模組名稱的列表當遇到from package import *時。這取決於包的作者來保持這個列表是最新的當一個包的新的版本被發布時。包的作者們也可以決定不支援它,如果他們沒有看到import * from他們的包的使用。例如,檔案sounds/effects/__init__.py可能包含下面的代碼:
這將意味著from sound.effects import *將匯入sound包的三個命名的子模組。
如果__all__沒有定義,語句from sound.effects import *並不從sound.effects包裡匯入所有的子模組到當前的命名空間裡;它僅僅確認包sound.effects已經被匯入(可能的運行__init__.py裡面的任何初始化代碼)並且匯入包裡定義的任何名稱。這包含通過__init__.py定義的任何名稱(和明確式載入的子模組)。這也包括通過上一個import語句被明確式載入的包的任何子模組。考慮下面的代碼:
在這個例子裡,當from...import語句被執行時,模組echo和surround被匯入到當前的命名空間,因為它們定義在sound.effects包裡。(當__all__被定義時這也起作用。)
當使用import *時,雖然遵從確定的模式,確定的模組被設計為只輸出名稱,在生產代碼裡它仍然被認為是壞的實踐。
記住,使用from Package import specific_submodule沒有錯誤。事實上,這是建議的寫法,除非正在匯入的模組需要使用來自不同包的具有相同名稱的子模組。
6.4.2 內建包的引用
當包被結構化到子包裡(就像例子中的sound包),你可以使用絕對的imports來引用兄弟包的模組。例如,如果模組sound.filters.vocoder需要使用包sound.effects裡面的echo模組,可以使用from sound.effects import echo。
你也可以寫相對的imports,使用import語句的from module import name形式。這些imports使用前置點(句點兒)來指示在相對import裡麵包含的當前的和父親的包。從surround模組,你可以這樣使用:
注意,相對imports是基於當前模組的名稱。因為主模組的名稱總是"__main__",打算用作Python應用程式的主模組的那些模組必須總是使用絕對imports。
多個目錄裡的包
包多支援一個特別的屬性,__path__。它被初始化為一個包含包的__init__.py檔案的目錄的名稱的列表在那個檔案裡的代碼被執行之前。這個變數可以被改變,這樣做影響將來對包裡的模組和子包的搜尋。
當然,這個特性也不常用,它可以被用於擴充一個包裡的模組集合。
本文是對官方網站內容的翻譯,原文地址:http://docs.python.org/3/tutorial/modules.html