python進階15變量作用域LEGB

原創博客鏈接:python進階15變量作用域LEGB

作用域

“作用域”定義了Python在哪一個層次上查找某個“變量名”對應的對象。接下來的問題就是:“Python在查找‘名稱-對象’映射時,是按照什麼順序對命名空間的不同層次進行查找的?”
答案就是:使用的是LEGB規則,表示的是Local -> Enclosed -> Global -> Built-in,其中的箭頭方向表示的是搜索順序。

 

1
2
3
4
Local 可能是在一個函數或者類方法內部。
Enclosed 可能是嵌套函數內,比如說 一個函數包裹在另一個函數內部。
Global 代表的是執行腳本自身的最高層次。
Built-in 是Python爲自身保留的特殊名稱。

練習01

 

1
2
3
4
5
6
7
8
9
10
11
12
a_var = 'global value'

def outer():
       a_var = 'local value'
       print('outer before:', a_var)
       def inner():
           nonlocal a_var
           a_var = 'inner value'
           print('in inner():', a_var)
       inner()
       print("outer after:", a_var)
outer()

結果:

 

1
2
3
outer before: local value
in inner(): inner value
outer after: inner value

分析:

練習02

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
a = 'global'

def outer():

    def len(in_var):
        print('called my len() function: ', end="")
        l = 0
        for i in in_var:
            l += 1
        return l

    a = 'local'

    def inner():
        global len
        nonlocal a
        a += ' variable'
    inner()
    print('a is', a)
    print(len(a))


outer()

print(len(a))
print('a is', a)

結果:

 

1
2
3
4
a is local variable
called my len() function: 14
6
a is global

可自行分析試試

注意點

01:在函數作用域內修改全局變量通常是個壞主意,因爲這經常造成混亂或者很難調試的奇怪錯誤。如果你想要通過一個函數來修改一個全局變量,建議把它作爲一個變量傳入,然後重新指定返回值
02:如果我們提前在全局命名空間中明確定義了for循環變量,也是同樣的結果!在這種情況下,它會重新綁定已有的變量:
For循環變量“泄漏”到全局命名空間

 

1
2
3
4
5
6
7
8
9
b = 1
for b in range(5):
    if b == 4:
        print(b, '-> b in for-loop')
print(b, '-> b in global')

結果:
4 -> b in for-loop
4 -> b in global

在Python 3.x中,我們可以使用閉包來防止for循環變量進入全局命名空間。下面是一個例子(在Python 3.4中執行):

 

1
2
3
4
5
6
7
i = 1
print([i for i in range(5)])
print(i, '-> i in global')

結果
[0, 1, 2, 3, 4]
1 -> i in global

爲何for裏面會有如此奇怪的規則?閉包本身具有獨立作用域,所以這裏的i對父域不會形成干擾。

還有另一個副作用就是

 

1
2
3
for i in range(5):
    print(i)
    i = 10

結果:

 

1
2
3
4
5
0
1
2
3
4

而不是直觀理解的執行一次就退出

代碼;

 

1
2
3
4
for i in range(5):
    i += 5
    print(i)
print(i)

結果:

 

1
2
3
4
5
6
5
6
7
8
9
9

第一:成功污染外面的i
第二:內部i+5只進行了1次,說明i=i+5,右側的i,是真正的for裏面的i,左側的i是外部的i,但是卻未報錯unbounderror的錯誤!(內部的i有賦值,所以理論上外部的i應該是被屏蔽的,應該報錯unbound纔對,但是沒報。即使勉強接受這一點,最終外面的i=9而非4,也很奇怪)

原因:for循環不會引入新的作用域,所以,循環結束後,繼續執行print(i),可以正常輸出i,原理上與情況3中的if相似。這一點Python就比較坑了,因此寫代碼時切忌for循環名字要與其他名字不重名纔行。
上式中,for裏面i+5,到外面的for那裏又重新賦值爲原有的i(無視了內部對i的修改),所以每次都+5了,而最終結果依然+5,是由於最後一次的i並未被成功賦值,所以最終結果看起來比較奇.

 

1
2
3
4
5
list_1 = [i for i in range(5)]
print(i)

結果:  
NameError: name 'i' is not defined

情況3中說到過,for循環不會引入新的作用域,那麼爲什麼輸出報錯呢?真相只有一個:列表生成式會引入新的作用域,for循環是在Local作用域裏面的。事實上,lambda、生成器表達式、列表解析式也是函數,都會引入新作用域。

參考

Python中的LEGB規則:https://www.cnblogs.com/GuoYaxiang/p/6405814.html
Python中命名空間與作用域使用總結:https://www.cnblogs.com/chenhuabin/p/10123009.html
一道題看Python的LEGB規則:https://www.ucloud.cn/yun/45499.html
Python LEGB規則:https://www.jianshu.com/p/3b72ba5a209c
python中的LEGB 規則:https://blog.csdn.net/xun527/article/details/76795328

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章