浅谈Python闭包

闭包是在其词法上下文中引用了自由变量的函数。

通俗地说,就是函数嵌套(后续称之为外层函数)另外一个函数(后续称之为内层函数),在内层函数中,引用外层函数的变量,每次对内层函数的调用,外层函数变量的值都会进行保持。

用个简单的例子来说明,使用闭包实现一个函数,求所有传入的数字的平均值。

def averager():
    """
    闭包实现求平均值的例子
    每次传入一个数字,返回所有传入的数字的平均值
    :return:
    """
    count = 0
    total = 0.0

    def _averager(value):
        nonlocal total, count
        total += value
        count += 1
        average = total/count
        return average

    return _averager

# 调用外层函数averager,得到内层函数_averager的对象,赋值给avg
# 此时闭包形成,外层函数的变量total, count, average会被保持
avg = averager()
# 每次调用内层函数avg时,外层函数变量的值都会被记住
# 第一次调用,外层函数的变量count=1, total=10.0, average=10.0
print(avg(10))
# 10.0
# 第二次调用,外层函数变量的值不会重置,仍然保持上次调用的结果
# 此时 count=2, total=30.0, average=15.0
print(avg(20))
# 15.0
# 第三次调用,外层函数变量的值仍然会保持上次调用的结果
# 此时 count=3, total=36.0, average=12.0
print(avg(6))
# 12.0

上面的例子中,最重要的部分在调用外层函数averager时,需要返回内层函数的对象_averager,注意这里的return _averager没有括号,并不是调用内层函数,而是返回内层函数的对象。

avg_cls = Averager()当调用外层函数,返回内层函数对象时,即形成闭包。

avg(10)每次对内层函数的调用,外层函数中的变量的值,都会被记住,即保持上次调用的结果。

如果不容易理解,可以用类实例实现类似上面闭包的功能,实际执行中,因为类实例self的参与,运行速度要略慢于闭包的实现。

# 以类实现类似上面闭包的功能
class Averager:

    def __init__(self):
        self.count = 0
        self.total = 0.0

    def __call__(self, value):
        self.total += value
        self.count += 1
        average = self.total/self.count
        return average

avg_cls = Averager()

print(avg_cls(10))
# 10.0
print(avg_cls(20))
# 15.0
print(avg_cls(6))
# 12.0

上面这个使用类实现的例子中,变量count和total,都保存在类实例avg_cls中,每次对类实例的调用,都会调用__call__方法计算平均值,因为是同一个实例,所以count和total的值都会存储在实例中。

闭包也是类似的效果,变量count和total也是存储在外层函数的上下文中,所以每次对内层函数调用,外层函数的变量不会被销毁,而是一直保持初始值(未调用内层函数时)或上次调用的结果(每次调用内层函数后,对count和total的修改都会被存储)。

posted @ 2018-01-17 00:05  BlackMatrix  阅读(442)  评论(0编辑  收藏  举报