python變量作用域


 

變量作用域

 

作用域指的是變量的有效范圍變量並不是在哪個位置都可以訪問的,訪問權限取決於這個變量是在哪里賦值的,也就是在哪個作用域內的。

通常而言,在編程語言中,變量的作用域從代碼結構形式來看,有塊級、函數、類、模塊、包等由小到大的級別。但是在Python中,沒有塊級作用域,

也就是類似if語句塊、for語句塊、with上下文管理器等等是不存在作用域概念的,他們等同於普通的語句。

 1 if True:            # if語句塊沒有作用域
 2     x = 1
 3 print(x)  4 # 1
 5 def func():         # 函數有作用域
 6     a = 8
 7 print(a)  8 # Traceback (most recent call last):
 9 # File "<pyshell#3>", line 1, in <module>
10 # a
11 # NameError: name 'a' is not defined

 

從上面的例子中,我們可以發現,在if語句內定義的變量x,可以被外部訪問,而在函數func()中定義的變量a,則無法在外部訪問。

通常,函數內部的變量無法被函數外部訪問,但內部可以訪問;類內部的變量無法被外部訪問,但類的內部可以。通俗來講,就是內部代碼可以訪問外部變量,

而外部代碼通常無法訪問內部變量。

 

變量的作用域決定了程序的哪一部分可以訪問哪個特定的變量名稱。Python的作用域一共有4層,分別是:

  • L (Local) 局部作用域
  • E (Enclosing) 閉包函數外的函數中
  • G (Global) 全局作用域
  • B (Built-in) 內建作用域
1 global_var = 0 # 全局作用域 2 def outer(): 3 out_var = 1 # 閉包函數外的函數中 4 def inner(): 5 inner_var = 2  # 局部作用域

 

前面說的都是變量可以找得到的情況,那如果出現本身作用域沒有定義的變量,那該如何尋找呢?

Python以L –> E –> G –>B的規則查找變量,即:在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,最后去內建中找。

如果這樣還找不到,那就提示變量不存在的錯誤。例如下面的代碼,函數func內部並沒有定義變量a,可是print函數需要打印a,那怎么辦?

向外部尋找!按照L –> E –> G –>B的規則,

層層查詢,這個例子很快就從外層查找到了a,並且知道它被賦值為1,於是就打印了1。

1 a = 1
2 
3 def func(): 4     print(a)

 

全局變量和局部變量

定義在函數內部的變量擁有一個局部作用域,被叫做局部變量,定義在函數外的擁有全局作用域的變量,被稱為全局變量。(類、模塊等同理)

所謂的局部變量是相對的。局部變量也有可能是更小范圍內的變量的外部變量。

局部變量只能在其被聲明的函數內部訪問,而全局變量可以在整個程序范圍內訪問。調用函數時,所有在函數內聲明的變量名稱都將被加入到作用域中。

 1 a = 1               # 全局變量
 2 
 3 def func():  4     b = 2           # 局部變量
 5     print(a)        # 可訪問全局變量a,無法訪問它內部的c
 6 
 7     def inner():  8         c = 3       # 更局部的變量
 9         print(a)    # 可以訪問全局變量a
10         print(b)    # b對於inner函數來說,就是外部變量
11         print(c)

 

global和nonlocal關鍵字

我們先看下面的例子:

 1 total = 0                        # total是一個全局變量
 2 
 3 def plus( arg1, arg2 ):  4     total = arg1 + arg2          # total在這里是局部變量.
 5     print("函數內局部變量total= ", total)  6     print("函數內的total的內存地址是: ", id(total))  7     return total  8 
 9 plus(10, 20) 10 print("函數外部全局變量total= ", total) 11 print("函數外的total的內存地址是: ", id(total))

很明顯,函數plus內部通過total = arg1 + arg2語句,新建了一個局部變量total,它和外面的全局變量total是兩碼事。而如果我們,

想要在函數內部修改外面的全局變量total呢?使用global關鍵字!

 

global:指定當前變量使用外部的全局變量

 

 1 global:指定當前變量使用外部的全局變量  2 
 3 total = 0                        # total是一個全局變量
 4 
 5 def plus( arg1, arg2 ):  6     global total    # 使用global關鍵字申明此處的total引用外部的total
 7     total = arg1 + arg2  8     print("函數內局部變量total= ", total)  9     print("函數內的total的內存地址是: ", id(total)) 10     return total 11 
12 plus(10, 20) 13 print("函數外部全局變量total= ", total) 14 print("函數外的total的內存地址是:

打印結果是:

1 函數內局部變量total=   30
2 函數內的total的內存地址是:  503494624
3 函數外部全局變量total=  30
4 函數外的total的內存地址是:  503494624

 

我們再來看下面的例子:

 1 a = 1
 2 print("函數outer調用之前全局變量a的內存地址: ", id(a))  3 
 4 def outer():  5     a = 2
 6     print("函數outer調用之時閉包外部的變量a的內存地址: ", id(a))  7     def inner():  8         a = 3
 9         print("函數inner調用之后閉包內部變量a的內存地址: ", id(a)) 10  inner() 11     print("函數inner調用之后,閉包外部的變量a的內存地址: ", id(a)) 12 outer() 13 print("函數outer執行完畢,全局變量a的內存地址: ", id(a))

 

如果你將前面的知識點都理解通透了,那么這里應該沒什么問題,三個a各是各的a,各自有不同的內存地址,是三個不同的變量。

打印結果也很好的證明了這點:

1 函數outer調用之前全局變量a的內存地址:  493204544
2 函數outer調用之時閉包外部的變量a的內存地址:  493204576
3 函數inner調用之后閉包內部變量a的內存地址:  493204608
4 函數inner調用之后,閉包外部的變量a的內存地址:  493204576
5 函數outer執行完畢,全局變量a的內存地址:  493204544

 

那么,如果,inner內部想使用outer里面的那個a,而不是全局變量的那個a,怎么辦?用global關鍵字?先試試看吧:

 1 a = 1
 2 print("函數outer調用之前全局變量a的內存地址: ", id(a))  3 def outer():  4     a = 2
 5     print("函數outer調用之時閉包外部的變量a的內存地址: ", id(a))  6     def inner():  7         global a   # 注意這行
 8         a = 3
 9         print("函數inner調用之后閉包內部變量a的內存地址: ", id(a)) 10  inner() 11     print("函數inner調用之后,閉包外部的變量a的內存地址: ", id(a)) 12 outer() 13 print("函數outer執行完畢,全局變量a的內存地址: ", id(a))
運行結果如下,很明顯,global使用的是全局變量a。
1 函數outer調用之前全局變量a的內存地址:  494384192
2 函數outer調用之時閉包外部的變量a的內存地址:  494384224
3 函數inner調用之后閉包內部變量a的內存地址:  494384256
4 函數inner調用之后,閉包外部的變量a的內存地址:  494384224
5 函數outer執行完畢,全局變量a的內存地址:  494384256
 
        
那怎么辦呢?使用nonlocal關鍵字!它可以修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量。將global a改成nonlocal a,代碼這里我就不重復貼了,
運行后查看結果,可以看到我們真的引用了outer函數的a變量。
1 函數outer調用之前全局變量a的內存地址:  497726528
2 函數outer調用之時閉包外部的變量a的內存地址:  497726560
3 函數inner調用之后閉包內部變量a的內存地址:  497726592
4 函數inner調用之后,閉包外部的變量a的內存地址:  497726592
5 函數outer執行完畢,全局變量a的內存地址:  497726528

 

面試真題:

不要上機測試,請說出下面代碼的運行結果:

1 a = 10
2 def test(): 3     a += 1
4     print(a) 5 test()

很多同學會說,這太簡單了!函數內部沒有定義a,那么就去外部找,找到a=10,於是加1,打印11!

我會告訴你,這段代碼有語法錯誤嗎?a += 1相當於a = a + 1,按照賦值運算符的規則是先計算右邊的a+1。但是,Python的規則是,如果在函數內部要

修改一個變量,那么這個變量需要是內部變量,除非你用global聲明了它是外部變量。很明顯,我們沒有在函數內部定義變量a,所以會彈出局部變量在未

定義之前就引用的錯誤。

 

更多的例子:

再來看一些例子(要注意其中的閉包,也就是函數內部封裝了函數):

 1 name = 'jack'
 2 
 3 def outer():  4     name='tom'
 5 
 6     def inner():  7         name ='mary'
 8         print(name)  9 
10  inner() 11 
12 outer()

 

 

 

上面的題目很簡單,因為inner函數本身有name變量,所以打印結果是mary。那么下面這個呢?

 1 name ='jack'
 2 
 3 def f1():  4     print(name)  5 
 6 def f2():  7     name = 'eric'
 8  f1()  9 
10 f2()

這題有點迷惑性,想了半天,應該是‘eric’吧,因為f2函數調用的時候,在內部又調用了f1函數,f1自己沒有name變量,那么就往外找,發現f2定義了個name,於是就打印這個name。錯了!!!結果是‘jack’!

Python函數的作用域取決於其函數代碼塊在整體代碼中的位置,而不是調用時機的位置。調用f1的時候,會去f1函數的定義體查找,對於f1函數,

它的外部是name ='jack',而不是name = 'eric'

 

再看下面的例子,f2函數返回了f1函數:

 1 name = 'jack'
 2 
 3 def f2():  4     name = 'eric'
 5     return f1  6 
 7 def f1():  8     print(name)  9 
10 ret = f2() 11 ret()
仔細回想前面的例子,其實這里有異曲同工之妙,所以結果還是‘jack’。
 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM