天天看點

python子產品子產品子產品和檔案命名空間導入子產品子產品導入的特性相關内建函數包

子產品

       子產品支援從邏輯上組織 Python 代碼。 當代碼量變得相當大的時候, 我們最好把代碼分成一些有組織的代碼段,前提是保證它們的彼此互動。 這些代碼片段互相間有一定的聯系, 可能是一個包含資料成員和方法的類, 也可能是一組相關但彼此獨立的操作函數。 這些代碼段是共享的,是以Python 允許 “調入” 一個子產品, 允許使用其他子產品的屬性來利用之前的工作成果, 實作代碼重用.這個把其他子產品中屬性附加到你的子產品中的操作叫做導入(import) 。那些自我包含并且有組織的代碼片斷就是子產品。

子產品和檔案

       如果說子產品是按照邏輯來組織 Python 代碼的方法, 那麼檔案便是實體層上組織子產品的方法。是以, 一個檔案被看作是一個獨立子產品, 一個子產品也可以被看作是一個檔案。 子產品的檔案名就是子產品的名字加上擴充名 .py 。這裡我們需要讨論一些關于子產品檔案結構的問題。 與其它可以導入類(class)的語言不同,在 Python 中你導入的是子產品或子產品屬性。

搜尋路徑和路徑搜尋

       子產品的導入需要一個叫做”路徑搜尋”的過程。 即在檔案系統”預定義區域”中查找 mymodule.py檔案(如果你導入 mymodule 的話)。 這些預定義區域隻不過是你的 Python 搜尋路徑的集合。路徑搜尋和搜尋路徑是兩個不同的概念, 前者是指查找某個檔案的操作, 後者是去查找一組目錄。 有時候導入子產品操作會失敗:

>>> import xxx
Traceback (innermost last):
File "<interactive input>", line , in ?
ImportError: No module named xxx
           

發生這樣的錯誤時, 解釋器會告訴你它無法通路請求的子產品, 可能的原因是子產品不在搜尋路徑裡, 進而導緻了路徑搜尋的失敗。預設搜尋路徑是在編譯或是安裝時指定的。 它可以通過以下方式修改:

1.一個是啟動 Python 的 shell 或指令行的 PYTHONPATH 環境變量。 該變量的内容是一組用冒号分割的目錄路徑。 如果你想讓解釋器使用這個變量, 那麼請確定在啟動解釋器或執行 Python 腳本前設定或修改了該變量。

2.解釋器啟動之後, 也可以通路這個搜尋路徑, 它會被儲存在 sys 子產品的 sys.path 變量裡。不過它已經不是冒号分割的字元串, 而是包含每個獨立路徑的清單。我們随時對這個清單進行修改,隻需要調用清單的 append() 方法即可:

>>> sys.path
['', 'C:\\Windows\\SYSTEM32\\python34.zip', 'F:\\Program Files\\python34\\DLLs', 'F:\\Program Files\\python34\\lib', 'F:\\Program Files\\python34', 'F:\\Program Files\\python34\\lib\\site-packages']
>>>sys.path.append('F:\\mylib')
>>>sys.path
['', 'C:\\Windows\\SYSTEM32\\python34.zip', 'F:\\Program Files\\python34\\DLLs', 'F:\\Program Files\\python34\\lib', 'F:\\Program Files\\python34', 'F:\\Program Files\\python34\\lib\\site-packages', 'F:\\mylib']
           

修改完成後, 你就可以加載自己的子產品了。 隻要這個清單中的某個目錄包含這個檔案, 它就會被正确導入。 當然, 這個方法是把目錄追加在搜尋路徑的尾部。 如果你有特殊需要, 可以使用清單的 insert() 方法操作 。

注意:你可能在多個路徑下有多個同名的子產品,這時,解釋器會使用沿搜尋路徑順序找到的第一個子產品。

使用 sys.modules 可以找到目前導入了哪些子產品和它們來自什麼地方。 和 sys.path 不同,sys.modules 是一個字典, 使用子產品名作為鍵( key) , 對應實體位址作為值( value )。

命名空間

       名稱空間是名稱(辨別符)到對象的映射。 向名稱空間添加名稱的操作過程涉及到綁定辨別符到指定對象的操作(以及給該對象的引用計數加 1 )。《Python 語言參考》(Python Language Reference)有如下的定義: 改變一個名字的綁定叫做重新綁定, 删除一個名字叫做解除綁定。

       python程式在執行期間有兩個或三個活動的名稱空間。 這三個名稱空間分别是局部名稱空間, 全局名稱空間和内建名稱空間, 但局部名稱空間在執行期間是不斷變化的, 是以我們說”兩個或三個”。 從名稱空間中通路這些名字依賴于它們的加載順序, 或是系統加載這些名稱空間的順序。

       Python 解釋器首先加載内建名稱空間。 它由 builtins 子產品中的名字構成。 随後加載執行子產品的全局名稱空間, 它會在子產品開始執行後變為活動名稱空間。 這樣我們就有了兩個活動的名稱空間。如果在執行期間調用了一個函數, 那麼将建立出第三個名稱空間, 即局部名稱空間。 我們可以通過 globals() 和 locals() 内建函數判斷出某一名字屬于哪個名稱空間。

名稱查找與變量作用域

名稱空間是純粹意義上的名字和對象間的映射關系, 而作用域還指出了從使用者代碼的哪些實體位置可以通路到這些名字。通路一個屬性時, 解釋器必須在三個名稱空間中的一個找到它。 首先從局部名稱空間開始, 如果沒有找到, 解釋器将繼續查找全局名稱空間. 如果這也失敗了, 它将在内建名稱空間裡查找。 如果最後的嘗試也失敗了, 你會得到這樣的錯誤:

>>> xxx
Traceback (most recent call last):
  File "<stdin>", line , in <module>
NameError: name 'xxx' is not defined
           

局部名稱空間中的名字會隐藏全局或内建名稱空間的對應對象。 這就相當于”覆寫”了那個全局變量。

>>> def f():
...     a=
...     print(a)
...
>>> a=
>>> f()

           

無限制名稱空間

       Python 的一個有用的特性在于你可以在任何需要放置資料的地方獲得一個名稱空間。你可以把任何想要的東西放入一個名稱空間裡。

>>> def f():                   
...     pass                   
...                           
>>> f.version=                
>>> f.name="f"                 
>>> print(f.version,f.name)    
 f                            
           

導入子產品

import語句

       使用 import 語句導入子產品, 它的文法如下所示:

import module1
import module2
:
import moduleN
           

也可以在一行内導入多個子產品,但是這樣的代碼可讀性不如多行的導入語句,最好一行隻導入一個子產品。

import module1,module2,module3,module4
           

導入子產品的最佳順序:Python 标準庫子產品、Python 第三方子產品、應用程式自定義子產品,解釋器執行到這條語句, 如果在搜尋路徑中找到了指定的子產品, 就會加載它。該過程遵循作用域原則, 如果在一個子產品的頂層導入, 那麼它的作用域就是全局的; 如果在函數中導入, 那麼它的作用域是局部的。如果子產品是被第一次導入, 它将被加載并執行。

from-import 語句

可以在子產品裡導入指定的子產品屬性。 也就是把指定名稱導入到目前作用域。 使用from-import 語句可以實作我們的目的, 文法格式:

from module import name1, name2,... nameN
           

之後使用導入的屬性就不必加子產品名了:

>>> from os import system
>>> system("ls")
           

子產品導入的特性

加載子產品時執行子產品

       加載子產品會導緻這個子產品被”執行”。 也就是被導入子產品的頂層代碼将直接被執行。 這通常包括設定全局變量以及類和函數的聲明。 如果有檢查 name 的操作, 那麼它也會被執行。一個子產品隻被加載一次, 無論它被導入多少次。 這可以阻止多重導入時代碼被多次執行。 例如你的子產品導入了 sys 子產品, 而你要導入的其他 5 個子產品也導入了它, 那麼每次都加載 sys (或是其他子產品)不是明智之舉! 是以, 加載隻在第一次導入時發生。

相關内建函數

globals() 和 locals() 内建函數

       globals() 和 locals() 内建函數分别傳回調用者全局和局部名稱空間的字典。 在一個函數内部, 局部名稱空間代表在函數執行時候定義的所有名字, locals() 函數傳回的就是包含這些名字的字典。 globals() 會傳回函數可通路的全局名字。在全局名稱空間下, globals() 和 locals() 傳回相同的字典, 因為這時的局部名稱空間就是全局空間。

reload()内建函數

reload() 内建函數可以重新導入一個已經導入的子產品。 它的文法如下: reload(module)

module 是你想要重新導入的子產品。使用 reload() 的時候有一些标準。 首先子產品必須是全部導入(不是使用 from-import), 而且它必須被成功導入。另外 reload() 函數的參數必須是子產品自身而不是包含子產品名的字元串。 也就是說必須類似 reload(sys) 而不是 reload(‘sys’)。子產品中的代碼在導入時被執行, 但隻執行一次. 以後執行 import 語句不會再次執行這些代碼,隻是綁定子產品名稱。 而 reload() 函數不同,每次調用reload() 函數,子產品中的代碼都會被執行。

       包是一個有層次的檔案目錄結構, 它定義了一個由子產品和子包組成的 Python 應用程式執行環境。Python 1.5 加入了包, 用來幫助解決如下問題:

1、為平坦的名稱空間加入有層次的組織結構

2、允許程式員把有聯系的子產品組合到一起

3、允許分發者使用目錄結構而不是一大堆混亂的檔案

3、幫助解決有沖突的子產品名稱

與類和子產品相同, 包也使用句點屬性辨別來通路他們的元素。 使用标準的 import 和from-import 語句導入包中的子產品。

目錄結構

假定我們的包有如下的目錄結構:

Phone/
        __init__.py
        common_util.py
        Voicedta/
                   __init__.py
                   Pots.py
                   Isdn.py
        Fax/
                   __init__.py
                   G3.py
        Mobile/
                   __init__.py
                   Analog.py
                   igital.py
        Pager/
                   __init__.py
                   Numeric.py
           

Phone 是最頂層的包, Voicedta 等是它的子包。 我們可以這樣導入子包:

import Phone.Mobile.Analog
Phone.Mobile.Analog.dial()
           

也可使用 from-import 導入子包:

注意:如果是較新版本的python,下列導入會有問題,詳見下面絕對導入和相對導入

from Phone import Mobile
from Phone.Mobile import Analog
Mobile.Analog.dial('555-1212')
Analog.dial('555-1212')
           

在我們上邊的目錄結構中, 我們可以發現很多的 init.py 檔案。 這些是初始化子產品,from-import 語句導入子包時需要用到它。 如果沒有用到, 他們可以是空檔案。 程式員經常忘記為 它 們 的 包 目 錄 加 入 init.py 文 件 , 所 以 從 Python 2.5 開 始 , 這 将 會 導 緻 一 個ImportWarning 資訊。

絕對導入

包的使用越來越廣泛, 很多情況下導入子包會導緻和真正的标準庫子產品發生(事實上是它們的名字)沖突。 包子產品會把名字相同的标準庫子產品隐藏掉, 因為它首先在包内執行相對導入, 隐藏掉标準庫子產品。為 此 , 所 有 的 導 入 現 在 都 被 認 為 是 絕 對 的 , 也 就 是 說 這 些 名 字 必 須 通 過 Python 路 徑(sys.path 或是 PYTHONPATH )來通路。這個決定的基本原理是子包也可以通過 sys.path 通路, 例如 import Phone.Mobile.Analog ,如果 Mobile不在Python 路 徑中,從 Mobile 子包内子產品中導入 Analog就會出問題。作為一個折中方案, Python 允許通過在子產品或包名稱前置句點實作相對導入。

相對導入

絕對導入特性限制了子產品作者的一些特權。失去了 import 語句的自由, 必須有新的特性來滿足程式員的需求。于是就有了相對導入。 相對導入特性稍微地改變了 import 文法, 讓程式員告訴導入者在子包的哪裡查找某個子產品。因為 import 語句總是絕對導入的, 是以相對導入隻應用于 from-import 語句。文法的第一部分是一個句點, 訓示一個相對的導入操作。 之後的其他附加句點代表目前 from起始查找位置後的一個級别。

from Phone.Mobile.Analog import dial
from .Analog import dial
from ..common_util import setup
from ..Fax import G3.dial.