Python第六章-函數02-函數的作用域

函數

三、作用域規則

有了函數之後,我們必須要面對一個作用域的問題。

比如:你現在訪問一個變數,那麼 python 解析器是怎麼查找到這個變數,並讀取到這個變數的值的呢? 依靠的就是作用域規則!

3.1 作用域

作用域(scope)

作用域就是 python 程式的一塊文本區域,在這個區域內,可以直接訪問(Directly accessible)命名空間。

直接訪問的意思就是:當你訪問一個絕對的命名的時候,直接在命名空間中查找

儘管作用域的定義是靜態的,但是作用域的使用(查找變數)卻是動態的。


在程式碼執行的任何時間,至少有 3 個嵌套的作用域,這些作用域的命名空間可以直接訪問。

  1. 內部作用域(局部作用域)。包含了所有的局部命名,在訪問變數的時候,首先在內部作用域中查找。
  2. 然後是嵌套函數的外層作用域。在這裡搜索非局部,但也是非全局的命名。(在 python 中允許在函數中定義函數的)
  3. 然後是包含當前模組的全局作用域。
  4. 最後搜索的是最外層的創建內置命名的作用域。

作用域搜索規則:LEGB

L:局部的(local)

E:封閉的(Enclosing)

G:全局的(Global)

B:內置的(Built-in)

一、局部命名空間

函數內部的命名空間,在調用函數的時候生成,調用結束時消失。當局部命名空間有效時,它是第一個用於檢查某個名字存在性的命名空間。如果在局部命名空間內找到該名稱,則返回與名字相關聯的對象,反之提示出錯。

3.2作用域在 python 中的具體應用

3.2.1.訪問局部作用域

def foo():      a = 20      print(a)    foo()  

說明:

函數內部訪問變數a, 先在foo函數內部查找。因為 a確實是在函數內部聲明的變數,然後就找到了a


3.2.2.訪問外部作用域

a = 100      def foo():      print(a)      foo()  

說明:

  1. foo函數內部,我們直接去訪問一個變數 a,那麼就會沿著作用域從內向外開始查找a
  2. 先查找foo的局部作用域,發現沒有a。然後繼續去foo函數的外部作用域,這個例子中就直接到了當前模組的全局作用域,所以找到了 a, 所以就輸出了全局作用域中a的值!

3.2.3.訪問外部函數的作用域

def outer():      a = 20        def inner():          print(a)        inner()      outer()  

說明:

  1. 我們在一個函數的內部聲明了一函數,這種函數嵌套在 python 中是允許的。
  2. 內部函數inner執行的時候,訪問變數a,現在inner內部找變數a, 沒有找到,然後去他外部的函數中找變數a, 找到後, 就直接輸出了他的值

3.2.4 python 針對修改變數值的特殊情況

3.2.4.1.只能修改局部變數

在 python 的函數中, 修改一個變數的值的時候,永遠操作的是局部變數

為什麼會這樣呢?

這其實是由 python 定義變數的方式所決定的.

python 不需要顯示的去定義變數,直接賦值的時候如果變數不存在直接就定義了.

如果在函數內部可以直接修改外部作用域變數的值,則就無法定義一個同名變數了.

所以, python 才規定不能在函數內部直接修改外部作用域變數的值.


a = 10      def foo():      a = 20 # 這裡其實是新創建了一個局部變數 a .並不是修改的全局作用域的變數 a        print(a) # 根據作用域的查找規則,這裡訪問的是局部變數 a      foo()  print(a) # 根據作用域查找規則,這裡訪問的是全局作用域的 a  


3.2.4.2.變數必須先賦值才能使用

看下面的程式碼:

a = 10      def foo():      a = a + 2    foo()  

說明:

a = a + 2 這行程式碼有問題. 為什麼?

首先要搞清楚 a + 2 中的a是局部變數還是全局變數?

是局部變數a!

在解釋器運行這個函數的時候, 已經檢測到函數內部有創建局部變數a, 所以這個時候你訪問到的一定是局部變數a.

a + 2 中的局部變數a還沒有 被賦值,所以和 2 相加拋出了異常.UnboundLocalError(局部變數錯誤)


a = 10      def foo():      print(a)      a = 20      foo()  

說明:
原因和前面的一樣的.

解析器已經檢測到你後面會聲明局部變數a, 所以print(a)中的 a 仍然是局部變數.但是還沒有賦值,所以就拋異常了


總結:

在函數內部如果你定義了局部變數,那麼你在任何地方都沒有辦法訪問到函數外部作用域的同名變數.


3.2.4.3 函數內修改全局變數

通過前面的學習, 正常情況下我們知道了在函數內部沒有辦法修改全局變數的值!

但是這只是正常情況下!

如果我們有在函數內部修改全局變數值的需求怎麼辦?

也是可以的, 但是我們需要做些小動作: 使用關鍵字global


a = 10    def foo():      global a    # 告訴 python 解析器, a 以後就是全局變數了      a = 20    foo()    print(a)    # 20  

說明:

  1. global 後面跟上全局變數的名, 那麼在後面的程式碼中就可以使用全局變數了.
  2. 如果有多個全局變數需要修改, global可以同時定義多個全局變數.global a, b, c

3.2.4.4內部函數修改外部函數的局部變數

當用到函數嵌套的時候, 內部函數正常情況下也是無法修改外部函數的局部變數, 只能訪問讀取.

如果想修改怎麼辦?

使用關鍵字:nonlocal

a = 10      def outer():      a = 20        def inner():          nonlocal a  # 把 a 綁定到外部函數的局部變數 a 上          a = 30        inner()        print("outer 的局部變數a:" + str(a))  # 30 被內部函數 inner 修改了      outer()  #print("全局變數a:%d"%a)  #print("全局變數a:",a)  print("全局變數a:" + str(a))