python函数闭包

Posted on 2021-11-06 14:36  foghorn  阅读(150)  评论(0编辑  收藏  举报

闭包函数

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。——维基百科

闭包函数的条件

  • 闭包函数必须有内嵌函数
  • 闭包函数必须引用外层函数的变量
  • 闭包函数返回内嵌函数的地址(函数名称)

闭包函数示例

def first():
    age = 10

    def second():  # 内嵌函数
        print(age)  # 引用外层函数的变量

    return second  # 返回内嵌函数的函数名

我们称上述函数second为一个闭包函数

判断一个函数是否为闭包

我们可以通过函数名.closure 在函数为闭包时,返回一个cell ,如果不是闭包函数则返回None

def first():
    age = 10

    def second():  # 内嵌函数
        print(age)  # 引用外层函数的变量
    print(second.__closure__)

    return second  # 返回内嵌函数的函数名

>>> s = first()
>>> s()
(<cell at 0x0000027BBE7A7318: int object at 0x00007FF9120FFB60>,)
10
def first():
    age = 10

    def second():  # 内嵌函数
        print(10)  # 引用外层函数的变量
    print(second.__closure__)

    return second  # 返回内嵌函数的函数名

>>> s = first()
>>> s()
None
10

second不是闭包函数,因为没有引用外层函数的变量

def first(age):
    def second():  # 内嵌函数
        print(age)  # 引用外层函数的变量
    print(second.__closure__)

    return second  # 返回内嵌函数的函数名

>>> s = first(100)
>>> s()
(<cell at 0x0000016A608D6408: int object at 0x00007FF9120FFB60>,)
100

second是闭包函数,引用了外层函数的参数

闭包函数的作用

开篇说到闭包函数被多次调用的过程中,其引用的外层变量能够保持其持久性,我们以一个实例来理解这一点。
假如有一个名为avg的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新的价格,因此平均值要考虑目前为止所有的价格。

实现方式一

class Averager():

    def __init__(self):
        self.series = []

    def __call__(self, new_val):
        self.series.append(new_val)
        total = sum(self.series)

        return total / len(self.series)

>>> avg = Averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

实现方式二

def make_averager():
    series = []
    
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total / len(series)
    
    return averager


>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

nonlocal声明

avg函数的另一种实现

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager


>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'count' referenced before assignment

问题是,当count是数字或者任何不可变类型时,count += 1语句的作用其实与count = count + 1一样。因此,我们在averager的定义体中为count赋值了,这会把count变成局部变量。total变量也受这个问题影响。
上一个代码片段没有出现这个问题,因为我们没有给series赋值,我们只是调用series.append,并把它传递给sun和len。也就是说,我们利用了列表是可变对象这一事实。
但是对于数字、字符串、元组等不可变对象来说,只能读取,不能更新。如果尝试重新绑定,例如count = count + 1,其实会隐式创建局部变量count,这样,count就不是自由变量了,因此不会保存在闭包中。为了解决这个问题,python3引入了nonlocal声明。

def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count
    return averager

Copyright © 2024 foghorn
Powered by .NET 9.0 on Kubernetes