Python Tutorial(六):模块
如果你從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鏡像庫)的作者不用再擔心彼此的模塊名稱。
假定你想設計一些模塊來統一的處理聲音文件和聲音數據。有許多不同格式的聲音文件,所以或許你需要創建和維護一個持續增長的模塊集合在多種不同格式的文件之間進行轉換?;蛟S也想在聲音數據上執行多種不同的操作,所以除此之外你將寫一個永遠沒有頭的模塊流來執行這些操作。這是一個可能的包結構:
當引入這個包,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
總結
以上是生活随笔為你收集整理的Python Tutorial(六):模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 物体检测的三种网络模型
- 下一篇: 吴恩达机器学习笔记二之多变量线性回归