Python命名空間和作用域之global和nonlocal初理解

 

一、命名空間:

1.命名空間 是從命名到對象的映射

2.當前命名空間主要是通過 Python 字典實現的,鍵爲變量名,值是變量對應的值。 

3.關於命名空間需要了解的一件很重要的事就是不同命名空間中的命名沒有任何聯繫,例如兩個不同的模塊可能都會定義一個名爲 maximize的函數而不會發生混淆-用戶必須以模塊名爲前綴來引用它們。

4. 一個命名空間中不能有重名,但是不同的命名空間可以重名而沒有任何影響。

5.分類

python程序執行期間會有2個或3個活動的命名空間(函數調用時有3個,函數調用結束後2個)。按照變量定義的位置,可以劃分爲以下3類:

    Local,局部命名空間,每個函數所擁有的命名空間,記錄了函數中定義的所有變量,包括函數的入參、內部定義的局部變量。

    Global,全局命名空間,每個模塊加載執行時創建的,記錄了模塊中定義的變量,包括模塊中定義的函數、類、其他導入的模塊、模塊級的變量與常量。

    Built-in,python自帶的內建命名空間,任何模塊均可以訪問,放着內置的函數和異常。

6.生命週期

    Local(局部命名空間)在函數被調用時才被創建,但函數返回結果或拋出異常時被刪除。(每一個遞歸函數都擁有自己的命名空間)。

    Global(全局命名空間)在模塊被加載時創建,通常一直保留直到python解釋器退出。

    Built-in(內建命名空間)在python解釋器啓動時創建,一直保留直到解釋器退出。

 

各命名空間創建順序:

python解釋器啓動 ->創建內建命名空間 -> 加載模塊 -> 創建全局命名空間 ->函數被調用 ->創建局部命名空間

各命名空間銷燬順序:

函數調用結束 -> 銷燬函數對應的局部命名空間 -> python虛擬機(解釋器)退出 ->銷燬全局命名空間 ->銷燬內建命名空間

    python解釋器加載階段會創建出內建命名空間、模塊的全局命名空間,局部命名空間是在運行階段函數被調用時動態創建出來的,函數調用結束動態的銷燬的。

 

二、作用域:

作用域 就是一個 Python 程序可以直接訪問命名空間的正文區域。這裏的直接訪問意思是一個對名稱的錯誤引用會嘗試在命名空間內查找。儘管作用域是靜態定義,在使用時他們都是動態的。每次執行時,至少有三個命名空間可以直接訪問的作用域嵌套在一起:

  • 包含局部命名的使用域在最裏面,首先被搜索;其次搜索的是中層的作用域,這裏包含了同級的函數;

    最後搜索最外面的作用域,它包含內置命名。

  • 首先搜索最內層的作用域,它包含局部命名任意函數包含的作用域,是內層嵌套作用域搜索起點,包含非局部,但是也非全局的命名

  • 接下來的作用域包含當前模塊的全局命名

  • 最外層的作用域(最後搜索)是包含內置命名的命名空間

作用域是針對變量而言,指申明的變量在程序裏的可應用範圍。或者稱爲變量的可見性。 

【變量查找法則】簡單總結:訪問順序:L->E->G->B 

即 LOCAL(局部作用域)->ENCLOSING(閉包函數外的函數,可理解爲局部外的局部)

->GLOBAL(全局作用域)->Built-in (內置作用域)(內置函數所在模塊的範圍)

 

    在任意一個作用域中找到變量則停止查找。所有作用域查找完成沒有找到對應的變量,則拋出 NameError: name 'xxxx' is not defined的異常。

    在局部作用域中,可以看到局部作用域、嵌套作用域、全局作用域、內建作用域中所有定義的變量。

    在全局作用域中,可以看到全局作用域、內建作用域中的所有定義的變量,無法看到局部作用域中的變量。

 

【分類】

    只有函數、類、模塊會產生作用域,代碼塊不會產生作用域(參考代碼1)。作用域按照變量的定義位置可以劃分爲4類:

    Local(函數內部)局部作用域

    Enclosing(嵌套函數的外層函數內部)嵌套作用域(閉包)

    Global(模塊全局)全局作用域

    Built-in(內建)內建作用域

 

【規則】

    1、靜態作用域規則

         定義:python中變量的作用域是由它在源代碼中的位置決定的。(名字查找是動態發生的)

         說明:參考代碼3/4,以代碼3爲例說明,在模塊中定義了一個全局變量 i = 1,在test方法中執行 i += 1,對變量 i進行了賦值動作,該賦值動作決定了i在test()方法中是一個局部變量, i += 1可以拆分爲兩步執行,首先執行 i + 1, 然後將結果賦值給i。執行i + 1操作時,i雖然申明爲局部變量,但是沒有綁定任何具體值,因此報錯。

    2、最內嵌套作用域規則

         定義:由一個賦值語句引進的名字在這個賦值語句所在的作用域裏是可見(起作用)的,而且在其內部嵌套的每個作用域內也可見,除非它被嵌套於內部的且引進同樣名字的賦值語句所遮蔽。

         說明:參考代碼5. 方法g()是方法f()中定義的內嵌函數。在方法f()中定義的局部變量 i =2,在內嵌方法 g()中是可見的。如果在g()中又定義一個重名的變量 i = 3,則f()中定義的變量將被遮蔽。

 

三、命名空間與作用域的關係

    命名空間定義了在某個作用域內變量名和綁定值之間的對應關係,命名空間是鍵值對的集合,變量名與值是一一對應關係。作用域定義了命名空間中的變量能夠在多大範圍內起作用。

    命名空間在python解釋器中是以字典的形式存在的,是以一種可以看得見摸得着的實體存在的。作用域是python解釋器定義的一種規則,該規則確定了運行時變量查找的順序,是一種形而上的虛的規定。

 

四、一些規則

1.在Python 中,如果沒有使用 global 語法,其賦值操作總是在最裏層的作用域

2.賦值不會複製數據,只是將命名綁定到對象。刪除也是如此del x 只是從局部作用域的命名空間中刪除命名 x 。

3.事實上,所有引入新命名的操作都作用於局部作用域。特別是 import 語句和函數定義將模塊名或函數綁定於局部作用域(可以使用 global 語句將變量引入到全局作用域)。

4.global 語句用以指明某個特定的變量爲全局作用域,並重新綁定它。nonlocal 語句用以指明某個特定的變量爲封閉作用域,並重新綁定它。

四、例子說明

def scope_test():
    def do_local():
        spam = "local spam"
    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"
    def do_global():
        global spam
        spam = "global spam"
    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)
    #spam="test spam2"
    #print(spam)

scope_test()
print("In global scope:", spam)

輸出: 

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
#test spam2
In global scope: global spam

接下來,讓我們一起一步步分析:

首先,spam = "test spam"

          spam綁定的是test spam,作用於scope_test 。

然後,do_local()

           do_local中的spam僅作用於do_local,也沒用global或nonlocal改變其作用域,將scope_test中的spam進行重新綁定。

接着,do_nonlocal()

           do_nonlocal使用了nonlocal改變了位於其命名空間中的spam作用域,由局部變爲閉包函數外的函數,即由do_nonlocal變爲了scope_test 。並通過  spam = "nonlocal spam"  將spam重新綁定了值。即此時do_nonlocal和scope_test 中的spam作用域一樣,綁定的值也一樣(筆者認爲兩個spam此時指向相同,合爲一體。其實在改變作用域的那一刻兩個spam已經是同一個變量)。

再接着,do_global()

            do_global使用global改變了do_global中的spam作用域,但沒改變scope_test 中的spam作用域,同樣的,do_global只是改變了do_global中作用域爲全局的spam所綁定的值,即全局變量spam綁定的值是 "global spam" ,但是閉包函數之外的函數中的spam綁定的值仍然是  "nonlocal spam" ,即scope_test 中的spam綁定的值仍是  "nonlocal spam" 。這就像do_local和scope_test 一樣,在全局中,無法調用函數scope_test中的spam,scope_test中的spam也影響不了全局中的spam,(可以把註釋那一行去掉運行看看)。

         print("After global assignment:", spam)-->此時根據變量查找規則LEGB,首先查找到的是作爲E的閉包函數外的函數中的變量spam,而不是作爲G的全局變量spam,而查找只要查找到了一個結果就會停止接下去的查找,故打印出來的是 "nonlocal_spam"。而在最後全局中調用打印spam時,由於scope_test中的spam只作用於scope_test函數中,故查找到的只有作爲全局變量的spam,其綁定的值就是"global spam"。

 

部分來源於:https://www.cnblogs.com/zhangxinhe/p/6963462.html

http://www.runoob.com/python3/python3-function.html

https://docs.pythontab.com/python/python3.5/classes.html#tut-scopeexample

 

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