2017-05-03

有點複雜的 Python Scope

Python  的變數 scope 和 Java 的 scope 還蠻不一樣的,要特別注意。

函式的傳入參數只存在於 local scope(也可以叫做 function scope),有別於 global scope。

在函式內指定的變數為 local scope,在函式外指定的變數為 global scope,這裡的指定就是 =。
global_var = 'Neil'
def aFunction(local_var):
    another_local_var = 'Egg'
    print(global_var) # Neil
    print(local_var) # Emma
    print(another_local_var) # Egg
print(global_var) # Neil
aFunction('Emma')
print(local_var) # NameError: name 'local_var' is not defined
print(another_local_var) # NameError: name 'another_local_var' is not defined
local scope 變數在函式結束後就消失了,不能再呼叫。

在函式裡(local scope)可以使用 global scope 變數,但不能對 global scope 變數改值(使用 = ),除非使用 global 語句。
global_var = 'Neil'
def aFunction():
    print(global_var) # Neil
    global_var = 'Egg' # UnboundLocalError: local variable 'global_var' referenced before assignment
aFunction()
在函式裡直接修改 global scope 變數的值會報錯,這個錯誤(UnboundLocalError: local variable 'global_var' referenced before assignment)很奇怪,呆會再解釋。

global_var = 'Neil'
def aFunction():
    global global_var
    print(global_var) # Neil
    global_var = 'Egg' # 沒報錯了
    print(global_var) # Egg
aFunction()
在函式裡先用 global 語句標注 global scope 變數,之後就可以在函式裡修改 global scope 變數的值。

接下來回頭解釋沒有使用 global 語句的那個奇怪訊息(UnboundLocalError: local variable 'global_var' referenced before assignment)。

先看一段類似的程式,沒有用 global 語句,也沒有先呼叫 global scope 變數,而是先修改 global scope 變數的值再印出來,這次就沒有報錯,奇怪吧?
global_var = 'Neil'
def aFunction():
    global_var = 'Egg'
    print(global_var) # Egg
aFunction()
了解原因後就不會覺得太奇怪。

首先,Python 允許 local scope 裡使用與 global scope「同名」的變數,這就解釋前一個範例為什麼不會報錯,因為函式裡的 global_var 根本就一個 local scope 變數,和函式外的 global_var 一點關係也沒有。

雖然說 Python 允許 local scope 裡使用與 global scope「同名」的變數,但不建議這麼做,因為這樣只會找自己麻煩。

再來就是 Python 有一個非常奇怪的行為,至少對一個 Java PG 來說是很不習慣的,那就是函式裡的任何變數指定,只要該變數沒有使用 global 語句,一律視為 local scope 變數,不管它出現的位置為何

最後一句話最重要,Python 在一個函式裡會先去掃出所有的變數指定,先確認它是 local scope 或 global scope,然後才開始執行函式內容
global_var = 'Neil'
def aFunction():
    print(global_var) # Neil
    global_var = 'Egg' # UnboundLocalError: local variable 'global_var' referenced before assignment
aFunction()
以上面函式為例,Python 先找到 global_var = 'Egg',然後沒看到 global global_var,所以認定 global_var 是 local scope 變數,最後在執行函式內容時,於第一行遇到 print(global_var) 就會報錯說:等等,你不可以在指定變數前就呼叫它。

這就是那個奇怪的錯誤訊息在說的事,UnboundLocalError: local variable 'global_var' referenced before assignment,不可以在指定變數前就呼叫它

因此如果要在函式裡使用 global scope 變數,那就在函式一開始的地方就使用 global 語句

另外也不能使用不同 local scope 間的變數,例如在 A 函式裡呼叫了 B 函式,在 B 函式裡不能使用 A 函式的 local scope 變數,也就是 A 函式不會變成 B  函式的 global 環境,global 永遠只有一個,就是主程式所在的位置

以下列出幾點識別變數 scope 的準則:

  • 在函式外指定的變數,為 global scope
  • 在函式內使用 global 語句的變數,為 global scope
  • 在函式內指定但沒有使用 global 語句的變數,為 local scope
  • 在函式內呼叫但沒有指定的變數,為 global scope

可以再簡化成:在函式內使用 global scope 變數時,global 語句只有在修改(重新指定)才需要,只有呼叫時不需要

一個變數的 scope 只會屬於一種,local 或 global,不會同時兼具兩種 scope,不能在函式先把變數當成 global scope,事後又想轉成 local scope,或者想把 local scope 變數再透過 global 語句轉成 global scope 變數。

最後,有一條簡單的原則可以避免這個複雜的 scope 邏輯,就是「不要在函式裡直接呼叫 global scope 變數,改以參數的方式傳入,若有需要,再用 return 的方式回傳」。

這就是 Utility Function  的設計概念,Python 的內建函式都是這麼做的。
---
---
---

沒有留言:

張貼留言