Python基礎手冊28——模塊的高級概念

三、模塊命名空間

命名空間(名稱空間)中保存了變量名到對象的映射。向命名空間添加變量名的操作過程涉及到綁定變量到指定對象的操作(以及給該對象的引用計數加 1 )。改變一個變量的綁定叫做重新綁定,刪除一個變量叫做解除綁定。

我們在之前已經介紹過在程序執行期間有兩個或三個活動的命名空間。 這三個名稱空間分別是局部命名空間,全局命名空間和內建命名空間,但局部命名空間在執行期間是不斷變化的,所以我們說"兩個或三個"。 從命名空間中訪問這些變量名依賴于它們的加載順序,或是系統加載這些命名空間的順序。

Python 解釋器首先加載內建命名空間。 它由 __builtins__ 模塊中的名字構成。 隨后加載執行文件的全局命名空間,它會在模塊開始執行后變為活動命名空間。 這樣我們就有了兩個活動的名稱空間。

如果在執行期間調用了一個函數,那么將創建出第三個命名空間,即局部命名空間。 我們可以通過 globals()locals() 內建函數判斷出某一名字屬于哪個命名空間。


1、命名空間和變量作用域

命名空間保存的是純粹意義上的變量名和對象間的映射關系,而作用域還指出了從用戶代碼的哪些物理位置可以訪問到這些變量名。

注意每個命名空間都是一個單獨的單元。但從作用域的觀點來看,事情是不同的。所有局部命名空間的名稱都在局部作用范圍內。局部作用范圍以外的所有變量名都在全局作用范圍內。

還要記得在程序執行過程中,局部命名空間和作用域會隨函數調用而不斷變化,而全局命名空間是不變的。


2、變量查找

那么作用域的規則是如何聯系到命名空間的呢? 它所要做的就是變量名查詢。訪問一個變量時,解釋器必須在三個命名空間中的一個找到它。 首先從局部命名空間開始,如果沒有找到,解釋器將繼續查找全局命名空間。如果這也失敗了,它將在內建命名空間里查找。

3、無限制的命名空間

Python 的一個有用的特性在于你可以在任何需要放置數據的地方獲得一個命名空間。比如,你可以在任何時候給函數添加屬性(使用熟悉的句點屬性標識)。

4、文件生成命名空間

模塊最好理解為變量名的封裝,也就是定義想讓系統其余部分看見變量名的場所。從技術上來講,模塊通常相應于文件,而 Python 會建立模塊對象,以包含模塊文件內所賦值的所有變量名。簡而言之,模塊就是命名空間(變量名建立所在的場所),而存在于模塊之內的變量名就是模塊對象的屬性。

5、導入和作用域

不導入一個文件,就無法存取該文件內所定義的變量名。也就是說,你不能自動看見另一個文件內的變量名。對于函數也是一樣的,函數絕對無法看見其他函數內的變量名,除非它們從物理上處于同一個函數內。模塊程序代碼絕對無法看見其他模塊內的變量名,除非明確地進行了導入。

在 Python 中,一段程序的作用域完全由程序所處的文件中的實際位置決定。作用域絕不會被函數調用或模塊導入影響。

解釋器執行到這些導入語句,如果在搜索路徑中找到了指定的模塊,就會加載它。該過程遵循作用域原則,如果在一個模塊的頂層導入,那么它的作用域就是全局的;如果在函數中導入,那么它的作用域是局部的。

6、命名空間的嵌套

雖然導入不會使命名空間發生向上的嵌套,但確實會發生向下的嵌套。利用屬性的點號運算路徑,有可能深入到任意嵌套的模塊中并讀取其屬性。

\模塊通過使用自包含的變量的包,也就是所謂的命名空間提供了將功能化的接口組織為系統的簡單的方法。在一個模塊文件的頂層定義的所有的變量名都成了被導入的模塊對象的屬性。導入給予了對模塊的全局作用域中的變量的讀取權。也就是說,在模塊導入時,模塊文件的全局作用域變成了模塊對象的命名空間。Python 的模塊允許將獨立的文件連接成一個更大的程序系統。

四、模塊搜索路徑

通常來說,導入過程最重要的部分是最一開始的搜索部分,也就是定位要導入的文件。在大多數情況下,可以依賴模塊導入搜索路徑的自動特性,完全不需要配置這些路徑。

Python 的模塊搜索路徑是下面這些部分組合而成的結果:
1、程序的主目錄
2、PYTHONPATH 目錄(如果已經進行了設置)
3、標準鏈接庫目錄
4、任何 .pth 文件的內容(如果存在的話)

這四個組件組合起來就變成了 sys.path 。默認搜索路徑是在編譯或是安裝時指定的。搜索路徑的第一和第三元素是自動定義的,第二和第四元素,可以用于拓展路徑,從而包含你自己的源代碼目錄。

當導入的模塊不在搜索路徑里時,導入模塊操作會失敗。


1、主目錄

Python 首先會在主目錄內搜索導入的文件。當你運行一個程序的時候,這個目錄就是你運行程序頂層腳本文件時所在的目錄。當在交互模式下工作時,這一入口就是你當前的工作目錄。

因為這個目錄總是先被搜索,如果程序完全位于單一目錄,所有導入都會自動工作,而不需要配置路徑。另一方面,由于這個目錄是先搜索的,其文件也將覆蓋路徑上的其他目錄中具有同樣名稱的模塊。


2、PYTHONPATH 目錄

搜索完主目錄之后如果沒有發現相應的文件,Python會從左至右(假設你設置了的話)搜索 PYTHONPATH 環境變量設置中羅列出的所有目錄。PYTHONPATH 是設置包含 Python 程序文件的目錄的列表,這些目錄可以是用戶定義的或平臺特定的目錄名。你可以把想導入的目錄都加進來,而 Python 會使用你的設置來擴展模塊搜索的路徑。

因為 Python 會先搜索主目錄,當導入的文件跨目錄時,這個設置才顯得格外重要。也就是說,如果你需要被導入的文件與進行導入的文件處在不同目錄時,可以考慮設置這個環境變量。


3、標準庫目錄

接著,Python 會自動搜索標準庫模塊的安裝目錄。因為這些一定會被搜索,通常是不需要添加到 PYTHONPATH 之中或包含到路徑文件中的。

4、.pth 文件目錄

Python 允許用戶通過配置 .pth 文件把有效的目錄添加到模塊搜索路徑中去,也就是在后綴名 .pth(路徑的意思)的文本文件中一行一行地列出目錄。

簡而言之,當內含目錄名稱的文本文件放到適當目錄時,也可以概括地扮演與PYTHONPATH 環境變量設置相同的角色。

當存在目錄的時候,Python 會把文件每行所羅列的目錄從頭到尾加到模塊搜索路徑列表的最后。實際上,Python 會將收集它所找到的所有路徑文件中的目錄名,并且過濾掉任何重復的和不存在的目錄。

搜索路徑的 PYTHONPATH 和路徑文件部分允許我們調整導入查找文件的地方。 搜索路徑的配置可能隨平臺以及 Python 版本而異。取決于你所使用的平臺,附加的目錄也可能自動加入模塊搜索路徑。


5、sys.path 列表

如果你想查看模塊搜索路徑在機器上的實際配置,可以通過打印內置的 sys.path 列表(也就是標準庫模塊 sys 的 path 屬性)來查看這個路徑。目錄名稱的字符串列表就是Python 內部實際的搜索路徑。導入時,Python 會由左至右搜索這個列表。

sys.path 是模塊搜索的路徑。Python 在程序啟動時進行配置,自動將頂級文件的主目錄(或者指定當前工作目錄的一個空字符串)、任何 PYTHONPATH 目錄、標準庫目錄,以及已經創建的任何 .pth 文件路徑的內容合并。得到一個 Python 在每次導入一個新文件的時候查找的目錄名的字符串列表。這個 sys.path 列表可以幫你確認你所做的搜索路徑的設置值:如果在列表中看不到設置值,就需要重新檢查你的設置。

如果你知道在做什么,這個列表也提供一種方式,讓腳本手動調整其搜索路徑。通過修改 sys.path 這個列表,你可以修改將來的導入的搜索路徑。一旦做了這類修改,就會對 Python 程序中將要導入的地方產生影響,因為所有導入和文件都共享了同一個sys.path 列表。因此可以使用這個技巧,在 Python 程序中動態配置搜索路徑。不過要小心:如果從路徑中刪除了重要目錄,就無法獲取一些關鍵的工具了。

sys.path 的設置方法只在修改的 Python 會話或程序(即進程)中才會存續。在Python 程序結束后,不會保留下來。PYTHONPATH 和 .pth 文件路徑配置時保存在操作系統中,而不是執行的 Python 程序中。PYTHONPATH 和 .pth 文件提供了更改持久的路徑修改方法。因此使用這種配置方法更全局一些:機器上的每個程序都會去查找PYTHONPATH 和 .pth,而且在程序結束后,他們還存在著。

修改完成后,你就可以加載自己的模塊了。 只要這個列表中的某個目錄包含這個文件, 它就會被正確導入。 當然, 這個方法是把目錄追加在搜索路徑的尾部。 如果你有特殊需要, 那么應該使用列表的 insert() 方法操作 ,把目錄追加在搜索路徑的首部。


6、模塊文件選擇

文件名的后綴(例如.py)是刻意從 import 語句中省略的。Python 會選擇搜索路徑中第一個符合導入文件名的文件。

  • 源代碼文件:b.py
  • 字節碼文件:b.pyc
  • 目錄:b,包導入
  • 編譯擴展模塊(通常是C或C++編寫),導入時使用動態連接
  • 用 C 編寫的編譯好的內置模塊,并通過靜態連接至 Python。
  • ZIP 文件組件,導入時會自動解壓縮
  • 內存映像,對于 frozen 可執行文件。

如果在不同目錄中有 b.py 和 b.so,Python 總是在由左至右搜索 sys.path 時,加載模塊搜索路徑那些目錄中最先出現(最左邊的)相符文件。但是,如果實在相同的目錄中找到這兩個文件,Python會遵循一個標準的調訓順序,不過這種順序不保證永遠保持不變,通常來說,你不應該依賴 Python 會在給定的目錄中選擇何種的文件類型:讓模塊名獨特一些,或者設置模塊搜索路徑,讓模塊選擇的特性更明確一些。


五、模塊的高級概念

1、核心風格: 導入語句的位置

推薦所有的模塊在 Python 模塊的開頭部分導入。 而且最好按照這樣的順序:

  • Python 標準庫模塊
  • Python 第三方模塊
  • 應用程序自定義模塊

然后使用一個空行分割這三類模塊的導入語句。 這將確保模塊使用固定的習慣導入,有助于減少每個模塊需要的 import 語句數目。

2、從 ZIP 文件中導入模塊

在 2.3 版中,Python 加入了從 ZIP 歸檔文件導入模塊的功能。 如果你的搜索路徑中存在一個包含 Python 模塊(.py, .pyc, or .pyo 文件)的 .zip 文件,導入時會把 ZIP 文件當作目錄處理,在文件中搜索模塊。
如果要導入的一個 ZIP 文件只包含 .py 文件, 那么 Python 不會為其添加對應的 .pyc 文件,這意味著如果一個 ZIP 歸檔沒有匹配的 .pyc 文件時,導入速度會相對慢一點。

3、用字符串格式的模塊名導入模塊

一條 import 或 from 語句中的模塊名是直接編寫的變量名稱。然而,有時候,我們的程序可以在運行時以一個字符串的形式獲取要導入的模塊的名稱。但是我們無法使用import 語句來直接載入以字符串形式給出其名稱的一個模塊,Python 期待一個變量名,而不是字符串。

為了解決這個問題,我們需要使用特殊的工具,從運行時生成的一個字符串來動態地載入一個模塊。最通用的方法是,把一條導入語句構建為 Python 代碼的一個字符串,并且將其傳遞給 exec() 內置函數以運行。

exec() 函數編譯一個代碼字符串,并且將其傳給 Python 解釋器以執行。exec() 唯一的缺點就是,每次運行時它必須編譯 import 語句。如果需要運行很多次,使用內置的 __import__ 函數來從一個字符串名稱載入,代碼會運行的更快。


4、__name__ 和 __main__

每個模塊都有個 __name__ 的內置屬性,Python 會自動設置該屬性:

如果文件是以頂層程序文件執行,在啟動時,__name__ 就會設置為字符串 "__main__" 。

如果文件被導入,__name__ 就會被設成模塊名。

結果就是模塊可以檢測自己的 __name__ 變量來確定它是在執行還是再導入。使用 __name__ 變量最常見的就是編寫測試代碼。簡而言之,可以在文件末尾加個 __name__ 判斷語句,把測試代碼放到判斷語句中。

編寫既可以作為命名行工具也可以作為工具庫使用的文件時,__name__ 技巧也很好用。


5、模塊設計理念

就像函數一樣,模塊也有設計方面的折中考慮:需要思考哪些函數和類要放進模塊以及模塊通信機制等。

總是在Python的模塊內編寫代碼:

沒有辦法寫出不在某個模塊之內的程序代碼。事實上,在交互模式提示符下輸入的程序代碼,其實是存在于內置模塊 __main__ 之內。交互模式提示符獨特之處就在于程序是執行后就立刻丟棄,以及表達式結果是自動打印的。

加載模塊會導致這個模塊被"執行"。 也就是被導入模塊的頂層代碼將直接被執行。 這通常包括設定全局變量以及類和函數的聲明。 如果有檢查 __name__ 的操作,那么它也會被執行。
當然,這樣的執行可能不是我們想要的結果。 你應該把盡可能多的代碼封裝到函數。 明確地說,只把函數和類定義放入模塊的頂層是良好的模塊編程習慣。

模塊耦合要降到最低:

我們應該盡量的最小化模塊間的耦合性。

模塊應該少去修改其他模塊的變量:

使用另一個模塊定義的全局變量,這完全是可以的(畢竟這就是客戶端導入服務的方式),但是,修改另一個模塊內的全局變量,通常是出現設計問題的征兆。


6、頂層代碼語句次序的重要性

當模塊首次導入(或重載)時,Python會從頭到尾的執行語句。在導入時,模塊文件頂層的程序代碼(不在函數內)就會被執行。因此,該語句是無法引用文件后面位置賦值的變量名。位于函數主體內的代碼直到函數被調用后才會運行。因為函數內的變量名在函數實際執行前都不會解析,通常可以引用文件內任意地方的變量。


《Python基礎手冊》系列:

Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數的定義
Python基礎手冊22 —— 函數的參數
Python基礎手冊23 —— 函數的調用
Python基礎手冊24 —— 函數中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一、模塊 1、模塊和導入 當程序代碼量變得相當大、邏輯結構變得非常復雜的時候,我們最好把代碼按照邏輯和功能劃分成一...
    常大鵬閱讀 3,025評論 0 9
  • 定義類并創建實例 在Python中,類通過 class 關鍵字定義。以 Person 為例,定義一個Person類...
    績重KF閱讀 3,971評論 0 13
  • 1.雙引號“”:完全匹配搜索 2.減號-:搜索不包含減號后面的詞的頁面。使用這個指令時減號前面必須是空格,減號后面...
    StormZang閱讀 422評論 0 2
  • 安慰人的時候說的最多的一句話是 交給時間吧,總會過去的 可是只有過去了的人才會知道 永遠都不會過去的 它是一根刺,...
    山名無山閱讀 172評論 0 0
  • 由繁化簡,歸納一個字。 由簡出繁,訴說一篇章。 簡簡繁繁,繁繁簡練, 這就是書,簡繁書也!
    喃喃涅閱讀 268評論 0 3