闭包的定义

  • 闭包函数是指函数内部的函数。闭包结构至少有二层,外层函数必须返回内层函数对象。
  • 内层函数要引用外层函数的变量(一般是外层函数的参数),而内层函数不一定要return。

闭包的逻辑

理解闭包的逻辑之后,有了一定python基础的都可以很容易写出闭包。下面通过案例来讲一下闭包的逻辑。

def discount(x): # 外层函数检查打折参数是否合理

if x<0.5 or x>1: # 打折参数<0.5或大于1时被判定为不合理,不会执行后续的计算内函数

return None

def count(prince, number): # 内函数根据商品单价和数量以及折扣计算应付款和实付款

result = prince * number

pay = result * x

print(f'总价是{result}元,实付{pay}元')

return count

discount(0.8)(2.88, 100) # 调用折扣函数计算8折2.88元单价100件商品应付款和实付款

out:

总价是288.0元,实付230.4元




python 闭包陷阱 python 闭包函数_python 闭包

定义商品计算应付款和实付款的计算函数



以上代码是销售商品时经常遇到的案例,discount函数是外层函数,用来检测打折数字是否合理。count是内层函数,根据参数单价和数量以及外层函数的参数折扣来计算应付款和实收款。

  1. 解释器遇到def discount时会在全局命名空间增加一个discount对象,它的值指向discount函数内的代码块,此时discount函数没有运行。
  2. 解释器跳过def discount下属的代码块内容后遇到discount(0.8)(2.88, 100),开始运行函数discount(0.8)。
  3. 执行完if语句块后遇到def count,此时在discount的局部命名空间增加一个count对象,它的值指向count函数内的代码块。
  4. 执行到return count时会把count对象返回给discount(0.8)(2.88,100),此时discount(0.8)部分已经运行完毕。
  5. 此时接收到discount返回的count对象,接下来运行的是count(2.88,100)。
  6. count(2.88,100)内部代码块运行完毕后,整个闭包函数运行完毕。



python 闭包陷阱 python 闭包函数_代码复用_02


闭包的扩展

我们可以通过如下的方式对闭包进行扩展,更加方便使用:

def discount(x): # 外层函数检查打折参数是否合理

if x<0.5 or x>1: # 打折参数<0.5或大于1时被判定为不合理,不会执行后续的计算内函数

return None
 def count(prince, number): # 内函数根据商品单价和数量以及折扣计算应付款和实付款
 result = prince * number
 pay = result * x
 print(f'总价是{result}元,实付{pay}元')
 return count
goldcard = discount(0.7) # 定义金卡客户函数,打7折
silvercard = discount(0.9) # 定义银卡客户函数,打9折
commomcard = discount(1) # 定义普通客户函数,不打折
goldcard(2.88, 100)
silvercard(2.88, 100)
commomcard(2.88,100)
print(id(goldcard)) # 查看金卡函数的内存id
print(id(silvercard)) # 查看银卡函数的内存id
print(id(commomcard)) # 查看普卡函数的内存id
out:
总价是288.0元,实付201.6元
总价是288.0元,实付259.2元
总价是288.0元,实付288.0元
1986802704096
1987081368336
1987081368192


python 闭包陷阱 python 闭包函数_python 闭包_03

定义金卡8折、银卡9折、普卡不打折函数


总结

  1. 将外层函数加参数赋值给另外一个变量,相当于定义了一个新的函数。这样可以大大提高代码复用率。例如前面案例中的goldcard、silvercard、commomcard这3个函数虽然都指向了discount函数,但因为它们的参数不同,实际上是3个独立的对象,通过print(id())可以看到它们的id各不相同
  2. 闭包被外层函数返回时,它需要引用的外层变量已经固定,形成了一个封闭的对象,当然闭包的参数例外。如果要修改外层函数的变量需要申明nonlocal。
  3. 熟练掌握闭包的概念和结构特性是理解并掌握装饰器的基础。
  4. 闭包是提高代码复用率的利器,如果发现某段代码经常需要复制粘贴,可以考虑是否可以写成闭包结构。