Python 命名空間和做用域

本文說下本身對 Python 命名空間和做用域的理解。html

注意:內容基於 Python 3.6python

命名空間

A namespace is a mapping from names to objects.後端

命名空間,直譯是名稱到對象(好比數字、字符串等)的映射,(個人理解是)這些名稱構成一個命名空間。通常有三種命名空間bash

  • 內置名稱(built-in names), Python 語言內置的名稱,好比函數名 abs、char 和異常名稱 BaseException、Exception 等等。
  • 全局名稱(global names),模塊中定義的名稱。
  • 局部名稱(local names),函數中定義的名稱。(類中定義的也是)

看個例子就清楚了app

# A 是全局名稱, object 是內置名稱
class A(object):
    a = -1  # a 是局部名稱(類中)

    # print_abs_a 是局部名稱(類中), self 是局部名稱(函數中)
    def print_abs_a(self):
        # temp_str 是局部名稱(函數中), abs 是內置名稱
        temp_str = '%s 的絕對值是 %s' % (self.a, abs(self.a))
        print(temp_str)  # print 是內置名稱

t = A()  # t 是全局名稱
複製代碼

命名空間的生命週期各不相同函數

  • 內置命名空間在編譯器啓動開始創建,直到程序結束
  • 全局命名空間,在模塊文件讀入後創建,直到程序結束。
  • 局部命名空間,在函數被調用後創建,函數退出時結束。遞歸函數每次調用都會創建不一樣的命名空間。對於類來講相似,實例化後創建,實例銷燬後結束。

做用域

A scope is a textual region of a Python program where a namespace is directly accessible. 「Directly accessible」 here means that an unqualified reference to a name attempts to find the name in the namespace.ui

做用域,是 Python 代碼中的一段文本區域,在這個區域裏能「直接」訪問一個命名空間中的名稱。所謂「直接」,就是隻要給出名稱(如 some_name)就能找到命名空間中的對應的名稱,而不須要使用相似 modulename.subname 或是 object.attribute 等這樣的方式。spa

有四種做用域:code

  • local scope,最內層,包含 local names,(搜索名稱時)最早被搜索
  • nonlocal scope, 若是一個函數(或類) A 裏面又包含了一個函數 B ,那麼對於 B 中的名稱來講 A 中的做用域就爲 nonlocal
  • global scope,包含 global names
  • builtin scope, 包含 built-in names,最後被搜索

其中,須要強調的是 local scope 和 nonlocal scope 是一個相對的概念。若是一個模塊中,函數 A 直接包含了函數 B , B 又直接包含了函數 C 。若是以 C 中的名稱做爲參考,那麼 C 中的做用域爲 local scope,則 B 中的做用域就爲 nonlocal scope。若是以 B 中的名稱做爲參考,那麼 B 中的做用域是 local scope, 則 A 中的做用域爲 nonlocal。若是以 A 中的名稱做爲參考,那麼 A 中的做用域是 local scope,不過要注意,模塊中的做用域始終爲 global scope,這時並無 nonlocal scope。htm

對於賦值操做,默認都是操做當前做用域中包含的名稱。假設如今在 local scope,若是要對 nonlocal scope 包含的名稱進行賦值,則要用 nonlocal 關鍵字。若是要對 global scope 中包含的名稱賦值要用 global 關鍵字。須要注意的是,若是在某個做用域內沒有對應的名稱,則在對應的做用域中會新增。 下面的例子能夠幫你理解賦值操做

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)

scope_test()
print("In global scope:", spam)
複製代碼

輸出爲

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
複製代碼

對應讀值操做,都是由內到外進行搜索。即 local scope -> nonlocal scope -> global scope -> builtin scope,若是都找不到對應的名稱,則報錯。

>>> x = 1
>>> def t():
...     def tt():
...         print(x)
...     tt()
...
>>> t()
1
>>> del x
>>> t()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in t
  File "<stdin>", line 3, in tt
NameError: name 'x' is not defined
複製代碼

若是要讀取指定做用域的名稱,則可使用對應的 nonlocal 或 global 關鍵字,若是對應做用域找不到該名稱,則直接報錯。

>>> x = 1
>>> def t():
...     x = 2
...     def tt():
...         global x
...         print(x)
...     tt()
...
>>> t()
1
>>> del x
>>> t()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in t
  File "<stdin>", line 5, in tt
NameError: name 'x' is not defined
複製代碼

固然,若是當前做用域已有同名的名稱,就不能使用這 nonlocal 或 global 了,不然會報錯。

>>> x = 1
>>> def t():
...     x = 2
...     global x
...
  File "<stdin>", line 3
SyntaxError: name 'x' is assigned to before global declaration
複製代碼

同時發現個有趣的地方

>>> def t():
...     def tt():
...         nonlocal x
...         print(x)
...     tt()
...
  File "<stdin>", line 3
SyntaxError: no binding for nonlocal 'x' found

>>> def t():
...     def tt():
...         global x
...         print(x)
...     tt()
...
>>> t()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in t
  File "<stdin>", line 4, in tt
NameError: name 'x' is not defined
複製代碼

第一個報的是語法錯誤,而第二個是運行時報的錯誤。這說明了一個問題:局部名稱的查找是編譯時就肯定的,而全局名稱和內置名稱的查找都是在運行時肯定的。(這裏只是指出來,瞭解下就行,暫時不必深刻)

小結

我的以爲,不必太在乎命名空間和做用域的定義,之因此有命名空間的說法,只是爲了引入做用域的概念。

咱們只須要清楚兩個方面的內容:一是,哪一個做用域包含哪些名稱;二是,相反的,賦值和讀值的時候它又是指向哪一個做用域,並理解 nonlocal 和 global 的使用。

參考

https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces
複製代碼

本文首發於公衆號「小小後端」。

相關文章
相關標籤/搜索