第13章 模块
13.1 模組就是程式
本節將給大家介紹一個新的知識,叫作模組。一早我們就說過模組是更高級的封裝。說到封裝,先回顧一下學過的有哪些?
容器,例如清單、元組、字串、字典等,這些是對資料的封裝。
函數,是對語句的封裝。
類,是對方法和屬性的封裝,也就是對函數和資料的封裝。
那本節學習的模組,又是怎樣一種封裝形式呢?要解答什麼是模組這個問題,其實只需要用一句話就可以概括:模組就是程式。沒錯,模組,就是平時寫的任何代碼,保存的每一個.py結尾的檔,都是一個獨立的模組。
舉個簡單的例子,在Python的安裝目錄下創建一個叫hello.py的文件,代碼如下:
def hi():
print("Hi everyone, I love FishC.com!")
當我把這個檔保存起來的時候,它就是一個獨立的Python模組了(注意:為了讓預設的IDLE可以找到這個模組,需要把檔放在Python的安裝目錄下)。
這時就可以在IDLE中導入模組了:
好,那試試調用一下hello模組中的hi函數:
噢?出錯了!從這個錯誤資訊可以看出錯誤的根源是Python找不到hi()這個函數。為什麼會這樣呢?明明在hello檔中已經定義了hi()函數,這裡Python卻說我們未定義?
13.2 命名空間
什麼是命名空間呢?命名空間(Namespace)表示識別字(identifier)的可見範圍。一個識別字可在多個命名空間中定義,它在不同命名空間中的含義是互不相干的。
比如你們班裡有個叫小花的同學,隔壁班也恰好有個叫小花的同學,由於她們在兩個不同的班級,所以老師上課點名直接叫小花是沒有問題的。但如果是期末統考,那麼整個年級的成績排名就分不清到底是你班還是隔壁班的小花排第一了。那怎麼辦呢?解決的方法很簡單,就是在名字的前邊寫上相應的班級就可以了。在這個例子中,班級就是命名空間。
在Python中,每個模組都會維護一個獨立的命名空間,我們應該將模組名加上,才能夠正常使用模組中的函數:
13.3 導入模組
下面介紹一下幾種導入模組的方法。
1.import模組名
直接import,但是在調用模組中的函數的時候,需要加上模組的命名空間。重新寫一個例子,用於計算攝氏度和華氏度的相互轉換:
再寫一個檔來導入剛才的模組:
2.from模組名import函數名
剛才那種方法有些讀者可能不是很喜歡,因為這個模組的名字太長了,每次調用模組裡的函數都要寫這麼長的命名空間,真是費力不討好又容易出錯。所以呢,就有了這種方法。
這種導入方法會直接將模組的命名空間覆蓋進來,所以調用的時候也就不需要再加上命名空間了:
# p13_3.py
from p13_1 import c2f, f2c print("32攝氏度 = %.2f華氏度" % c2f(32))
print("99華氏度 = %.2f攝氏度" % f2c(99))
這裡還可以使用萬用字元星號(*)來導入模組中所有的命名空間:
from p13_1
import *
但是強烈要求大家不要使用這種方法,因為這樣做會使得命名空間的優勢蕩然無存,一不小心還會陷入名字混亂的局面。
3.import模組名as新名字
最後一種方法是作者本人大力推崇的,你可以用這種方法給導入的命名空間替換一個新的名字。
# p13_4.py
import p13_1 as tc print("32攝氏度 = %.2f華氏度" % tc.c2f(32))
print("99華氏度 = %.2f攝氏度" % tc.f2c(99))
13.4 __name__='__main__'
前邊已經介紹了模組的作用以及模組的用法。來回顧一下,模組的主要作用有哪些?
第一點無疑就是封裝組織Python的代碼,你想想,當代碼量非常大的時候,可以有組織有紀律地根據不同的功能,將代碼分割成不同的模組。這樣,每個模組相互之間是獨立開的。那大家說說,這代碼是分開了容易閱讀和測試,還是撂在一塊容易?我們肯定是更願意去閱讀和測試一小段代碼,而不是每一次都劈頭蓋臉地將一個程式從頭讀起。
然後,模組的另一個重要的特性就是實現代碼的重用。比如你寫了一段發送郵件的代碼,多次優化之後發現這非常棒,你就可以封裝成一個獨立的模組,以後在任何程式需要發送郵件的時候,只需要導入這個模組就可以直接使用了,而不用在每個需要發送郵件的程式中都重複寫同樣的代碼。
相信很多讀者朋友已經開始去閱讀別人的代碼(注:通常通過閱讀比你牛的人寫的代碼,會讓你的技術水準飛速提高),在閱讀代碼時,會發現很多代碼中都有if__name__=='__main__'這麼一行語句,但卻不知道有什麼用?
先舉個例子,一般寫完代碼要先測試下:
單獨這個運行是沒問題的:
但如果是在另一個檔中(p13_6.py)導入後再調用:
# p13_6.py
import p13_5 as tc print("32攝氏度 = %.2f華氏度" % tc.c2f(32))
print("99華氏度 = %.2f攝氏度" % tc.f2c(99))
就會出現問題:
Python把模組中(p13_5.py)的測試函數也一塊兒執行了,而這並不是我們想要的……避免這種情況的關鍵在於:讓Python知道該模組是作為程式運行還是導入到其他程式中。為了實現這一點,需要使用模組的__name__屬性:
在作為程式運行的時候,__name__屬性的值是'__main__',而作為模組導入的時候,這個值就是該模組的名字了。因此,你就不難理解if__name__=='__main__'這句代碼的意思了。
上面的代碼確保只有單獨運行p13_7.py時才會執行test()函數。
13.5 搜索路徑
現在遇到一個問題,寫好的模組應該放在哪裡?有讀者可能會說:“不是應該放在和導入這個模組檔的原始程式碼同一個資料夾內嗎?”沒錯,這是一種方案。但有的讀者可能不希望把所有的代碼都放在一個框裡,因為我想通過資料夾的方式更好地組織我的代碼。可以做到嗎?沒問題,但在此之前你必須先理解搜索路徑這個概念。
Python模組的導入需要一個路徑搜索的過程。就是說,你導入一個叫作hello的模組,那麼Python會在預定義好的搜索路徑中尋找一個叫作hello.py的模組檔——如果有,則導入模組;如果沒有,則導入失敗。而這個搜索路徑,就是一組目錄,可以通過sys模組中的path變數顯示出來(不同的機器上顯示的路徑資訊可能不一樣):
列出的這些路徑都是Python在導入模組操作時會去搜索的,儘管這些模組都可以使用,但site-packages目錄是最佳的選擇,因為它就是用來做這些事情的。
當然按照這個邏輯來說,只需要告訴Python你的模組檔在哪裡找,Python在導入模組的時候就能正確地找到它:
13.6 包
在實際的開發中,一個大型的系統有成千上萬的Python模組是很正常的事情。單單用模組來定義Python的功能顯然還不夠,如果都放在一起顯然不好管理並且有命名衝突的可能,因此Python中也出現了包的概念。
什麼是包呢?事實上有點像剛剛所做的:把模組分門別類地存放在不同的資料夾,然後把各個資料夾的位置告訴Python。只是包的實現要更為簡潔一些。創建一個包的具體操作如下:
(1)創建一個資料夾,用於存放相關的模組,資料夾的名字即包的名字;
(2)在資料夾中創建一個__init__.py的模組檔,內容可以為空;
(3)將相關的模組放入資料夾中。
注意
注意第(2)步,必須要在每一個包目錄下建立一個__init__.py的模組,可以是一個空檔,也可以寫一些初始化代碼。這個是Python的規定,用來告訴Python將該目錄當成一個包來處理。
接下來就是在程式中導入包的模組(包名.模組名):
# p13_8.py # 將p13_7.py放在了資料夾M
1中 import M1.p13_7 as tc
print("32攝氏度 = %.2f華氏度" % tc.c2f(32))
print("99華氏度 = %.2f攝氏度" % tc.f2c(99))
看,程式正常執行:
13.7 像個極客一樣去思考
Python社區有句俗語叫“Python自己帶著電池”。什麼意思呢?這要從Python的設計哲學說起……
Python的設計哲學是“優雅、明確、簡單”,因此,Python開發者的哲學是“用一種方法,最好是只有一種方法來做一件事”。雖然作者常常鼓勵大家多思考,條條大路通羅馬,那是為了訓練大家的發散性思維。但在正式程式設計中,如果有完善的並且經過嚴密測試過的模組可以實現,那麼建議大家最好使用現成的模組。
隨Python附帶安裝有Python標準庫,說“Python自己帶著電池”,指的就是標準庫裡的模組。這些模組都極其有用,一般常見的任務都有相應的模組可以實現。不過Python標準庫裡包含的模組有數百個之多,一個個模組單獨來講,那著實不現實。所以本節主要是將告訴大家如何獨立地探究模組。
對於Python來說,學習資料其實一直都在身邊。這裡給大家分析下遇到問題,自己應該如何去找答案(其實90%的問題都可以自己找到解決方法)。首先要找的就是Python的文檔,選擇Help→Python Docs選項。
來看下Python的官方説明文檔由幾部分構成,如圖13-1所示。
圖13-1 Python官方幫助文檔
Parts of the
documentation:
Python文檔的主要組成部分
What's new in
Python 3.4?
or all"What's new"documents
since 2.0:
Python 3.4有什麼新的特性和改進?或者列舉自2.0以後的所有新特性。
Tutorial:
簡易教程,簡單地介紹Python的語法,這個系列教程比它要詳細得多,所以這裡大家可以不用看。
Library
Reference:
Python官方的枕邊書,這裡邊詳細地列舉了Python所有的內置函數和標準庫的各個模組的用法,非常詳細,但是你看不完的,當作字典來查就可以了。
Installing
Python Modules:
教你如何安裝Python的協力廠商模組。
Distributing
Python Modules:
教你如何發佈Python的協力廠商模組。
Python除了標準庫的幾百個模組之外,還有個Pypi社區,收集了全球的Python愛好者貢獻的模組,你自己寫了一個模組覺得要分享給世界,你也可以發佈上去。
Language
Reference:
討論Python的語法和設計哲學。
Python Setup
and Usage:
介紹在各個平臺上如何使用Python。
Python HOWTOs:
這裡是深入探討一些特定的主題。
Extending and
Embedding:
介紹如何用C和C++開發Python的擴展模組。
FAQs:
常見問題解答。
另外值得一提的是PEP(如果查看文檔經常會看到PEP後邊加上一些數字編號)。PEP是Python
Enhancement Proposals的縮寫,翻譯過來就是Python增強建議書的意思。它是用來規範與定義Python的各種加強與延伸功能的技術規格,好讓Python開發社區能有共同遵循的依據。
每個PEP都有一個唯一的編號,這個編號一旦給定了就不會再改變。例如,PEP
3000就是用來定義Python3的相關技術規格;而PEP
333則是Python的Web應用程式介面WSGI(Web Server
Gateway Interface 1.0)的規範。關於PEP本身的相關規範是定義在PEP 1,而PEP
8則定義了Python代碼的風格指南。有關PEP的列表大家可以參考PEP 0:https://www.python.org/dev/peps/。
舉個例子,說說作者平時遇到問題是怎麼自救的。前邊不是舉了一個計時器的例子嘛,那是自己寫的一個計時器。其實在實際應用中,不建議大家自己動手寫計時器,因為有很多未知的因素會影響到你的資料。所以建議用現成的模組——timeit來對你的代碼進行計時。
那現在假設我不知道timeit模組的用法,應該如何下手?
首先應該先查找幫助文檔,可以使用文檔的搜索或者索引功能——一般情況下輸入關鍵字之後,文檔第一個顯示出來的內容就是你需要的,如圖13-2所示。
圖13-2 如何在幫助文檔中找到自己需要的內容
首先出現的是關於這個模組的介紹,如圖13-3所示。
圖13-3 timeit模組
幫大家大概翻譯下:
timeit — Measure execution time of small code snippets
timeit模組詳解——準確測量小段代碼的執行時間
Source code:Lib/timeit.py(該模組所在的位置)
--------------------------------------------------------------------------------
timeit模組提供了測量Python小段代碼執行時間的方法。它既可以在命令列介面直接使用,也可以通過導入模組進行調用。該模組靈活地避開了測量執行時間時容易出現的錯誤。
接下來就是簡單的使用方法介紹,如圖13-4所示。
圖13-4 timeit模組簡單的使用方法介紹
接著是指出這個模組裡邊包含哪些類、函數、變數及其功能和用法。最後就是實際應用的例子。基本上所有的模組文檔都會遵循這麼一個順序。如果你認為要快速學習一個模組都得讀這麼長的文檔的話,那你還是“too
young,too simple”了。
快速掌握一個模組的用法,可以利用IDLE。先導入模組:
可以調用__doc__屬性,查看這個模組的簡介,可以用print把它帶格式的列印出來:
使用dir()函數可以查詢到該模組定義了哪些變數、函數和類:
但並不是所有這些名字對我們都有用,所以要過濾掉一些不需要的東西。你可能留意到這裡有個__all__屬性,事實是它就是幫助我們完成這麼一個過濾的操作:
timeit模組其實只有一個類和三個函數供我們外部調用而已,所以用__all__屬性就可以直接獲得可供調用介面的資訊。
這裡有兩點需要注意:第一,不是所有的模組都有__all__屬性;第二,如果一個模組設置了__all__屬性,那麼使用“from
timeit import *”這樣的形式導入命名空間,就只有__all__屬性這個清單裡邊的名字才會被導入,其他的名字不受影響:
但如果沒有設置__all__屬性的話,用“from模組名import *”就會把所有不以底線開頭的名字都導入到當前的命名空間。所以,建議在編寫模組的時候,將對外提供的介面函數和類都設置到__all__屬性這個清單裡。
另外還有一個叫作__file__的屬性,這個屬性指明了該模組的原始程式碼位置:
最後,還有一道殺手鐧,也是我們常用的——使用help()函數:
關於timeit模組,由於這個模組實在太有用了(經常用來實現代碼計時),所以作者把對應的文檔做了下翻譯,大家可以收藏一下,今後你肯定會用上的(http://bbs.fishc.com/thread-55593-1-1.html)。
大澳飛宇註:
本書適合學習Python3的入門讀者,也適用對編程一無所知,但渴望用編程改變世界的朋友們!
本書提倡理解為主,應用為王。因此,只要有可能,小甲魚(作者)都會通過生動的實例來讓大家理解概念。
雖然這是一本入門書籍,但本書的「野心」可並不止於「初級水平」的教學。本書前半部分是基礎的語法特性講解,后半部分圍繞着Python3在爬蟲、Tkinter和游戲開發等實例上的應用。
編程知識深似海,小甲魚沒辦法僅通過一本書將所有的知識都灌輸給你,但能夠做到的是培養你對編程的興趣,提高你編寫代碼的水平,以及鍛煉你的自學能力。最后,本書貫徹的核心理念是:實用、好玩,還有參與。

























0 留言:
發佈留言