例子引入
例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/env python # encoding: utf-8 def func1(): x = 1 print globals() print 'before func1:', locals() def func2(): a = 1 print 'before fun2:', locals() a += x print 'after fun2:', locals() func2() print 'after func1:', locals() print globals() if __name__ == '__main__': func1() |
可以正常輸出結果: 並且需要注意,在func2
使用x
變數之前的名字空間就已經有了'x':1
.
1 2 3 4 |
before func1: {'x': 1} before fun2: {'a': 1, 'x': 1} after fun2: {'a': 2, 'x': 1} after func1: {'x': 1, 'func2': <function func2 at 0x7f7c89700b90>} |
稍微改一點:如下
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python # encoding: utf-8 def func1(): x = 1 print 'before func1:', locals() def func2(): print 'before fun2:', locals() x += x #就是這裡使用x其餘地方不變 print 'after fun2:', locals() func2() print 'after func1:', locals() if __name__ == '__main__': func1() |
輸出就開始報錯: 而且在before func2
也沒有了x
.
1 2 3 4 5 6 7 8 9 10 |
before func1: {'x': 1} before fun2: {} Traceback (most recent call last): File "test.py", line 18, in <module> func1() File "test.py", line 14, in func1 func2() File "test.py", line 11, in func2 x += x UnboundLocalError: local variable 'x' referenced before assignment |
這兩個例子正好涉及到了python
裡面最核心的內容:名字空間,正好總結一下,然後在解釋這幾個例子。
名字空間(Namespace)
比如我們定義一個”變數”
1 2 |
In [14]: a NameError: name 'a' is not defined |
所以,這裡更準確的叫法應該是名字
。 一些語言中比如c,c++,java
變數名是記憶體地址別名, 而Python 的名字就是一個字串,它與所指向的目標物件關聯構成名字空間裡面的一個鍵值對{name: object}
,因此可以這麼說,python的名字空間
就是一個字典.。
分類
python裡面有很多名字空間,每個地方都有自己的名字空間,互不干擾,不同空間中的兩個相同名字的變數之間沒有任何聯絡一般有4種:LEGB
四種
locals
: 函式內部的名字空間,一般包括函式的區域性變數以及形式引數enclosing function
: 在巢狀函式中外部函式的名字空間, 對fun2
來說,fun1
的名字空間就是。globals
: 當前的模組空間,模組就是一些py
檔案。也就是說,globals()類似全域性變數。__builtins__
: 內建模組空間, 也就是內建變數或者內建函式的名字空間。
當程式引用某個變數的名字時,就會從當前名字空間開始搜尋。搜尋順序規則便是: LEGB
.
1 |
locals -> enclosing function -> globals -> __builtins |
一層一層的查詢,找到了之後,便停止搜尋,如果最後沒有找到,則丟擲在NameError
的異常。這裡暫時先不討論賦值
操作。
比如例1中的a = x + 1
這行程式碼,需要引用x
, 則按照LEGB
的順序查詢,locals()也就是func2
的名字空間沒有,進而開始E
,也就是func1
,裡面有,找到了,停止搜尋,還有後續工作,就是把x
也加到自己的名字空間,這也是為什麼fun2
的名字空間裡面也有x
的原因。
訪問方式
其實上面都已經說了,這裡暫時簡單列一下
- 使用
locals()
訪問區域性名稱空間 - 使用
globals()
訪問全域性名稱空間
這裡有一點需要注意,就是涉及到了from A import B
和import A
的一點區別。
1234567891011#!/usr/bin/env python# encoding: utf-8import copyfrom copy import deepcopydef func():x = 123print 'func locals:',locals()s = 'hello world'if __name__ == '__main__':func()print 'globals:', globals()
輸出結果:
1 2 3 4 5 6 7 8 9 10 |
func locals: {'x': 123} globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__': 'test.py', '__package__': None, 's': 'hello world', 'func': <function func at 0x7f1c3d617c80>, 'deepcopy': <function deepcopy at 0x7f1c3d6177d0>, '__name__': '__main__', 'copy': <module 'copy' from '/usr/lib/python2.7/copy.pyc'>, '__doc__': None} |
從輸出結果可以看出globals()
包含了定義的函式,變數等。對於'deepcopy': <function deepcopy at 0x7f1c3d6177d0>
可以看出deepcopy
已經被匯入到自己的名字空間了,而不是在copy
裡面。 而匯入的import copy
則還保留著自身的名字空間。因此要訪問copy
的方法,就需要使用copy.function
了。這也就是為什麼推薦使用import module
的原因,因為from A import B
這樣會把B
引入自身的名字空間,容易發生覆蓋或者說汙染。
生存週期
每個名字空間都有自己的生存週期,如下:
__builtins__
: 在python
直譯器啟動的時候,便已經建立,直到退出globals
: 在模組定義被讀入時建立,通常也一直儲存到直譯器退出。locals
: 在函式呼叫時建立, 直到函式返回,或者丟擲異常之後,銷燬。 另外遞迴函式每一次均有自己的名字空間。
看著沒有問題,但是有很多地方需要考慮。比如名字空間都是在程式碼編譯時期確定的,而不是執行期間。這個也就可以解釋為什麼在例1中,before func2:
的locals()裡面包含了x: 1
這一項。再看下面這個,
1 2 3 4 |
def func(): if False: x = 10 #該語句永遠不執行 print x |
肯定會報錯的,但是錯誤不是
1 |
NameError: global name 'x' is not defined |
而是:
1 |
UnboundLocalError: local variable 'x' referenced before assignment |
雖然x = 10
永遠不會執行,但是在執行之前的編譯階段,就會把x
作為locals
變數,但是後面編譯到print
的時候,發現沒有賦值,因此直接丟擲異常,locals()
裡面便不會有x
。這個就跟例子2中,before func2
裡面沒有x
是一個道理。
賦值
為什麼要把賦值單獨列出來呢,因為賦值操作對名字空間的影響很大,而且很多地方需要注意。
核心就是: 賦值修改的是名稱空間,而不是物件, 比如:
1 |
a = 10 |
這個語句就是把a
放入到了對應的名稱空間, 然後讓它指向一個值為10的整數物件。
1 2 |
a = [] a.append(1) |
這個就是把a
放入到名字空間,然後指向一個列表物件, 然而後面的a.append(1)
這句話只是修改了list
的內容,並沒有修改它的記憶體地址。因此
並沒有涉及到修改名字空間。
賦值操作有個特點就是: 賦值操作總是在最裡層的作用域.也就說,只要編譯到了有賦值操作,就會在當前名字空間內新建立一個名字,然後開始才繫結物件。即便該名字已存在於賦值語句發生的上一層作用域中;
總結
分析例子
現在再看例子2, 就清晰多了, x += x
編譯到這裡時,發現了賦值語句,於是準備把x
新加入最內層名字空間也就是func2
中,即使上層函式已經存在了; 但是賦值的時候,又要用到x
的值, 然後就會報錯:
1 |
UnboundLocalError: local variable 'x' referenced before assignment |
這樣看起來好像就是 內部函式只可以讀取外部函式的變數,而不能做修改,其實本質還是因為賦值
涉及到了新建locals()
的名字。
在稍微改一點:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python # encoding: utf-8 def func1(): x = [1,2] print 'before func1:', locals() def func2(): print 'before fun2:', locals() x[0] += x[0] #就是這裡使用x[0]其餘地方不變 print 'after fun2:', locals() func2() print 'after func1:', locals() if __name__ == '__main__': func1() |
這個結果就是:
1 2 3 4 |
before func1: {'x': [1, 2]} before fun2: {'x': [1, 2]} after fun2: {'x': [2, 2]} after func1: {'x': [2, 2], 'func2': <function func2 at 0x7fb67b253b18>} |
咋正確了呢—這不應該要報錯嗎? 其實不然,就跟上面的a.append(1)
是一個道理。
x[0] += x[0]
這個並不是對x
的賦值操作。按照LEGB
原則, 搜到func1
有變數x
並且是個list
, 然後將其加入到自己的locals()
, 後面的x[0] += x[0]
, 就開始讀取x
的元素,並沒有影響func2
的名字空間。另外無論func1
與func2
的名字空間的x
沒有什麼關係,只不過都是對[1, 2]
這個列表物件的一個引用。
這個例子其實也給了我們一個啟發,我們知道內部函式無法直接修改外部函式的變數值,如例2,如果藉助list
的話, 就可以了吧!比如把想要修改的變數塞到一個list
裡面,然後在內部函式裡面做改變!當然python3.x
裡面有了nonlocal
關鍵字,直接宣告一下就可以修改了。看到這裡,對作用域理解應該有一點點了吧。
延伸
與閉包的不同
我們都知道閉包是把外部函式的值放到func.func_closure
裡面,為什麼不像上面的例子一樣直接放到函式的名字空間呢?
這是因為locals()
空間是在函式呼叫的時候才建立! 而閉包只是返回了一個函式, 並沒有呼叫,也就沒有所謂的空間。
locals()與globals()
在最外層的模組空間裡locals()
就是globals()
1 2 |
In [2]: locals() is globals() Out[2]: True |
另外我們可以手動修改globals()
來建立名字
1 2 3 |
In [3]: globals()['a'] = 'abcde' In [4]: a Out[4]: 'abcde' |
但是locals()
在函式裡面的話, 貌似是不起作用的,如下:
1 2 3 4 5 6 7 8 9 10 |
In [5]: def func(): ...: x = 10 ...: print x ...: print locals() ...: locals()['x'] = 20 ...: print x In [6]: func() 10 {'x': 10} 10 |
這是因為直譯器會將 locals 名字複製到 一個叫FAST
的 區域來優化訪問速度,而實際上直譯器訪問物件時,是從FAST
區域裡面讀取的,而非locals()
。所以直接修改locals()
並不能影響x
(可以使用exec
動態訪問,不再細述)。 不過賦值操作,會同時重新整理locals()
和FAST
區域。
查了不少資料,說了這麼多,我只想說,作為python
最核心的東西,名字空間還有很多很多地方需要探究,比如
- 作用域(scope)與名字空間, 這裡只是模糊了二者的區別
- 物件導向,也就是類的名字空間, 又有不一樣的地方。。。
學一點記錄一點吧。