python中space_漫谈Python的Namespace
編程派微信號:codingpy
前言python里面最核心的內(nèi)容就是:名字空間(namespace)
例子引入
例1
可以正常輸出結(jié)果: 并且需要注意,在func2使用x變量之前的名字空間就已經(jīng)有了'x':1.
稍微改一點:如下
例2:
輸出就開始報錯: 而且在before func2也沒有了x.
這兩個例子正好涉及到了python里面最核心的內(nèi)容:名字空間,正好總結(jié)一下,然后在解釋這幾個例子。
名字空間(Namespace)
比如我們定義一個”變量”
所以,這里更準確的叫法應該是名字。 一些語言中比如c,c++,java 變量名是內(nèi)存地址別名, 而Python的名字就是一個字符串,它與所指向的目標對象關(guān)聯(lián)構(gòu)成名字空間里面的一個鍵值對{name: object},因此可以這么說,python的名字空間就是一個字典。
分類
python里面有很多名字空間,每個地方都有自己的名字空間,互不干擾,不同空間中的兩個相同名字的變量之間沒有任何聯(lián)系一般有4種: LEGB四種locals:函數(shù)內(nèi)部的名字空間,一般包括函數(shù)的局部變量以及形式參數(shù)
enclosing function:在嵌套函數(shù)中外部函數(shù)的名字空間, 對fun2來說, fun1的名字空間就是.
globals:當前的模塊空間,模塊就是一些py文件。也就是說,globals()類似全局變量。
__builtins__: 內(nèi)置模塊空間,也就是內(nèi)置變量或者內(nèi)置函數(shù)的名字空間。
當程序引用某個變量的名字時,就會從當前名字空間開始搜索。搜索順序規(guī)則便是: LEGB。
一層一層的查找,找到了之后,便停止搜索,如果最后沒有找到,則拋出在NameError的異常。這里暫時先不討論賦值操作。
比如例1中的a = x +1 這行代碼,需要引用x,則按照LEGB的順序查找,locals()也就是func2的名字空間沒有,進而開始E,也就是func1,里面有,找到了,停止搜索,還有后續(xù)工作,就是把x也加到自己的名字空間,這也是為什么fun2的名字空間里面也有x的原因。
訪問方式
其實上面都已經(jīng)說了,這里暫時簡單列一下 1. 使用locals()訪問局部命名空間 2. 使用globals()訪問全局命名空間。
這里有一點需要注意,就是涉及到了from A import B 和import A的一點區(qū)別。
輸出結(jié)果:
從輸出結(jié)果可以看出globals()包含了定義的函數(shù),變量等。對于'deepcopy': 可以看出deepcopy已經(jīng)被導入到自己的名字空間了,而不是在copy里面。 而導入的import copy則還保留著自身的名字空間。
因此要訪問copy的方法,就需要使用copy.function了。這也就是為什么推薦使用import module的原因,因為from A import B這樣會把B引入自身的名字空間,容易發(fā)生覆蓋或者說污染。
生存周期
每個名字空間都有自己的生存周期,如下:__builtins__: 在python解釋器啟動的時候,便已經(jīng)創(chuàng)建,直到退出
globals:在模塊定義被讀入時創(chuàng)建,通常也一直保存到解釋器退出。
locals : 在函數(shù)調(diào)用時創(chuàng)建,直到函數(shù)返回,或者拋出異常之后,銷毀。 另外遞歸函數(shù)每一次均有自己的名字空間。
看著沒有問題,但是有很多地方需要考慮。比如名字空間都是在代碼編譯時期確定的,而不是執(zhí)行期間。這個也就可以解釋為什么在例1中,before func2:的locals()里面包含了x: 1 這一項。再看下面這個
肯定會報錯的,但是錯誤不是
而是:
雖然x = 10永遠不會執(zhí)行,但是在執(zhí)行之前的編譯階段,就會把x作為locals變量,但是后面編譯到print的時候,發(fā)現(xiàn)沒有賦值,因此直接拋出異常,locals()里面便不會有x。這個就跟例子2中,before func2里面沒有x是一個道理。
賦值
為什么要把賦值單獨列出來呢,因為賦值操作對名字空間的影響很大,而且很多地方需要注意。 核心就是: 賦值修改的是命名空間,而不是對象, 比如:
這個語句就是把a放入到了對應的命名空間, 然后讓它指向一個值為10的整數(shù)對象。
這個就是把a放入到名字空間,然后指向一個列表對象, 然而后面的a.append(1)這句話只是修改了list的內(nèi)容,并沒有修改它的內(nèi)存地址。因此并沒有涉及到修改名字空間。
賦值操作有個特點就是:賦值操作總是在最里層的作用域,也就說,只要編譯到了有賦值操作,就會在當前名字空間內(nèi)新創(chuàng)建一個名字,然后開始才綁定對象。即便該名字已存在于賦值語句發(fā)生的上一層作用域中;
總結(jié)
分析例子
現(xiàn)在再看例子2, 就清晰多了, x += x 編譯到這里時,發(fā)現(xiàn)了賦值語句,于是準備把x新加入最內(nèi)層名字空間也就是func2中,即使上層函數(shù)已經(jīng)存在了; 但是賦值的時候,又要用到x的值,然后就會報錯:
這樣看起來好像就是 內(nèi)部函數(shù)只可以讀取外部函數(shù)的變量,而不能做修改,其實本質(zhì)還是因為賦值涉及到了新建locals()的名字。 在稍微改一點:
這個結(jié)果就是:
咋正確了呢—這不應該要報錯嗎?
其實不然,就跟上面的a.append(1)是一個道理。 x[0] += x[0]這個并不是對x的賦值操作。按照LEGB原則, 搜到func1有變量x并且是個list, 然后將其加入到自己的locals(),后面的x[0] += x[0], 就開始讀取x的元素,并沒有影響func2的名字空間。
另外無論func1與func2的名字空間的x沒有什么關(guān)系,只不過都是對[1, 2]這個列表對象的一個引用。
這個例子其實也給了我們一個啟發(fā),我們知道內(nèi)部函數(shù)無法直接修改外部函數(shù)的變量值,如例2,如果借助list的話,就可以了吧!比如把想要修改的變量塞到一個list里面,然后在內(nèi)部函數(shù)里面做改變!當然python3.x里面有了nonlocal關(guān)鍵字,直接聲明一下就可以修改了。看到這里,對作用域理解應該有一點點了吧。
延伸
與閉包的不同
我們都知道閉包是把外部函數(shù)的值放到func.func_closure里面,為什么不像上面的例子一樣直接放到函數(shù)的名字空間呢?這是因為locals()空間是在函數(shù)調(diào)用的時候才創(chuàng)建! 而閉包只是返回了一個函數(shù), 并沒有調(diào)用,也就沒有所謂的空間。
locals()與globals()
在最外層的模塊空間里locals()就是globals()
另外我們可以手動修改globals()來創(chuàng)建名字
但是locals()在函數(shù)里面的話, 貌似是不起作用的,如下:
這是因為解釋器會將 locals 名字復制到 一個叫FAST的區(qū)域來優(yōu)化訪問速度,而實際上解釋器訪問對象時,是從FAST區(qū)域里面讀取的,而非locals()。
所以直接修改locals()并不能影響x(可以使用exec動態(tài)訪問,不再細述)。 不過賦值操作,會同時刷新locals()和FAST區(qū)域。
查了不少資料,說了這么多,我只想說,作為python最核心的東西,名字空間還有很多很多地方需要探究,比如作用域(scope)與名字空間,這里只是模糊了二者的區(qū)別
面向?qū)ο?#xff0c;也就是類的名字空間, 又有不一樣的地方。。。
學一點記錄一點吧。
掃碼關(guān)注編程派
獲取最新教程及資源推送
↓↓↓ 點擊閱讀原文,查看更多Python教程
閱讀原文:http://mp.weixin.qq.com/s?__biz=MzAwNDc0MTUxMw==\x26mid=2649639698\x26idx=1\x26sn=c4559bb43b3e98626e9a2f1b65f8ca76\x26chksm=833dabf4b44a22e257a4a38d15f616ff1bd04ae776f83d583899bdeed426803cf091de1280ac#rd
總結(jié)
以上是生活随笔為你收集整理的python中space_漫谈Python的Namespace的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android studio pytho
- 下一篇: python能制作游戏吗_如何用Pyth