Python回顾与整理10:模块
? ? ? ? 模塊是用來組織Python代碼方法的方法,而包則是用來組織模塊的,充分利用好包和模塊將有利于開發(fā)出結(jié)構(gòu)清晰的大型程序。
1.什么是模塊
????????所謂模塊,其實(shí)就是一個(gè)包含了特定功能代碼的.py文件,在這個(gè).py文件中,主要有如下的代碼類型:
-
包含數(shù)據(jù)成員和方法的類
-
一組相關(guān)但彼此獨(dú)立的操作函數(shù)
-
全局變量
????????使用import語句就可以導(dǎo)入一個(gè)模塊中的相關(guān)屬性。
2.模塊和文件
????????模塊是按照邏輯上來組織Python代碼的方法,而體現(xiàn)在物理層面上,它就是一個(gè)文件,因此,一個(gè)文件被看作是一個(gè)獨(dú)立模塊,一個(gè)模塊也可以被看作是一個(gè)文件。模塊的文件名就是模塊的名字加上擴(kuò)展名.py。
????????與其他可以導(dǎo)入類的語言不同,在Python中導(dǎo)入的是模塊或模塊屬性。
(1)模塊名稱空間
????????名稱空間是Python非常重要的一個(gè)概念,所謂名稱空間,其實(shí)指的是一個(gè)名稱到對(duì)象的關(guān)系映射集合??梢砸?yàn)槊總€(gè)模塊都定義了它自己的唯一的名稱空間,所以不同模塊間不會(huì)出現(xiàn)名稱交叉現(xiàn)象,通過句點(diǎn)屬性的訪問方式,即使兩個(gè)模塊里有相同名稱的變量,由于模塊名稱的不同,也不會(huì)發(fā)生名稱沖突。
(2)搜索路徑和路徑搜索
????????模塊的導(dǎo)入(使用import語句)需要一個(gè)叫做“路徑搜索”的過程,即在文件系統(tǒng)“預(yù)定義區(qū)域”中查找要導(dǎo)入的模塊文件,而這些預(yù)定義區(qū)域其實(shí)是Python搜索路徑的集合,這里需要注意下面兩個(gè)概念:
-
路徑搜索:指查找某個(gè)文件的操作,是動(dòng)詞
-
搜索路徑:需要查找的一組目錄,是名詞
????????如果模塊名稱不在搜索路徑中,就會(huì)觸發(fā)ImportError異常:
| 1 2 3 4 | >>>?import?mymodules Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> ImportError:?No?module?named?mymodules |
????????而默認(rèn)搜索路徑是在編譯或安裝時(shí)指定的,可以在兩個(gè)地方修改:
-
設(shè)置環(huán)境變量PYTHONPATH
-
在sys.path中添加搜索路徑
????????啟動(dòng)Python解釋器后,搜索路徑會(huì)被保存在sys模塊的sys.path變量中:
| 1 2 3 | >>>?import?sys >>>?sys.path ['',?'/usr/local/lib/python2.7/dist-packages/pip-8.0.2-py2.7.egg',?'/usr/local/lib/python2.7/dist-packages/setuptools-3.3-py2.7.egg',?'/usr/lib/python2.7',?'/usr/lib/python2.7/plat-x86_64-linux-gnu',?'/usr/lib/python2.7/lib-tk',?'/usr/lib/python2.7/lib-old',?'/usr/lib/python2.7/lib-dynload',?'/usr/local/lib/python2.7/dist-packages',?'/usr/lib/python2.7/dist-packages',?'/usr/lib/python2.7/dist-packages/PILcompat',?'/usr/lib/python2.7/dist-packages/gtk-2.0',?'/usr/lib/python2.7/dist-packages/ubuntu-sso-client'] |
????????返回的是一個(gè)列表,第一個(gè)元素表示的是當(dāng)前目錄??梢酝ㄟ^向這個(gè)列表添加元素(使用append或insert)來增加搜索路徑:
| 1 2 3 4 5 6 | >>>?import?my Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> ImportError:?No?module?named?my >>>?sys.path.append('/home/xpleaf/test') >>>?import?my |
????????如果有多個(gè)相同的模塊名稱,Python解釋器會(huì)使用沿著搜索路徑順序找到的第一個(gè)模塊。
????????另外使用sys.modules可以找到當(dāng)前導(dǎo)入了哪些模塊和它們來自什么地方,如下:
| 1 2 | >>>?sys.modules {'copy_reg':?<module?'copy_reg'?from?'/usr/lib/python2.7/copy_reg.pyc'>,?'sre_compile':?<module?'sre_compile'?from?'/usr/lib/python2.7/sre_compile.pyc'>,...} |
????????可以看到,與sys.path不同,sys.modules返回的是一個(gè)字典,其中key為模塊的名稱,鍵值為模塊的路徑。
3.名稱空間
????????名稱空間是名稱(標(biāo)識(shí)符)到對(duì)象的映射,向名稱空間添加名稱的操作過程涉及綁定標(biāo)識(shí)符到指定對(duì)象的操作,同時(shí)會(huì)給該對(duì)象的引用計(jì)數(shù)加1。主要有以下幾種名稱空間:
-
內(nèi)建名稱空間:由__builtins__模塊中的名字構(gòu)成,Python解釋器啟動(dòng)時(shí)自動(dòng)加載
-
全局名稱空間:加載完內(nèi)建名稱空間后加載
-
局部名稱空間:執(zhí)行一個(gè)函數(shù)時(shí)會(huì)產(chǎn)生該名稱空間,函數(shù)執(zhí)行結(jié)束后就會(huì)釋放
????????其中,關(guān)于__builtins__模塊和__builtin__模塊的區(qū)別,可以查看這篇博文:《Python中__builtin__和__builtins__的深入探討》
????????
(1)名稱空間與變量作用域比較
????????名稱空間是純粹意義上的名字和對(duì)象的映射關(guān)系,而作用域指出了從用戶代碼的哪些物理位置可以訪問到這些名字。其關(guān)系如下:
????????顯然就很清晰了。
(2)名稱查找、確定作用域、覆蓋
????????訪問一個(gè)屬性時(shí),解釋器必須要在三個(gè)名稱空間中的一個(gè)找到它,首先是局部名稱空間,如果沒有找到,則查找全局名稱空間,如果還是查找失敗,就查找內(nèi)建名稱空間,最后還是失敗了,就會(huì)引發(fā)NameError異常。
????????顯然上面的名稱查找過程充分地體現(xiàn)了作用域覆蓋的特性。
(3)無限制的名稱空間
????????可函數(shù)中,可以通過func.attribute給函數(shù)添加屬性屬性,從而創(chuàng)建了另外一個(gè)名稱空間,而在面向?qū)ο缶幊讨?#xff0c;也可以通過類似的方式給一個(gè)實(shí)例,一個(gè)實(shí)例就是一個(gè)名稱空間:
| 1 2 3 4 5 6 7 8 9 10 | >>>?foo?=?test() >>>?foo.name?=?'xpleaf' >>>?foo.loving?=?'cl' >>>? >>>?foo.name 'xpleaf' >>>?foo.loving 'cl' >>>?foo.__dict__ {'name':?'xpleaf',?'loving':?'cl'} |
4.導(dǎo)入模塊
(1)import語句
????????使用import語句可以導(dǎo)入模塊,建議以這樣的順序?qū)肽K:
-
Python標(biāo)準(zhǔn)庫模塊
-
Python第三方模塊
-
應(yīng)用程序自定義模塊
????????解釋器執(zhí)行import語句時(shí),如果在搜索路徑中找到該模塊,就會(huì)進(jìn)行加載,該過程遵循下面的原則:
-
如果在一個(gè)模塊的頂層導(dǎo)入,它的作用域是全局的
-
如果在函數(shù)中導(dǎo)入一個(gè)模塊,它的作用域是局部的
-
如果模塊是被第一次導(dǎo)入,它將加載并執(zhí)行
????????對(duì)于最后一點(diǎn),可以驗(yàn)證如下:
| 1 2 3 | >>>?import?test Hello >>>?import?test |
????????test模塊中只有一個(gè)print語句,可以看到后面再次導(dǎo)入時(shí)并不執(zhí)行該print語句。
(2)from-import語句
????????可以使用from-import語句導(dǎo)入指定的模塊屬性,即把指定名稱導(dǎo)入到當(dāng)前作用域(全局名稱空間或局部名稱空間,取決于導(dǎo)入的位置)中,語法如下:
| 1 | from?module?import?name1[,?name2[,?...nameN]] |
????????當(dāng)然,可以多行導(dǎo)入:
| 1 2 | from?flask?import?render_template,?redirect,?flash,?\ ????url_for,?request,?current_app,?jsonify |
????????也可以使用from-import-as:
| 1 | from?datetime?import?datetime?as?showtime |
5.導(dǎo)入模塊的特性
????????當(dāng)模塊被導(dǎo)入時(shí),會(huì)有如下的特性:
-
載入時(shí)執(zhí)行模塊
????????加載模塊會(huì)導(dǎo)致這個(gè)模塊被“執(zhí)行”,也就是被導(dǎo)入模塊的頂層代碼將直接被執(zhí)行,這通常包括全局變量以及類和函數(shù)的聲明,如果有檢查__name__的操作,那么它也會(huì)被執(zhí)行。
????????當(dāng)然,應(yīng)該把盡可能多的代碼封裝到函數(shù),只把函數(shù)和模塊定義放入模塊的頂層是良好的模塊編程習(xí)慣。
-
導(dǎo)入(import)和加載(load)
????????一個(gè)模塊只被加載(執(zhí)行)一次,無論它被導(dǎo)入多少次。前面已經(jīng)有相應(yīng)的例子。
-
導(dǎo)入到當(dāng)前名稱空間
????????即把模塊的名稱空間導(dǎo)入到當(dāng)前作用域(全局名稱空間或局部名稱空間,取決于導(dǎo)入的位置),這樣的話,如果當(dāng)前作用域擁有相同名稱的變量,就會(huì)被覆蓋。
-
關(guān)于__future__
????????可以通過下面的導(dǎo)入語句使用Python的一些新特性:
| 1 | from?__future__?import?new_feature |
-
警告框架
????????需要使用到時(shí)再查看相關(guān)文檔。
-
從ZIP文件中導(dǎo)入模塊
-
“新的”導(dǎo)入鉤子
6.模塊內(nèi)建函數(shù)
????????如下:
-
__import__()
????????import語句實(shí)際上是調(diào)用了該函數(shù),語法如下:
| 1 | __import__(name,?globals={},?locals={},?fromlist=[],?level=-1) |
? ? ????即可以這樣來導(dǎo)入sys模塊:
| 1 | sys?=?__import__('sys') |
-
globals()和locals()
????????使用globals()和locals()可以分別輸出包含全局名稱空間和局部名稱空間的一個(gè)字典,只是在全局名稱空間下,globals()和locals()的輸出是相等的,因?yàn)檫@時(shí)的局部名稱空間就是全局名稱空間。
????????一個(gè)技巧是,可以通過這兩個(gè)函數(shù)來調(diào)用全局或局部變量,在這開發(fā)中可能會(huì)用到,如下:
| 1 2 3 4 5 6 7 | >>>?def?sayHi(name): ...?????print?'Hello,?',?name ...? >>>?globals()['sayHi'] <function?sayHi?at?0x7f56dc7bf140> >>>?globals()['sayHi']('xpleaf') Hello,??xpleaf |
-
reload()
????????使用reload()可以重新加載一個(gè)已經(jīng)導(dǎo)入的模塊,對(duì)于前面只有print語句的test模塊,測(cè)試如下:
| 1 2 3 4 5 6 | >>>?import?test Hello >>>?import?test >>>?reload(test) Hello <module?'test'?from?'test.pyc'> |
????????不過使用reload()時(shí)需要遵循下面的原則:
模塊必須是全部導(dǎo)入,而不能是使用from-import
reload()的參數(shù)是模塊名字本身,而不是其字符串,即是reload(test)而不是reload('test')
7.包
????????包是一個(gè)有層次的文件目錄結(jié)構(gòu),它定義了一個(gè)由模塊和子包組成的Python應(yīng)用程序執(zhí)行環(huán)境。
(1)目錄結(jié)構(gòu)
????????有如下的包及其結(jié)構(gòu):
????????在包外運(yùn)行Python交互器,則下面的導(dǎo)入方式都是可以的:
| 1 2 3 4 5 6 7 8 9 10 11 | import?Phone.Mobile.Analog Phone.Mobile.Analog.dial() from?Phone?import?Mobile Mobile.Analog.dial('3245648') from?Phone.Mobile?import?Analog Analog.dial() from?Phone.Mobile.Analog?import?dial dial('3245648') |
????????只是上面的導(dǎo)入方式其實(shí)都是絕對(duì)導(dǎo)入。
(2)__init__.py
????????只要是包,都必須包含__init__.py文件,否則只是一個(gè)普通的目錄結(jié)構(gòu),而不是包,關(guān)于__init__.py,有如下特性:
-
導(dǎo)入一個(gè)包,實(shí)際上是導(dǎo)入這個(gè)包的__init__.py文件(模塊)
????????對(duì)于上面的包結(jié)構(gòu),如果Phone/__init__.py的內(nèi)容如下:
| 1 2 | packageName?=?'Phone' print?'Package?<Phone>' |
????????則導(dǎo)入Phone包時(shí)輸出如下:
| 1 2 | >>>?import?Phone Package?<Phone> |
????????此時(shí)只是把Phone加入到當(dāng)前名稱空間中:
| 1 2 | >>>?dir() ['Phone',?'__builtins__',?'__doc__',?'__name__',?'__package__'] |
????????因?yàn)閷?dǎo)入一個(gè)包實(shí)際上是導(dǎo)入這個(gè)包的__init__.py模塊,所以,只要不在這個(gè)模塊中的名稱空間,都無法通過Phone來訪問,即使已經(jīng)導(dǎo)入了Phone:
| 1 2 3 4 5 6 7 8 | >>>?Phone.Mobile Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> AttributeError:?'module'?object?has?no?attribute?'Mobile' >>>?Phone.Pager Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> AttributeError:?'module'?object?has?no?attribute?'Pager' |
????????因?yàn)樯厦娴腳_init__.py模塊中并沒有Mobile和Pager這兩個(gè)變量,即這兩個(gè)變量不在其名稱空間中,因?yàn)開_init__.py中定義了一個(gè)packageName變量,所以可以通過Phone來訪問:
| 1 2 | >>>?Phone.packageName 'Phone' |
-
在__init__.py中可以先導(dǎo)入一些模塊或子包,使得可以通過上層包訪問子包
????????上面的例子中,顯然不能通過Phone來訪問它的子包Mobile,現(xiàn)在可以在Phone/__init__.py中來先導(dǎo)入Phone的幾個(gè)子包:
| 1 2 | import?Mobile import?Pager |
????????就可以通過Phone來訪問了:
| 1 2 3 4 5 | >>>?import?Phone >>>?Phone.Mobile <module?'Phone.Mobile'?from?'Phone/Mobile/__init__.pyc'> >>>?Phone.Pager <module?'Phone.Pager'?from?'Phone/Pager/__init__.pyc'> |
????????當(dāng)然,如果想要訪問子包下的模塊,也是不行的,原因是一樣的:
| 1 2 3 4 | >>>?Phone.Mobile.Digital Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> AttributeError:?'module'?object?has?no?attribute?'Digital' |
-
使用__all__變量來導(dǎo)入所有子包
????????如果需要使用from Package import *的方式來導(dǎo)入包下的所有模塊,則需要定義__all__變量,實(shí)際上,該語句中的*表示的即是__all__列表中所包含的包名(或模塊名)。
????????Phone/__init__.py內(nèi)容如下:
| 1 2 3 | import?Mobile __all__?=?['Fax',?'Pager',?'Voicedta'] |
????????使用from Package import *語句導(dǎo)入:
| 1 2 3 | >>>?from?Phone?import?* >>>?dir() ['Fax',?'Pager',?'Voicedta',?'__builtins__',?'__doc__',?'__name__',?'__package__'] |
????????可以發(fā)現(xiàn)只有Fax,Pager,Voicedta這三個(gè)子包被導(dǎo)入了,但是Mobile這個(gè)子包并沒有被導(dǎo)入,那是因?yàn)樗辉赺_all__變量所定義的列表中。
(3)絕對(duì)導(dǎo)入與相對(duì)導(dǎo)入
????????使用下面的方式,都為絕對(duì)導(dǎo)入:
| 1 2 | from?Package?import?Module import?Package |
????????這時(shí)候,解釋器會(huì)從sys.path定義的路徑中去搜索需要導(dǎo)入的包或模塊。當(dāng)然,使用相對(duì)于當(dāng)前路徑的導(dǎo)入式的話有時(shí)候可以加快導(dǎo)入速度,相對(duì)導(dǎo)入可以使用下面的方式:
| 1 | from?.[Package/Module]?import?Modulce/Variable |
????????不過需要遵循下面的原則:
-
必須是使用from import語句
-
from 關(guān)鍵字后面一定有句點(diǎn)標(biāo)識(shí).
-
只適用在包中
????????這三個(gè)原則都非常重要,尤其是最后一個(gè),如果不清楚該原則的話,會(huì)導(dǎo)致錯(cuò)誤使用相對(duì)導(dǎo)入從而帶來不必要的各種糾結(jié)。
????????關(guān)于這一點(diǎn),可以參考《Python cookbook》的相關(guān)內(nèi)容:《10.3 使用相對(duì)路徑名導(dǎo)入包中子模塊》
8.模塊的其他特性
(1)自動(dòng)載入的模塊
????????主要是__builtin__模塊,它會(huì)正常地被載入,這和__builtins__模塊相同。
(2)阻止屬性導(dǎo)入
????????如果不想讓某個(gè)模塊屬性被"from module import *"導(dǎo)入,那么可以給不想導(dǎo)入的屬性名稱加上一個(gè)下劃線(_)。不過如果導(dǎo)入了整個(gè)模塊,這個(gè)隱藏?cái)?shù)據(jù)的方法就不適用了。
????????manage模塊內(nèi)容:
| 1 2 | name?=?'xpleaf' _loving?=?'cl' |
????????舉例如下:
| 1 2 3 4 5 6 7 8 9 10 11 | >>>?from?manage?import?* >>>?name 'xpleaf' >>>?_loving Traceback?(most?recent?call?last): ??File?"<stdin>",?line?1,?in?<module> NameError:?name?'_loving'?is?not?defined >>>? >>>?import?manage >>>?manage._loving 'cl' |
(3)不區(qū)分大小的導(dǎo)入
????????主要取決于操作系統(tǒng)的文件系統(tǒng)是否區(qū)分大小寫。
(4)源代碼編碼
????????在Python模塊文件中,默認(rèn)是使用ASCII編碼,如果需要使用其他編碼方法,比如Unicode編碼,則可以在一個(gè)Python模塊文件的開頭這樣寫:
| 1 2 | #!/usr/bin/env?python #?-*-?coding:?UTF-8?-*- |
????????或者:
| 1 | #?coding:?utf-8 |
(5)導(dǎo)入循環(huán)
????????允許正確的導(dǎo)入循環(huán),如果出現(xiàn)不能解決的導(dǎo)入循環(huán)問題,可以嘗試在出現(xiàn)導(dǎo)入循環(huán)的兩個(gè)模塊中的一個(gè)模塊中的import語句移到最后,或根據(jù)實(shí)際情況來進(jìn)行解決。
(6)模塊執(zhí)行
????????可以執(zhí)行一個(gè)Python模塊,參考后面的內(nèi)容。
本文轉(zhuǎn)自 xpleaf 51CTO博客,原文鏈接:http://blog.51cto.com/xpleaf/1764586,如需轉(zhuǎn)載請(qǐng)自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的Python回顾与整理10:模块的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一章---对象导论
- 下一篇: shell脚本输出菱形与等边三角形