模塊&包---import時發生的那些事

大型Python程序以模塊module和包package的形式組織。

模塊:就是一些.py文件,可以包含函數、變數、類等符號;

包:由模塊及子包組成

使用import載入的模塊實際上可分為4個通用類別:

  • 使用Python編寫的代碼(.py文件)
  • 已被編譯為共享庫或DLL的C或C++擴展
  • 包含一組模塊的包
  • 使用C編寫並鏈接到Python解釋器的內置模塊

模塊module

  • import module (包括import module as xxx)

首次使用import載入模塊時,它將做3件事:

  1. 創建新的命名空間,用作在相應源文件中定義的所有對象的容器。在模塊重定義的函數和方法在使用global語句時將訪問該命名空間。
  2. 在新創建的命名空間中執行模塊中包含的代碼。
  3. 在調用函數中創建名稱來引用模塊命名空間。這個名稱與模塊的名稱相匹配。

e.g. 在某模塊A中import math時,會在A中創建一個命名空間,並在這個命名空間中執行math.py當中的代碼,並且在A中創建了math這個名稱來引用這個命名空間。所以你就可以在A中用math.sqrt()和math.pi這些函數及變數了。

  • from module import symbol (包括from module import *, from module import symbol as xxx )

從模塊導入選定符號

from語句用於將模塊中的具體定義載入到當前命名空間中。from語句相當於import,但它不會創建一個名稱來引用新創建的模塊命名空間,而是將對模塊中定義的一個或多個對象的引用放到當前命名空間中

e.g. 在模塊A中 from math import sqrt,那麼sqrt函數會直接導入到當前的命名空間中來,並沒有創建新的命名空間 。所以就可以在A直接使用sqrt()函數了。

注意要點:

  • 星號(*)通配符用於載入模塊中的所有定義,但以下劃線開頭的定義除外;
  • 通過定義列表__all__,模塊可以精確控制from module import *導入的名稱集合;<下圖為驗證>
  • 函數的全局命名空間始終是定義該函數的模塊,而不是將函數導入並調用該函數的命名空間;<具體請參考Python參考手冊8.2節,因與此文無關,暫且不表>

定義__all__對from module import *的影響

總結:一個模塊里可以定義函數、變數、類等符號,模塊裡面還可以導入其他模塊、導入其他模塊裡面的符號。模塊還可以定義一個__all__列表,用以控制from module import * 這條語句的效果。


包package

包可用於將一組模塊分組到一個常見的包名稱下。這項技術有助於解決不同應用程序中使用的模塊名稱之間的命名空間衝突問題。包是通過使用與其相同的名稱創建目錄,並在該目錄中創建文件__init__.py來創建的。如果需要,可以向該目錄中放入其他源文件、編譯後的擴展和子包。

例如下面:包package包含兩個子包pack1、pack2,還包含一個func模塊

包的常見結構

QUESTION:要使用包裡面的函數,難道只能package.func.xxx或者package.pack1.module_11.xxx這樣調用嗎?可是很多時候我們導入一個包,可以直接用一個函數的。比如import requests,就可以直接用requests.get()函數了。這是怎麼發生的呢?

  • 只要第一次導入包中的任何部分,就會執行文件__init__.py中的代碼。

<上面模塊的導入講到,第一次導入一個模塊也會執行這個模塊>,所義,其實導入一個包,就是導入這個包中的__init__.py模塊,但是用包名來引用這個模塊。

  • from package import * 要小心

from module import * 會將模塊中的符號都導入(如果定義了__all__的話,就導入__all__列表定義的內容)。

本來希望from package import *將與某個包相關聯的所有子模塊導入到當前命名空間中。但是由於各個系統之間的文件名約定不同,Python無法準確的確定各個模塊的具體內容。結果,該語句只會導入package目錄的__init__.py文件中定義的所有名稱(包括__init__.py文件定義的函數,導入的內容等)。當然__init__.py也是個模塊,所以這種行為可以通過定義__init__.py文件的列表__all__來修改。

查看不同包的__all__

  • 單獨導入包名(import package)不會導入包中所包含的所有子模塊。

這句話要一分為二地看:

  • 當包的__init__.py文件為空時,導入包名沒法使用包內的子包及模塊

__init__.py為空,from pack1 import *無法導入子模塊

  • 包的__init__.py並不為空

我們說,導入一個包,會執行包的__init__.py文件,那麼就可以在這上面做文章了。

想一想,我們requests庫時,不是直接import requests,然後就可以直接使用requests.get()等方法了嗎?

requets庫的__init__.py文件

在requests庫的__init__.py文件中,已經把一些常用的方法導進來了。


總結:

  • 第一次導入一個模塊,會執行這個模塊
  • 可以通過修改模塊module的__all__列表,來改變from module import * 時的效果
  • 導入一個包,其實就是導入包的__init__.py模塊
  • 如果包的__init__.py模塊為空,那麼import package這樣的語句是不能使用包當中的任何模塊的
  • 如果包的__init__.py模塊為空,那我們只能使用import package.module或者from package import module這樣的導入方式
  • __init__.py也是個模塊,其實也可以在__init__.py中直接定義函數fun,那樣import package就可以直接用package.fun這個函數了是吧。但是我們一般不會這麼干,這樣會使__init__.py文件太亂
  • __init__.py也是個模塊,那也可以在這個模塊中導入其他模塊,這樣import package時,就能直接使用一些符號了。
  • __init__.py也是個模塊,也可以定義__all__列表變數,控制from package import * 的作用。

綜上

  • 導入一個包,其實就是導入包的__init__.py模塊。
  • 這個模塊裡面可定義的內容跟一般的模塊沒什麼兩樣。

推薦閱讀:

python 的 dict真的不會隨著key的增加而變慢嗎?
如何在visual studio上寫 python?
python中的漢諾塔遞歸演算法的具體運算過程是怎樣的?

TAG:Python | 编程 | Python教程 |