从 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

参考文献

作者的话

前几天朋友问我说你最近在忙什麽,我跟他说我忙着准备铁人赛文章,结果他跟我说现在跑三铁还要写文章?

我:????

关於兔兔们

兔法无边


<<:  DAY14-EXCEL统计分析:F分配实例

>>:  Day 14: 人工智慧在音乐领域的应用 (AI作曲-演算法基础概念)

NNI安装後的验证02

前回提及,在浏览器网址,输入 127.0.0.1:8085,进入WebUI管理介面。此时,您将会看到...

Day27 vue.js简易照片上传功能(base64)

延续昨日 今天我们来处理一下新增帐户的照片 这是我们目前的修改帐户页面 首先先把layout做出来 ...

Day04-Vue指令

昨天提到绑定的概念,v-开头的就是Vue下使令语言,今天就要来研究它们到底有哪些还有能做什麽! v-...

Day25 - 加入简单的动画

今天想偷个懒,先为App加入一点动画, 主要是从欢迎页到登入页的过度动画。 Navigation t...

轻松排序!sort 的延伸用法,Ruby 30 天刷题修行篇第十一话

嗨,我是 A Fei,大家周末过得如何?五倍振兴券想好怎麽花了吗?如果没有,可以给我(被揍),啊不是...