iT邦幫忙

2021 iThome 鐵人賽

DAY 28
2
Software Development

從 JavaScript 角度學 Python系列 第 28

從 JavaScript 角度學 Python(28) - 閉包(Closure)

前言

那 JavaScript 中有一個東西叫做閉包 (Closure),Python 也會有嗎?所以這章節就輕鬆的聊一下如果你想要在 Python 中寫閉包的話該如何撰寫。

變數作用域

首先先簡單回顧聊一下前面的「從 JavaScript 角度學 Python(6) - 變數作用域」章節,我們都知道每一個變數都有屬於它自己的作用域,例如:全域變數、區域變數等等。

那麼如果拿全域變數來講的話,你只要放在程式碼的最外層,就一定可以透過類似 JavaScript 的範圍鍊概念向上尋找到變數:

money = 1

def fn():
  print(money) # 1

fn()

至於區域變數呢?前面都有說過 Python 的變數類似 JavaScript 的 var 變數,因此會是以 function 為作用域,因此宣告在函式內的變數我們就無法在這個函式之外取得:

def fn():
  money = 1
  print(money) # 1

fn()
print(money) # NameError: name 'money' is not defined

好的,我們複習差不多了,今天大概就到這邊...

https://ithelp.ithome.com.tw/upload/images/20210927/20119486FIZvh5wrKb.png

別急,我們只是快速複習一下變數作用域的部分,接下來就讓我們準備了解 Python 的閉包該怎麼寫吧。

閉包(Closure)

那麼在了解並開始撰寫 Python 閉包之前,讓我先擷取我先前寫的 JavaScript 閉包範例程式碼:

function count() {
 let icash = 1000;
 return function (price){
  icash = icash - price;
  return icash;
 }
}
let ray = count();
ray(100); // 900
ray(200); // 700

上面是一個非常簡單的 JavaScript 閉包概念,在正常情況下 count 函式中的 icash 變數會因為被內部匿名函式所引用的關係,而不會被釋放掉記憶體,藉此可以讓我們重複使用這個變數記憶體,如果對於 JavaScript 的閉包觀念不熟悉的話,建議可以先閱讀我這一篇「JavaScript 核心觀念(38)-函式以及 This 的運作-閉包 Closure」文章。

那這邊讓我們拉回到 Python 中,如果想要寫出跟上面 JavaScript 的閉包我們又給如何撰寫呢?讓我們看一下將上面程式碼改成 Python 的話會是長什麼樣子呢?:

def count():
  icash = 1000
  def reduce(price):
    nonlocal icash
    icash = icash - price
  return reduce

ray = count()
ray(100) # 900
ray(200) # 700

oh!這邊你可千萬不要這樣子寫:

def count():
  icash = 1000
  return def (price):
    nonlocal icash
    icash = icash - price

ray = count()
ray(100) # 900
ray(200) # 700

如果你嘗試改得像 JavaScript 一樣的話只會得到一個 SyntaxError: invalid syntax 的錯誤唷。

那麼透過上面的程式碼示範之後也了解到閉包的概念不外乎也與巢狀函式脫離不了關係。

工廠模式

那麼我們都知道 JavaScript 的閉包進階用法還有一個工廠模式,透過工廠模式我們可以有一些操作方法,例如顯示目前的金額,所以這邊也擷取我先前寫的 JavaScript 閉包工廠模式的範例:

function myMoney(storage) {
  var money = storage;
  return function(price) {
    return { // 使用物件函數的方式來製作功能查詢及扣除餘額
      nowMoney: function () {
        return console.log(money);
      },
      count: function (price) {
        if(money < price) return console.log('餘額不足,目前餘額: ' + money + ' $'); // 當 price 大於目前 餘額 money 就回傳錯誤。
        if (!money <= 0) { // 當 money 等於 0 或是小於 money 就不進入計算。
          return money = money - price;
        }
        return console.log('餘額扣除失敗,目前餘額: ' + money + ' $');
      }
    }
  }
}
// 小明比較窮只儲值 500$
var ming = myMoney(500);
// 小美暴發戶儲值了 5000$
var mei = myMoney(5000);
// 小王不知道哪裡來的錢,儲值了 30000$
var wang = myMoney(30000);

// 小明連三天都花了 500$
ming().count(100);
ming().count(100);
ming().count(300);
//查詢小明目前餘額
ming().nowMoney();
// 小美花了 2300
mei().count(1600);
mei().count(100);
mei().count(600);
//查詢小美目前餘額
mei().nowMoney();
// 小王只花 300
wang().count(300);
// 查詢小王目前餘額
wang().nowMoney();

那麼問題來了 Python 可以寫出一樣的功能嗎?答案是可以的,只是在寫法上必須做一下調整:

def myMoney(storage):
  icash = storage

  def nowMoney():
    return print('目前餘額:', icash)

  def count(price):
    nonlocal icash
    icash = icash - price
    return print('金額扣除後剩餘:', icash)

  return {
    'nowMoney': nowMoney,
    'count': count,
  }

# 小明比較窮只儲值 500$
ming = myMoney(500)
# 小美暴發戶儲值了 5000$
mei = myMoney(5000)
# 小王不知道哪裡來的錢,儲值了 30000$
wang = myMoney(30000)

# 小明連三天都花了 500$
ming['count'](100)
ming['count'](100)
ming['count'](300)
# 查詢小明目前餘額
ming['nowMoney']()
# 小美花了 2300
mei['count'](1600)
mei['count'](100)
mei['count'](600)
# 查詢小美目前餘額
mei['nowMoney']()
# 小王只花 300
wang['count'](300)
# 查詢小王目前餘額
wang['nowMoney']()

雖然與 JavaScript 範例稍微有點不同,我是直接回傳一個字典,但是依然可以達到閉包的效果,因此我們也可以再次驗證只要變數有被持續引用的狀況下,那麼變數記憶體就不會被回收掉,而持續可以被使用唷。

那麼學到現在有沒有再一次體會到想要無痛轉 Python 是有可能的呢?而且 Python 學起來真的很簡單呢~

https://ithelp.ithome.com.tw/upload/images/20210927/201194862WoNtwQ4Cz.png

參考文獻

作者的話

前幾天朋友問我說你最近在忙什麼,我跟他說我忙著準備鐵人賽文章,結果他跟我說現在跑三鐵還要寫文章?

我:????

關於兔兔們

兔法無邊


上一篇
從 JavaScript 角度學 Python(27) - 傳值?傳參考?
下一篇
從 JavaScript 角度學 Python(29) - BeautifulSoup
系列文
從 JavaScript 角度學 Python31

尚未有邦友留言

立即登入留言