摘要:當程序引用某個變量的名字時,就會從當前名字空間開始搜索。對于可以看出已經被導入到自己的名字空間了,而不是在里面。因此并沒有涉及到修改名字空間。按照原則,搜到有變量并且是個然后將其加入到自己的后面的就開始讀取的元素,并沒有影響的名字空間。
前言源自我的博客
python里面最核心的內容就是:名字空間(namespace)
例1
#!/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.
before func1: {"x": 1} before fun2: {"a": 1, "x": 1} after fun2: {"a": 2, "x": 1} after func1: {"x": 1, "func2":}
稍微改一點:如下
例2:
#!/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.
before func1: {"x": 1} before fun2: {} Traceback (most recent call last): File "test.py", line 18, infunc1() 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里面最核心的內容:名字空間,正好總結一下,然后在解釋這幾個例子。
比如我們定義一個"變量"
In [14]: a NameError: name "a" is not defined
所以,這里更準確的叫法應該是名字。 一些語言中比如c,c++,java 變量名是內存地址別名, 而Python 的名字就是一個字符串,它與所指向的目標對象關聯構成名字空間里面的一個鍵值對{name: object},因此可以這么說,python的名字空間就是一個字典.。
分類python里面有很多名字空間,每個地方都有自己的名字空間,互不干擾,不同空間中的兩個相同名字的變量之間沒有任何聯系一般有4種: LEGB四種
locals: 函數內部的名字空間,一般包括函數的局部變量以及形式參數
enclosiing function: 在嵌套函數中外部函數的名字空間, 對fun2來說, fun1的名字空間就是。
globals: 當前的模塊空間,模塊就是一些py文件。也就是說,globals()類似全局變量。
__builtins__: 內置模塊空間, 也就是內置變量或者內置函數的名字空間。
當程序引用某個變量的名字時,就會從當前名字空間開始搜索。搜索順序規則便是: LEGB.
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的一點區別。
#!/usr/bin/env python # encoding: utf-8 import copy from copy import deepcopy def func(): x = 123 print "func locals:",locals() s = "hello world" if __name__ == "__main__": func() print "globals:", globals()
輸出結果:
func locals: {"x": 123} globals: {"__builtins__":, "__file__": "test.py", "__package__": None, "s": "hello world", "func": , "deepcopy": , "__name__": "__main__", "copy": , "__doc__": None}
從輸出結果可以看出globals()包含了定義的函數,變量等。對于 "deepcopy":
每個名字空間都有自己的生存周期,如下:
__builtins__: 在python解釋器啟動的時候,便已經創建,直到退出
globals: 在模塊定義被讀入時創建,通常也一直保存到解釋器退出。
locals : 在函數調用時創建, 直到函數返回,或者拋出異常之后,銷毀?!×硗膺f歸函數每一次均有自己的名字空間。
看著沒有問題,但是有很多地方需要考慮。比如名字空間都是在代碼編譯時期確定的,而不是執行期間。這個也就可以解釋為什么在例1中,before func2:的locals()里面包含了x: 1 這一項。再看下面這個,
def func(): if False: x = 10 #該語句永遠不執行 print x
肯定會報錯的,但是錯誤不是
NameError: global name "x" is not defined
而是:
UnboundLocalError: local variable "x" referenced before assignment
雖然x = 10永遠不會執行,但是在執行之前的編譯階段,就會把x作為locals變量,但是后面編譯到print的時候,發現沒有賦值,因此直接拋出異常,locals()里面便不會有x。這個就跟例子2中,before func2里面沒有x是一個道理。
賦值為什么要把賦值多帶帶列出來呢,因為賦值操作對名字空間的影響很大,而且很多地方需要注意。
核心就是: 賦值修改的是命名空間,而不是對象, 比如:
a = 10
這個語句就是把a放入到了對應的命名空間, 然后讓它指向一個值為10的整數對象。
a = [] a.append(1)
這個就是把a放入到名字空間,然后指向一個列表對象, 然而后面的a.append(1)這句話只是修改了list的內容,并沒有修改它的內存地址。因此
并沒有涉及到修改名字空間。
賦值操作有個特點就是: 賦值操作總是在最里層的作用域.也就說,只要編譯到了有賦值操作,就會在當前名字空間內新創建一個名字,然后開始才綁定對象。即便該名字已存在于賦值語句發生的上一層作用域中;
現在再看例子2, 就清晰多了, x += x 編譯到這里時,發現了賦值語句,于是準備把x新加入最內層名字空間也就是func2中,即使上層函數已經存在了; 但是賦值的時候,又要用到x的值, 然后就會報錯:
UnboundLocalError: local variable "x" referenced before assignment
這樣看起來好像就是 內部函數只可以讀取外部函數的變量,而不能做修改,其實本質還是因為賦值涉及到了新建locals()的名字。
在稍微改一點:
#!/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()
這個結果就是:
before func1: {"x": [1, 2]} before fun2: {"x": [1, 2]} after fun2: {"x": [2, 2]} after func1: {"x": [2, 2], "func2":}
咋正確了呢---這不應該要報錯嗎? 其實不然,就跟上面的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()
In [2]: locals() is globals() Out[2]: True
另外我們可以手動修改globals()來創建名字
In [3]: globals()["a"] = "abcde" In [4]: a Out[4]: "abcde"
但是locals()在函數里面的話, 貌似是不起作用的,如下:
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)與名字空間, 這里只是模糊了二者的區別
面向對象,也就是類的名字空間, 又有不一樣的地方。。。
學一點記錄一點吧。
參考1
2
3
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37779.html
摘要:正如儒家經典所闡述修身齊家治國平天下。除此之外,模塊還有如下最基本的屬性在一個模塊的全局空間里,有些屬性是全局起作用的,稱之為全局變量,而其它在局部起作用的屬性,會被稱為局部變量。 導讀:Python貓是一只喵星來客,它愛地球的一切,特別愛優雅而無所不能的 Python。我是它的人類朋友豌豆花下貓,被授權潤色與發表它的文章。如果你是第一次看到這個系列文章,那我強烈建議,請先看看它寫的前...
摘要:正如儒家經典所闡述修身齊家治國平天下。除此之外,模塊還有如下最基本的屬性在一個模塊的全局空間里,有些屬性是全局起作用的,稱之為全局變量,而其它在局部起作用的屬性,會被稱為局部變量。 導讀:Python貓是一只喵星來客,它愛地球的一切,特別愛優雅而無所不能的 Python。我是它的人類朋友豌豆花下貓,被授權潤色與發表它的文章。如果你是第一次看到這個系列文章,那我強烈建議,請先看看它寫的前...
摘要:在內置命名空間不能使用全局和局部的名字??梢孕蜗蟮乩斫獬蓛戎妹臻g具有最高級別,不需要定義就可以使用,全局命名空間次之,最低級是局部命名空間。 python中的命名空間分三種: 內置的命名空間,在啟動解釋器的時候自動加載進內存的各種名字所在的空間,比如print,input等不需要定義就可以使用的名字 全局命名空間,就是從上到下所有我們定義的變量名和函數名所在的空間,是在程序從上到下...
閱讀 3025·2023-04-25 20:22
閱讀 3343·2019-08-30 11:14
閱讀 2595·2019-08-29 13:03
閱讀 3183·2019-08-26 13:47
閱讀 3226·2019-08-26 10:22
閱讀 1271·2019-08-23 18:26
閱讀 618·2019-08-23 17:16
閱讀 1913·2019-08-23 17:01