Python模塊化

Python的包和模塊

module類型

在Python中,使用import關鍵字導入一個包或者模塊,模塊是一個名爲module的類型,只有模塊類型纔可以直接使用import導入。首先是導包過程。

print('導入前:', dir())   # 導包前全局變量
import os
print('導入後:', dir())   # 導包後全局變量

-----輸出結果-----#省略
導入前:全局變量中沒有"os"
導入後:出現了"os"

這說明全局變量中出現了os標識符,這也是我們爲什麼可以使用os.chdir()等通過os標識符進行成員訪問的原因。

再看一下os標識符的類型及os指向的這個對象。

print(type(os))
print(type(os.path))
print(global()["os"])  # 收集全局變量,查看os對象信息

-----輸出結果-----
<class 'module'>
<class 'module'>
<module 'os' from 'E:\\Python\\lib\\os.py'>

上面結果顯示osos.path是一個module類型,這也是os可以使用import導入的原因。只有module類型才能被導入。同理os.path也可以直接被導入。

print(dir())
import os.path
print(dir())
-----輸出結果-----#省略
導入前:全局變量中沒有"os" 或者 "os.path"
導入後:出現了"os",但是沒有"os.path"

os.path並不是一個合法的標識符(字符,下劃線,數字),所以這個os.path對象沒有被變量接受,但是我們之後需要能訪問這個os.path對象 ,所以python爲我們保留了os變量指向os模塊。我們纔可以使用os.path的方式訪問。當然我們使用別名的方式os.path就可以被接收了。

import os.path as osp
osp.dirname(".")           # osp 即指向 os.path模塊對象.調用它全局空間中的dirname(".")函數                   
print(type(osp))           # <class 'module'>
print(globals()["osp"])    # <module'ntpath'from'E:\\Python\\lib\\ntpath.py'>

osp直接指向了我們需要os.path模塊,我們可以osp進行訪問,os變量也就沒有意義,所以此時的全局變量總並不存在os這個全局變量,但是os這個模塊確實被import加載到內存空間中。只是在該模塊中沒有對應的表示符進行對應。

模塊搜索順序

sys.path中記錄了模塊的所有順序,並且可以程序執行時候,導入了哪些類,所有被導入的類都會緩存在sys.modules中,調用即可查看。

import sys
print(*sys.path, "\n", *sys.modules, sep="\n")
--------輸出結果------
'''
D:\學習資料\練習項目\pycharm基礎練習\myproject   # 當前路徑
D:\學習資料\練習項目\pycharm基礎練習             # pycharm 創建的項目環境
E:\Python\python36.zip                       # 以下爲Python安裝路徑下的各個路徑,可以爲zip,egg文件
E:\Python\DLLs
E:\Python\lib
E:\Python
E:\Python\lib\site-packages
'''

{'builtins': <module 'builtins' (built-in)>,   # 已經被導入的各個模塊信息
'sys': <module 'sys' (built-in)>, 
'os': <module 'os' (built-in)>, 
'_frozen_importlib': <module 'importlib._bootstrap'
...
'_tracemalloc': <module '_tracemalloc' (built-in)>, 
'myproject': <module 'myproject' (namespace)>}

.egg文件是由setuptools庫創建的包,第三方庫常用的格式,添加了元數據信息的zip文件。

使用from導入

from ... import ...從一個模塊中導入對象,這個模塊可能是一個.py文件也可能是一個包(目錄),導入的對象可以是包或者模塊下的函數,類,模塊等全局變量標識符,並且導入後可以直接使用import後的原變量標識符進行訪問,而不需要加上模塊前綴。fromimport後指定的內容都將被加載到內存。

from os import path          # 從os 導入 path模塊,並可以使用path全局變量直接訪問
from os.path import dirname  # 從os.path 模塊導入 dirname方法,可以直接通過dirname標識符訪問該方法對象。

from os.path import *        # 默認從os.path模塊導入所有公共變量(非下劃線開頭),並直接使用原變量名作爲該包下的全局變量

使用第三種方式容易造成該包中的全局變量被導入包中全局變量覆蓋。除非清楚的知道兩個包中的變量不會重名,否則儘量不使用。

使用 from ... import *默認導入的是公開變量(非___打頭的變量),非公開變量導入必須手動才能訪問,或者添加到all屬性中才能使用*導入

from os import _abc, __abc   # 直接指定可導入,* 默認不含該類屬性

__all__

在被導入模塊中,可以添加__all__屬性來指定該模塊默認的全部可向外導出的模塊。配合from ...import * 使用。__all__屬性是一個字符串列表,這些字符串必須與該全局空間中的全局變量名對應,或者與該包下的子包名對應。

__all__ = ["x", "y", "_z"]

x = 1
y = 2
z = 3
_z = 4 

這個包被其他包使用 *導入時,導出__all__中對應的x, y,_z變量,實現了我們手動控制變量的導出。

包導入時屬性優先,沒有這個屬性纔會判斷是否有這個模塊。所以如果屬性名和模塊名衝突,該模塊將無法再被導出。

Python中的包就是一個文件夾,文件夾下放各個模塊的.py文件或者一個子包,方便管理。在包上也是可以寫代碼,這就是需要這個包下的__init__.py文件,這個文件屬於這個包,當我們僅僅導入一個包時候,就是導入這個包下的__init__.py文件中的全局變量所指定的內容。並不會自動的導入這個包下的子模塊。

包文件和模塊文件

包模塊的代碼託管到包下的__init__.py文件中,即使包沒有__init__文件仍然可以被導入,但並沒有導入任何內容,這種情況下只能通過這個包名去尋找這個包下的模塊文件。使用from ... inport ...導入子模塊中的內容。

包是一個稍微特殊的模塊對象,包的屬性有['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__'],根據__package__屬性可以判斷它是否是一個包,該屬性爲None或者不存在表示不是包。

包下的模塊爲這個包的子模塊,他們之間可以通過package.mod_name進行訪問,前提是這個模塊已經被導入,可以使用from package import mol_name或者import package.mol_name進行導入,兩種導入效果相同,只是導入空間的變量名不同,使用形式不同。但是隻import package無法自動導入它的子包,package.mul_name將不能訪問。

相對導入和絕對導入

使用import導入模塊順序在sys.module記錄,默認優先從執行文件的當前目錄中尋找,無法找到再去Python安裝文件中的庫中尋找。這是一種絕對路徑導入。

相對導入通常在一個包中使用,方便的將包中的各個模塊進行關聯,這種關聯只受包內文件的相對位置約束,然而對於一個包我們通常不會去幹涉內部的文件關係。相對導入原則遵循文件系統使用原則,即使是包__init__.py文件,這時屬於這個包下的一個子文件,等同於這個包下的其他子文件。

符號 示例
. .m 當前文件夾中的m模塊
.. ..m 上一級文件中的m模塊
... ...m 上上一級文件中的m模塊
from .m import a     # 將會在當前目錄下找 m 文件並導入a
from ..m import a    # 訪問該文件的上一層目錄找 m 文件並導入a
from .m.m1 import a  # 當前文件所在目錄中的m包下的 m1文件並導入a

以上導入方式只能在包中使用,(任何一個相對導入的路徑不能涉及主包所處的當前目錄)這些文件不能作爲主模塊直接運行,它是一種包內部的關係的建立方式,直接運行將報錯。

命名空間

當我們導入一個模塊時,我們可以訪問這個模塊是因爲在當前的作用域內部,創建了一個與模塊名同名的全局變量,纔可以通過這個全局變量對這個模塊進行訪問。所以我們是否能使用一個模塊,需要兩個條件:

  • 1、該模塊被加載到內存中
  • 2、可以找到一個標識符直接和這個模塊對象關聯或者可以通過其他標識符的成員訪問方式訪問到這個模塊對象。

不同的導入文件方式在這個命名空間中創建的標識符是不同的

import os        # 只導入os模塊(os下的__init__.py中的內容),創建os標識符
import os as o   # 只導入os模塊,創建o標識符

import os.path   # 同時導入os 和 os.path,只創建 os標識符,指向os模塊。os.path可訪問
import os.path as osp  # 同時導入os 和 os.path,只創建 osp標識符,指向os.path模塊,os模塊在內存,但是無法訪問,無標識符

from os import path   # 同時導入os 和 os.path,只創建path標識符,指向os.path。os無法訪問,無標識符
from os import *      # 同時導入os中所有公開變量,如果有__all__屬性,導入指定的內容,創建所有 * 指代的標識符,不會創建os

form .m import a   # 在包內導入, m 和 a 均導入,m標識符可能存在,可使用 dir查看確認再使用,a 標識符存在

自定義模塊

模塊命名規範
在導入模塊時,會使用模塊名作爲標識符,這就要求模塊名滿足標識符的命名規範,這樣纔可以被導入。並且模塊一幫使用小寫字符,可以使用下劃線分割。

模塊重複導入

模塊被加載一次後會在內存中緩存這個模塊對象,這個模塊的辨識信息被緩存到sys.modules中。導入模塊時候,首先是從sys.modules中查詢該模塊是否已經被加載,如果已存在,並不會從文件中讀取,而是直接使用內存中已存在的這個模塊對象,否則import會IO搜索該模塊的文件,編譯,執行這個模塊並加載大內存。

緩存的模塊被保存在sys.module中,包括自己,其中還包括解釋器運行需要的模塊。
自定義模塊a/b/c路徑,如果只導入a模塊import a,直接使用a.b是有風險的,該模塊沒有被加載,只有使用import關鍵字時,解釋器纔會模塊進行加載,而不會自動的加載。

模塊運行

當我們執行一個.py文件,此時解釋器會啓動去執行,但在執行這個文件之前,解釋器還需要其他的工作,比如導入一些包,例如io,將我們的.py文件讀入內存,或者build-in,因爲我們可能使用到了這個內建函數。還有其他很多準備流程,當這個工作完成後,纔會執行我們自己的.py文件。而這個.py文件也會被命名爲__main__包。查看這個模塊名,顯示爲__main__

print(__name__)       # __main__

除解釋器執行的代碼外,這個模塊將會作爲最外層的代碼塊,這個模塊的結束代表該線程的結束。也被稱作頂層代碼。頂層代碼包的"name"屬性爲__main__,其餘模塊的__name__屬性爲模塊名。所以測試代碼通常這樣寫

if __name__ == "__main__":
    # test code   

只有這個模塊直接執行時,if 中的測試代碼纔會執行。

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