2020年9月12日星期六

51 《零基礎入門學習Python》筆記 第051講:__name__屬性、搜索路徑和包

《零基礎入門學習Python》筆記    第051講:__name__屬性、搜索路徑和包


目錄
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
測試題(筆試,不能上機哦~)
0. __name__ 屬性的含義是什麼?
1. 什麼時候__name__ 屬性的值是"__main__"?
2. 如果獲得當前Python 的搜索路徑?
3. 如果你不想將相關的模塊文件放在當前文件夾內,那最好的選擇是?
4. 如果你見到import urllib.request 語句,那麼這個urllib 是什麼?
5. Python 如何區分一個文件夾是普通文件夾還是包?
動動手(一定要自己動手試試哦~)
0. 執行下邊a.py 或b.py 任何一個文件,都會報錯,請改正程序。
1. 下邊是一個Python 項目的基本結構,請你合理組織它們,便於維護和使用。

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

上一節課我們已經基本介紹了模塊的作用以及用法,我們現在來簡單回顧一下(溫故而知新,可以為師矣),模塊的主要作用有哪些呢?
封裝、組織Python的代碼:當代碼量非常大的時候,我們可以有組織、有紀律的根據不同的功能將代碼分割為不同的模塊,這樣,每個模塊之間互相就是分隔開的、獨立開的,那大家想想,代碼是分開了容易閱讀和測試還是放在一起好?我們當然是更願意去閱讀和測試一小段代碼,而不是一來就劈頭蓋臉把整個程序讀起。
模塊的第二個作用就是實現代碼的重用,比如說你寫了一段發送郵件的代碼,多次調試、優化之後你覺得這段代碼非常棒了,那你可以將這段代碼封裝成一個獨立的模塊,那以後在任何程序需要發送郵件的時候,只要導入這個模塊,調用相關的函數就可以實現了,而不是說每一次都重複寫相同的代碼。
(一)我們這一講接著談談模塊,第一個內容是:
if __name__ == '__main__'
相信大部分人在讀別人的Python代碼的時候,都會看到這麼一句代碼,但是卻不知道有什麼用,那今天我就來給大家解惑啦,我們先來舉個例子吧:
還是上節課溫度轉換的例子,一般我們在寫了一個模塊之後,我們會單獨對這個模塊進行測試,因為一個大程序會有許多的模塊,我們應該每個模塊獨立寫完之後,在後面寫一個獨立的test(),對這個模塊的功能進行測試,而不是組裝起來才測試,那就很難調試了。對於這個溫度轉換的模塊,我們這裡的測試就是對前面兩個函數進行調用,如果正確,那就OK了
我們在模塊裡調用test(),然後模塊就可以直接運行了。
  1. def c2f(cel):
  2. fah = cel * 1.8 + 32
  3. return fah
  4. def f2c(fah):
  5. cel = (fah - 32) / 1.8
  6. return cel
  7. def test():
  8. print("测试,0摄氏度 = %.2f华氏度"%c2f(0))
  9. print("测试,0华氏度 = %.2f摄氏度"%f2c(0))
  10. test()
運行結果:
  1. >>>
  2. ====== RESTART: C:\Users\XiangyangDai\Desktop\TemperatureConversion.py ======
  3. 测试,0摄氏度 = 32.00华氏度
  4. 测试,0华氏度 = -17.78摄氏度
經過測試,模塊的功能是沒問題的,然後我們在calc.py 這段源代碼裡面調用這個模塊的話:
  1. import sys
  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")
  3. import TemperatureConversion as tc
  4. print("32摄氏度 = %.2f华氏度"%tc.c2f(32))
  5. print("99华氏度 = %.2f摄氏度"%tc.f2c(99))
運行結果:
  1. >>>
  2. =============== RESTART: C:\Users\XiangyangDai\Desktop\calc.py ===============
  3. 测试,0摄氏度 = 32.00华氏度
  4. 测试,0华氏度 = -17.78摄氏度
  5. 32摄氏度 = 89.60华氏度
  6. 99华氏度 = 37.22摄氏度
Python就不自覺的把模塊中的測試代碼也給打印出來了,那這並不是我們想要的啊,那麼這種情況該怎麼去避免呢?
避免這種情況的關鍵在於讓Python知道該模塊是作為程序運行還是導入到其他程序中,為了實現這一點,你就需要使用__name__變量,
如果是在主程序裡面使用這個__name__變量的話,那得到的是個"__main__"
如果是在模塊中調用 這個__name__變量的話,那得到的就是這個模塊的名字。
  1. >>> __name__
  2. '__main__'
  3. >>>
  4. >>> tc.__name__
  5. 'TemperatureConversion'
這樣的話,你就不難理解這個  if __name__ == '__main__'這句代碼的意思了。
所以在上面的模塊的測試文件中,需要這樣改寫:
  1. def c2f(cel):
  2. fah = cel * 1.8 + 32
  3. return fah
  4. def f2c(fah):
  5. cel = (fah - 32) / 1.8
  6. return cel
  7. def test():
  8. print("测试,0摄氏度 = %.2f华氏度"%c2f(0))
  9. print("测试,0华氏度 = %.2f摄氏度"%f2c(0))
  10. if __name__ == "__main__":
  11. test()
也就是說,只有在直接運行該模塊代碼時(也就是測試模塊的時候),__name__ 才等於"__main__",這時才運行test(),打印測試結果,當調用該模塊時, __name__ 是等於"模塊名"的,就不會打印測試結果了。
(二)我們接下來探索第二個問題:搜索路徑
像我們遇到一個問題:寫好的模塊應該擺在哪裡?有人說,“上節課你不是說應該放在和導入模塊的文件的源文件放在同一個文件夾嗎”,這是一個方案,你要調用哪個模塊,導入哪個模塊,把他們全部放在Python模塊的文件夾下,但是有的人就說了,“我並不喜歡把所有的程序放在同一個文件夾下,我想通過文件夾的形式更好的組織我的代碼”,這是可以實現的,但是在此之前,你必須先理解搜索路徑這個概念。
Python模塊的導入需要一個路徑搜索的過程,也就是說,你導入一個叫做hello 的模塊,Python會在預定義好的搜索路徑裡面找一個叫做hello.py 的模塊文件,如果有,則導入模塊,如果沒有,則導入失敗,而這個搜索路徑呢,就是一個列表,一組目錄,我們可以sys 模塊中的path 變量把它顯示出來:
  1. >>> import sys
  2. >>> sys.path
  3. ['D:\\ProgramFiles\\Anaconda3\\Lib\\idlelib', 'D:\\ProgramFiles\\Anaconda3\\python35.zip', 'D:\\ProgramFiles\\Anaconda3\\DLLs', 'D:\\ProgramFiles\\Anaconda3\\lib', 'D:\\ProgramFiles\\Anaconda3', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\Sphinx-1.4.6-py3.5.egg', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\win32', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\win32\\lib', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\Pythonwin', 'D:\\ProgramFiles\\Anaconda3\\lib\\site-packages\\setuptools-27.2.0-py3.5.egg']
sys是與系統相關的模塊,Python會從上面的路徑中一個一個的去搜索有沒有相關的模塊。這裡需要解釋的是,不同的系統的sys.path顯示的路徑是不一樣的,這裡最佳的存放模塊的位置是'...\\lib\\site-packages',該文件夾本來就是設置類存放模塊的。
另外,按照這裡邏輯來說,你只需要告訴Python 你的模塊文件在哪裡找,Python在導入模塊的時候就可以正確找到,例如:我們想把桌面上的一個模塊導入,我們直接導入的話,是找不到的,因為桌面的路徑是不屬於sys.path 的搜索路徑的,我們就可以把桌面路徑加到搜索路徑中去。
  1. import sys
  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")
這時候,再導入就不會出錯了,對於其他任意的文件夾,都可以這樣操作。
(三)接下來談最後一個概念:包(package)
在實際開發中,一個大型的系統通常有成千上萬個Python模塊,那麼使用模塊定義Python的功能還是遠遠不夠的,因為如果你把這些模塊全部放在一起,那你想想看,成千上萬個文件放在一個文件夾是什麼概念,非常亂,非常糟糕,而且還會有命名衝突的可能,那麼因此呢,Python中也出現了包的概念,什麼是包呢,其實就像我們剛才做的,把模塊分門別類的放到不同的文件夾,然後把各個文件夾的位置告訴Python。但是包的做法更加簡潔,創建一個包也非常簡單,步驟如下:
1、創建一個文件夾,用於存放相關的模塊,文件夾的名字就是包的名字;
例如,我們這裡創建一個文件夾M1,然後把模塊扔進去:
2、在文件夾中創建一個__init__.py 的模塊文件,內容可以為空:
3、將相關的模塊放入文件夾中(在第一步中我們就已經放進去了)。
在第2步中,必須在包目錄下創建一個__init__.py的模塊,你可以是一個空文件,但是必須要有這個文件,因為這是Python的規定,用來告訴Python,把這個文件夾(這個目錄)當做一個包來管理。
接下來,要導入包的模塊,怎麼導?其實也很簡單,就是使用包名.模塊名來導入就可以了
  1. import sys
  2. sys.path.append("C:\\Users\\XiangyangDai\\Desktop")
  3. import M1.TemperatureConversion as tc
  4. print("32摄氏度 = %.2f华氏度"%tc.c2f(32))
  5. print("99华氏度 = %.2f摄氏度"%tc.f2c(99))
  6. >>>
  7. =============== RESTART: C:\Users\XiangyangDai\Desktop\calc.py ===============
  8. 32摄氏度 = 89.60华氏度
  9. 99华氏度 = 37.22摄氏度

測試題(筆試,不能上機哦~)


0. __name__ 屬性的含義是什麼?

答:所有模塊都有一個__name__ 屬性,__name__ 的值取決於如何應用模塊,在作為獨立程序運行的時候,__name__ 屬性的值是'__main__',而作為模塊導入的時候,這個值就是該模塊的名字了。


1. 什麼時候__name__ 屬性的值是"__main__"?

答:模塊在作為獨立程序運行的時候,__name__ 屬性的值是'__main__'


2. 如果獲得當前Python 的搜索路徑?

  1. >>> import sys
  2. >>> sys.path
  3. ['', 'C:\\Python34\\Lib\\idlelib', 'C:\\WINDOWS\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages']


3. 如果你不想將相關的模塊文件放在當前文件夾內,那最好的選擇是?

答:放在site-packages 文件夾,因為它就是用來存放你的模塊文件的。


4. 如果你見到import urllib.request 語句,那麼這個urllib 是什麼?

答:是一個包,Python 把同類的模塊放在一個文件夾中統一管理,這個文件夾稱之為一個包。
urllib 是Python 負責管理URL 的包,用於訪問網址(後邊我們會講到)。


5. Python 如何區分一個文件夾是普通文件夾還是包?

答:看文件夾中是否有__init__.py 文件。
必須在包文件夾中創建一個__init__.py 的模塊文件,內容可以為空。可以是一個空文件,也可以寫一些初始化代碼。這個是Python 的規定,用來告訴Python 將該目錄當成一個包來處理。

動動手(一定要自己動手試試哦~)


0. 執行下邊a.py 或b.py 任何一個文件,都會報錯,請改正程序。

注:這道題原理跟上一節課的課後作業(測試題4、5)類似,如果上節課你搞懂了,這道題應該可以想出解決方案,不要輕易看答案,除非你已經抓破頭皮……
  1. # a.py
  2. import b
  3. def x():
  4. print('x')
  5. b.y()
  6. # b.py
  7. import a
  8. def y():
  9. print('y')
  10. a.x()
  11. 执行 b.py 引发下边异常:
  12. >>>
  13. Traceback (most recent call last):
  14. File "/Users/FishC/Desktop/b.py", line 1, in <module>
  15. import a
  16. File "/Users/FishC/Desktop/a.py", line 1, in <module>
  17. import b
  18. File "/Users/FishC/Desktop/b.py", line 6, in <module>
  19. a.x()
  20. AttributeError: 'module' object has no attribute 'x'
答:因為在執行b.py 的加載過程中,需要創建新的模塊對象b,然後執行b.py 對應的字節碼。當遇到第一條語句(import a)的時候,Python 會轉而去導入a.py 並生成模塊對象a。同樣遇到第一條語句(import b)的時候,Python 就跑去導入模塊b,此時發現b 模塊已經導入(在sys.modules 中存在),繼而執行b 模塊的字節碼,當執行到ax() 的時候,由於模塊a 此時並未完全導入,所以拋出AttributeError 異常。
怕有些魚油可能看不懂,小甲魚給大家整理下,看Python 是如何被當成猴子耍的:
執行b.py -> import a -> 查找a 模塊-> 未發現a 模塊對象-> 導入a.py -> import b -> 查找b 模塊-> 發現b 模塊對象-> 接著往下執行字節碼(import a 已執行過,Python 有機制確保不重複導入,因而不會再執行) -> ax() -> 在a 模塊中找不到x(),因為a 還沒有被完全導入嘛……
好了,解決的方案也很簡單,用這節課的知識,就是使用if __name__ == "__main__" 來確保Python 不要在導入的過程中調用不該調用的函數。
所以應該這麼寫:
  1. # a.py
  2. import b
  3. def x():
  4. print('x')
  5. if __name__ == "__main__":
  6. b.y()
  7. # b.py
  8. import a
  9. def y():
  10. print('y')
  11. if __name__ == "__main__":
  12. a.x()


1. 下邊是一個Python 項目的基本結構,請你合理組織它們,便於維護和使用。

答:通過將相關的模塊組織成包,使項目結構更為完善和合理。從而增強代碼的可維護性和實用性。
以下提供一個可供參考的Python項目結構(僅供參考,沒有硬性規定):
|----README/
| |----readme.txt
| |----LICENSE.txt
| |----requirents.txt
| |----setup.py
|----docs/
| |----help.html
| |----quickstart.html
|----test/
| |----__init__.py
| |----test_basic.py
| |----test_advanced .py
|----package/
| |----__init__.py
| |----moduleA.py
| |----moduleB.py
| |----moduleC.py
| |--- -static/
| | |----images/
| | |----sounds/
|----setup.py

0 留言:

發佈留言