python學習筆記(八)包和庫

 

目錄

(一)何爲包

(二)創建一個包

1)包內的__init__文件

2)mathproj包的基本用法

3)子包和子模塊的加載

4)包內的import語句

5)all屬性

6)包的合理使用

7)包總結

(三)Python庫的使用


 

(一)何爲包

模塊可以讓小塊代碼輕鬆得以重新利用。當項目不斷壯大,就會出現多種問題,需要重載的代碼在物理邏輯上都會超出單個文件的合理大小。解決這個問題的方案是把有關聯的模塊組合到同一個包中。

 

模塊是容納代碼的文件,一個模塊定義了一組Python函數和其他對象,通常這些函數和對象都是關聯的。模塊的名稱由文件名稱而來。

包就是包含代碼和子目錄的目錄。包裏包含了一組通常相互關聯的代碼文件(模塊)。包的名稱由主目錄名而來。

包是模塊概念的自然擴展,旨在應付非常大型的項目。模塊把相互關聯的函數、類和變量進行了分組,同理,包則是把相互關聯的模塊進行了分組。

 

 

Python搜索模塊的路徑是由四部分構成的:程序的主目錄、PATHONPATH目錄、標準鏈接庫目錄和.pth文件的目錄,這四部分的路徑都存儲在sys.path 列表中。

 

在導入自定義的模塊時,除了指定模塊名之外,也需要指定目錄,由於Python把目錄稱作包,因此,這類導入被稱爲包導入。包導入把計算機上的目錄變成Python的命名空間,而目錄中所包含的子目錄和模塊文件則對應命名空間中的屬性。

Python已經導入的模塊保存在一個內置的sys.modules字典中,以便記錄哪些模塊已經記錄了。

如果想看模塊搜索路徑在機器上的實際配置,可以通過打印內置的sys.path列表來查看,這個列表是sys模塊的path屬性:

>>> import sys
>>> sys.path
['', 'D:\\python37\\Lib\\idlelib', 'D:\\python37\\python37.zip', 'D:\\python37\\DLLs', 'D:\\python37\\lib', 'D:\\python37', 'C:\\Users\\administrator\\AppData\\Roaming\\Python\\Python37\\site-packages', 'D:\\python37\\lib\\site-packages']

 

 

(二)創建一個包

這裏用一個能夠運行的例子來演示包機制的內部工作原理。文件名和路徑將顯示爲普通文本,以便明確我們分析的是文件還是目錄,還是由該文件/目錄定義的模塊/包。以下是這個mathproj包的樹狀結構圖:

以下是這個包的代碼:

  • mathroj/__init__.py文件
print("Hello from mathproj init")
__all__ = ['comp']
version = 1.03
  • mathproj/comp/__init__.py
__all__ = ['c1']
print("Hello from mathproj.comp init")
  • mathproj/comp/c1.py
x = 1.00
  • mathproj/comp/numeric/__init__.py
print("Hello from numeric init")
  • mathproj/comp/numeric/n1.py
from mathproj import version
from mathproj.comp import c1
from mathproj.comp.numeric.n2 import h
def g():
    print("version is ",version)
    print(h())
  • mathproj/comp/numeric/n2.py
def h():
    return "Called function h in module n2"

當我們把以上代碼按照樹狀結構輸入mathproj文件中後,還要確保mathproj在python的當前工作目錄。

1)包內的__init__文件

以上代碼中能發現,每個文件夾中都有一個__init__.py文件,這個文件有兩個用途:

  • Python要求,只有包含__init__.py文件的目錄纔會被識別爲包。這可防止意外導入包含其他Python代碼的目錄。
  • 當第一次加載包或子包時。Python會自動執行__init__.py文件。有了這種自動執行的機制,就能夠完成任何必要的包初始化工作。

很多包都不需要在其__init__文件中寫入任何內容,只要保證有個空的__init__.py文件就行了。

2)mathproj包的基本用法

當做好以上工作後,保證你的mathproj模塊存在於你的IDLE模塊檢索路徑之中時,在Python shell中執行以下語句:

>>> import mathproj
Hello from mathproj init
>>> mathproj.version
1.03

如果一切順利,Python將會執行__init__.py文件輸出一段字符串,並且進入一個新的命令提示符,不會有錯誤信息。也就是說,第一次加載包的時候會自動執行__init__.py文件。

mathproj/__init__.py文件將把變量version賦值爲1.03。變量version在mathproj包命名空間的作用域內,創建完畢後就可以通過mathproj訪問,在mathproj/__init__.py文件之外也能夠進行訪問。

在使用過程中,包看起來與模塊類似,都可以通過屬性訪問到其內部定義的對象。因爲包就是模塊的彙總。

3)子包和子模塊的加載

mathproj包中的各個文件也是可以相互進行交互的。比如我們想訪問mathproj/comp/nmeric/n1.py文件中定義的函數g,那麼這個模塊是否已經加載完畢了。上面已經導入了mathproj包,那麼它的子包是否導入了呢:

>>> mathproj.comp.numeric.n1
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    mathproj.comp.numeric.n1
AttributeError: module 'mathproj' has no attribute 'comp'

我們發現訪問這個函數會進行報錯,也就是說,只加載頂級模塊是不夠的,這並不會加載其全部子模塊。這是符合Python理念的,即清晰比簡潔更重要。

解決以上問題,只要導入所需模塊即可,然後就可以執行該模塊中的函數g了:

>>> import mathproj.comp.numeric.n1
Hello from mathproj init
Hello from mathproj.comp init
Hello from numeric init
>>> mathproj.comp.numeric.n1.g()
version is  1.03
Called function h in module n2

不過這麼導入的話,每一個包中的__init__.py文件都會被執行,每一個包都被導入,可以在其中產看上一級包的內存地址判斷這個包是否被導入:

>>> mathproj.comp
<module 'mathproj.comp' from 'D:\\python37\\mathproj\\comp\\__init__.py'>
>>> mathproj.comp.numeric
<module 'mathproj.comp.numeric' from 'D:\\python37\\mathproj\\comp\\numeric\\__init__.py'>

4)包內的import語句

包內的文件不會自動獲得包內其他文件的訪問權限,無法訪問同一包內其他文件中定義的對象。我們來看一下n1.py文件中:

from mathproj import version
from mathproj.comp import c1
from mathproj.comp.numeric.n2 import h
def g():
    print("version is ",version)
    print(h())

n1.py文件想要訪問其他包和文件中的version以及h函數,需要先進行導入。

因爲n2.py和n1.py位於同一個目錄中,所以還可以使用相對導入的方式,只要在子模塊名前加一個.即可。也就是n1.py的第三行寫成這樣,一樣可以奏效:

from .n2 import h

5)all屬性

一般來說,如果外部代碼執行了from module inport *語句,就應該從mathproj導入全部的非私有對象名稱。實際上着比較難以實現主要問題是,對於不容的操作系統來說,對文件名的定義規則比較含糊,這就導致了子包導入後其確切名稱也含糊不定。

例如,寫的是from mathproj import *,那麼comp會被導入爲comp、Comp還是COMP呢?如果只是依賴系統給出的名稱,那麼結果是不可預測的。

爲了解決這個問題,Python引入了__all__屬性,__all__屬性應該給出一個字符串列表,定義瞭如果外部代碼對這個包使用from module import *時,應該導入的名稱。如果沒有定義這個屬性的話,我們在使用from module import *時,不會對該包執行任何操作。

我們在上面定義的包中輸入以下語句:

>>> from mathproj import *
Hello from mathproj init
Hello from mathproj.comp init
>>> comp
<module 'mathproj.comp' from 'D:\\python37\\mathproj\\comp\\__init__.py'>
>>> c1
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    c1
NameError: name 'c1' is not defined
>>> from mathproj.comp import *
>>> c1
<module 'mathproj.comp.c1' from 'D:\\python37\\mathproj\\comp\\c1.py'>

從上面的代碼中能看出來,使用from module import *導入時,和前面import module語句一樣的規則。

導入包時,只是對這個包的名稱,而不會導入這個包裏面的包的名稱,要想得到這個名稱也需要進行一次顯式的導入。

6)包的合理使用

大多數包的結構,都不應該像以上例子暗示的那麼複雜。有了Python包機制,包設計時的複雜程度和嵌套層次就能有很大的自由度了。

  • 包不應該採用嵌套很深的目錄結構。除非代碼量極其龐大,否則沒有必要這樣做。大多數包只需要一個頂級目錄即可。兩層目錄結構就應該能有效處理絕大部分情況。正如Python之禪中所述“平直勝於嵌套”。
  • 只要不在__all__屬性中列出,就可以用__all__屬性對from module import *隱藏這些對象的名稱。儘管如此,但這可能並不算是一種好方案,因爲這會導致不同導入方式的結果不一致。如果需要隱藏對象名稱,請用前導雙下劃線讓他們稱爲私有對象。

7)包總結

1,包的初始化

Python在首先導入某個目錄時,會自動執行該目錄下的__init__.py文件中的所有程序代碼。

2,模塊命名空間的初始化

在包導入模型中,腳本內的目錄路徑,在導入後會變成真實的對象路徑,即爲目錄創建的模塊對象提供了命名空間。

3,from *語句的行爲

在__init__.py文件內使用__all__列表,來定義目錄以from * 語句形式導入時,需要導出的屬性清單。如果沒有設置__all__,from *語句不會自動加載潛逃與該目錄內的子模塊,也就是說,只加載該目錄下的__init__.py文件中羅列在__all__列表中的變量。

注意以下幾點:

  1. 有了包機制,就能夠創建橫跨多個文件和目錄的代碼庫。
  2. 相比單個模塊,利用包可以更好地組織大型的代碼集。
  3. 除非真有龐大而複雜的庫,否則包裏嵌套的目錄大於一或兩層時就得當心了。

 

(三)Python庫的使用

在Python中,所謂的庫是由多個組件組成的,包括無須import語句即可使用的數值和列表之類的內置數據類型和常量,以及一些內置函數和異常。庫中最大的部分就是大量的模塊。只要安裝了Python,就可以用庫來操作各類數據和文件、與操作系統交互、爲衆多互聯網協議編寫服務端和客戶端、開發和調試代碼。

 

 

參考:

  1. 悅光陰

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章