從 JavaScript 角度學 Python(6) - 變數作用域
前言
前一篇介紹了函式與變數,那麼接下來要來回頭補充一下關於前面所沒聊到的變數作用域。
變數作用域
首先先讓我們回顧一下 JavaScript 的變數作用域部分,在 JavaScript 中有三種宣告變數的方式,分別為:
var
let
const
我們都知道這三種宣告變數的方式與它所屬的變數作用域都是不同的,例如: var
的作用域是以函式 (function
) 作為區分,因此如果不是在函式內宣告的話,就很容易影響到全域環境,也就是所謂的全域污染:
1 | if(true) { |
那麼全域污染會有什麼下場呢?最明顯就是不小心變數互相覆蓋的問題:
1 | var myName = 'Ray'; |
而 let
與 const
則是以區塊 (block) 作為區分,什麼是區塊呢?只要你看到 {
、 }
包起來的就是一個區塊、一個 block:
1 | if(true) { |
因此當我們在閱讀程式碼時,我們是可以透過宣告變數的關鍵字來了解該變數是屬於哪一種變數作用域,那 Python 呢?Python 又如何呢?我們可以先試著將上面程式碼改成 Python 版本試試看:
1 | if True: |
首先我們可以發現 Python 並不是以 Block 作為變數作用域的,那麼會不會是以函式當作作用域呢?這邊我們也可以實際驗證一下其結果:
1 | def fn(): |
答案是…
是的,Python 在變數宣告上是以 function
為作用域。
透過上面的範例我們可以了解到 Python 的變數類似於 JavaScript 的 var
變數而不是 let
變數,但這邊要注意 Python 並沒有提升的概念唷。
因此我們也可以知道一個不小心是有可能發生污染事件的:
1 | myName = 'Ray' |
範圍鍊
接下來是關於範圍鍊的部分,JavaScript 有一個觀念是關於範圍鍊,那麼什麼是範圍鏈呢?簡單來講就是,假使這個變數不存在於這個函式時,它會往外層去尋找這個變數或是函式是否存在,而且是一直一直一直往上尋找:
1 | var myName = 'Ray'; |
又或者是這一種類型的範例程式碼:
1 | var value = 1; // 全域變數 |
因此當函式本身若沒有相對應的變數或函式時,它就會自動往外層去尋找,而這就是 JavaScript 所謂的範圍鍊概念,如果對於範圍鍊不熟悉的話,可詳見 此篇 文章。
現在就讓我們拉回到 Python,Python 也會有相同概念嗎?我們先將前面其中一個範例程式碼直接改寫成 Python 來試試看:
1 | value = 1 |
從結果論來講,我們可以看到 print
輸出的結果依然是 1
而不是找不到這個變數,因此 Python 在變數的尋找上是與 JavaScript 非常相像的,所以你要說 Python 有沒有範圍鍊得概念呢?我想是有的。
nonlocal 與 global
最後的結尾處我想聊一下關於 nonlocal
與 global
這兩個很特別的東西,首先先讓我們看一小段 Python 範例,也就是非常巢狀的巢狀函式:
1 | def fn1(): |
而我們在實際開發時單字量可能很少,例如我這個英文很差的人永遠都只有那幾個單字
回歸到 Python 的部分,剛剛有提到往往我們開發系統時可能會寫得很長很多,所以變數名稱就會有很高的機率重複,例如就像這樣:
1 | a = 'Ray' |
雖然以結論來講上面程式碼看起來是沒有什麼問題的,畢竟每一個函式都有一個自己的變數,但是如果今天我們希望 fn3
的 a
是重新賦予 global 的變數 a
呢?
1 | a = 'Ray' |
發現了嗎?由於我們在每個函式內重新宣告了一個變數都是 a
,所以 Python 認為我們是要宣告一個新的變數而不是賦予到 global a
中,因此這時候我們就可以使用 Python 所提供的 global 變數名稱
語法告知 Python:「嘿!我這個變數是 Global 的哦!你不要建立一個區域變數!」:
1 | a = 'Ray' |
那麼這樣就可以解決我們作用域的問題,畢竟就如同前面所言 Python 並沒有變數宣告的語法,因此 Python 必須透過其他的方式去告知它這個變數不是重新定義,而是直接取用外層。
反之如果你想取用的是函式內的變數,那麼也是一樣,只是在此所使用的是 nonlocal
:
1 | a = 'Ray' |
那麼這時候你可能會想說為什麼 fn1
的 a
不會受到影響呢?你可以利用範圍鍊的概念去思考,當 fn3
的 a
像外層尋找時,會優先找到 fn2
的 a
因此它就會停止向上了,所以假設如果 fn2
的 a 若不存在的話,fn3
的 a
變數就會一直往上尋找,這一點觀點我們也可以嘗試驗證:
1 | a = 'Ray' |
透過一個實際的案例來講,我們也可以再次體會到 JavaScript 的範圍鍊觀念是可以套用在 Python 上來看的,不知道有沒有讓已經快燒壞大腦的你有一點明瞭了呢?
作者的話
昨天調整了花雕醉雞的比例,基本上大約 200cc 花雕酒配 100cc 米酒差不多,然後這次嘗試直接將鹽包著肌肉一起處理,發現晚上回到家弄來吃之後還不錯吃。
關於兔兔們
- Tailwind CSS 臺灣官網
- Tailwind CSS 臺灣 (臉書粉絲專頁)
- 兔兔教 × Tailwind CSS Taiwan (臉書社群)
- 兔兔教大本營