安全開發:說一說Python的作用域與名稱空間
0x00 前言
在這個系列的開始,介紹了Python文件操作相關的概念,這一節我會說一說對Python的作用域與名稱空間的理解。
很多同學可能會覺得說的東西比較基礎似乎和安全沒有什麼關係,其實不然。我是這麼理解的,know it,then hack it.真正牛逼的漏洞都是對程序本身有深入的理解後挖出來的,『人肉掃描器』沒啥意思,只有真正弄懂了基礎,再去分析漏洞報告,做到舉一反三,我相信0day也會隨之而來。
同時,我更希望看到的是隨著SDL的落地,安全意識的提高,從源頭上「堵住」漏洞,走出『挖漏洞,補漏洞』的怪圈。
不羅嗦了,開始今天的主題。
0x01 Python的名稱空間
什麼是名稱空間?
簡單來說,名稱空間就是存放名字與值綁定關係的地方。
這裡說的名字與值,不單單指變數名與變數值之間的關係。函數名、類名等均為這裡所指的名字。
名稱空間是內存中一塊隔離的空間。
名稱空間還方便了大型項目的協作開發,Python之禪中也提到,「名稱空間是一個偉大的發明,在日常開發中,我們要儘可能多的使用它。」
名稱空間的分類:
名稱空間共分為3類,
1、全局名稱空間
2、局部名稱空間
3、內置名稱空間
註:變數名、函數名、類名等都是文中所指的名字。
內置名稱空間,就是在Python解釋器啟動時生效,在Python解釋器關閉時失效。像一些常見的函數,比如說print()、len()、max()等均存在於內置名稱空間。
全局名稱空間,定義的是文件級別的名字與值之間的綁定關係。舉個例子,比如說我們在當前文件中定義變數x=1、函數def fuck:pass,等諸如此類,未在函數或類中定義的變數與值之間的綁定關係就是全局名稱空間。這類名字與值之間的綁定關係,直接在Python文件的上下文中定義,未在函數或類的內部定義。
局部名稱空間,定義的是函數內部名字與值之間的綁定關係。
在啟動Python解釋器時,內置名稱空間隨之生效。此時可以直接調用一些內置的函數,而不需要對該類函數進行定義。當Python解釋器關閉時,內置名稱空間隨之失效。
執行Python文件時,Python解釋器會將該Python文件級別的名字與值之間的綁定關係存放起來,存放該綁定關係的空間就是全局名稱空間。當該Python文件執行完畢時,全局名稱空間也隨之失效。
在調用函數時,局部名稱就會臨時生效。函數調用結束,局部名稱空間隨之失效。
需要注意的是,類中定義的名字與值之間的綁定關係也可以理解為局部名稱空間。與函數中的名稱空間不同,類定義完,該名稱空間就會產生。除非類被刪除,否則這塊名稱空間不會被回收。
可見,名稱空間的載入順序如下:
1、先載入內置名稱空間
2、再載入全局名稱空間
3、最後有可能載入局部名稱空間
反之,Python解釋器查找名字的順序為,先局部,再全局,最後內置。
0x02 作用域
作用域就是一個作用範圍,簡單理解,也跟找名字有關。
眾所周知,Python的三塊名稱空間有:內置名稱空間、全局名稱空間、局部名稱空間。
這幾個內存空間的特點如下:
拿內置名稱空間來說,
生效:在Python解釋器啟動時,內置名稱空間生效。
失效:在Python解釋器關閉時,內置名稱空間失效。
那全局名稱空間呢?
生效:執行Python文件時,全局名稱空間生效。
失效:當Python文件執行完畢時,全局名稱空間失效。
局部名稱空間:
生效:當函數被調用時,局部名稱空間臨時生效。
失效:函數調用結束,局部名稱空間失效。
圍繞Python的名稱空間,其作用域一共分為兩塊:全局作用域、局部作用域。
全局作用域由內置名稱空間、全局名稱空間兩部分的名字與值之間的綁定關係組成。
其特點為:全局存活,全局有效,伴隨Python文件執行始終。
局部作用域由局部名稱空間的名字與值之間的綁定關係構成。
其特點為:臨時存活,局部有效。
總結一下,名字的查找順尋為LEGB,即locals -> enclosing function -> globals -> builtins。
locals:代表函數內部的名字空間,包括函數內部的局部變數,和形式參數。
enclosing:外部函數嵌套的名字空間,這種形式常見於閉包中。
globals:全局變數,即文件級別定義的名字與值之間的綁定關係。函數定義所在模塊的名稱空間。
builtins:內置模塊的名字空間。
查看作用域的兩個內置函數:globals()、locals()。
globals(),用來查看全局名稱空間的名字。
dir(globals()[__builtins__]),用來查看內置名稱空間的名字。
在內置名稱空間中,存在諸如print、max、len等名字,這就是為什麼我們可以直接調用這些函數,而不需要進行定義的原因。
locals(),用來查看局部名稱空間的名字。
在文件級別而不是在函數中調用locals()查看名字時,與globals()的返回值相同。
作用域關係,在函數定義時就已經確定,與調用位置無關。
x = 23333def f1(): def f2(): print(x) return f2f = f1()print(f)f()def func(): x = 123 f()func()
如上代碼,由於作用域關係在函數定義時就已經確定了,即f()函數的作用域關係在定義時確定x = 23333(全局名稱空間),與調用位置x=123(局部名稱空間)無關,所以最後的輸出結果仍為23333而不是123。
0x03 關鍵字global和nonlocal的引入
x = 1def foo(): x = 10foo()print(x)
該代碼的執行結果為:1
x = 1def foo(): global x x = 10foo()print(x)
該代碼的執行結果為:10
在函數內部(局部名稱空間),要想修改全局名稱空間內變數的值需要使用關鍵字global聲明。
x = 1def foo(): x = 2 def foo2(): nonlocal x x = 13213321 foo2() print(foo:+str(x))foo()print(x)
該代碼的執行結果為:
foo:132133211
在嵌套定義的函數中,要想修改上一級函數中(上一級局部名稱空間)變數的值,需要使用關鍵字nonlocal進行聲明。若上一級不存在該變數,則繼續向上一級尋找,直到找到為止(僅限在函數內部)。找不到則報錯。
0x04 參考鏈接
9. Classes - Python 3.6.4 documentation
推薦閱讀:
※網路中的「豆腐渣工程」與真正的二次認證
※業務連續性計劃概述(Day008)
※安全態勢感知:一些哲學思考
※盤點物聯網網路和設備安全的五個誤解
※博彩借錢風暴(朋友QQ被盜):我是如何反擊詐騙犯!