Python之闭包

文章目录

    • 一 什么是闭包?
    • 二 闭包的实现
        • 2.1 类方法实现计算某件商品的历史平均销量
        • 2.2 函数实现计算某件商品的历史平均销量
        • 2.3 闭包函数的组成要素
        • 2.4 判断闭包函数
        • 2.5 闭包函数的相关元素查看
    • 三 nonlocal的使用

一 什么是闭包?

在上篇文章:Python变量作用域 里面我们讲了python中的几种变量作用域,其中Enclosed(嵌套)变量作用域说的是 在函数中嵌套函数时,外层函数变量的作用域,其实这也叫闭包。内层函数能够引用外层函数的变量,是因为闭包延伸了外层函数的变量作用域。
所以什么是闭包呢?闭包是一种函数,一种延伸了变量作用域的函数,它会保留定义函数时变量的绑定,当定义变量的作用域不可以用了,那些绑定仍然可以被调用。
具体如下述。

二 闭包的实现

比如要求:计算某件商品的历史平均销量。

2.1 类方法实现计算某件商品的历史平均销量
#类实现计算商品历史平均销量
class Avg_goods():

    def __init__(self):
        #保存历史总和
        self.his_sum = []

	#__call__方法使类实例可以像函数一样被调用
    def __call__(self, num):
        self.his_sum.append(num)
        total = sum(self.his_sum)
        return total/len(self.his_sum)

avg = Avg_goods()
print(avg.his_sum, avg(20))
print(avg.his_sum, avg(21))
print(avg.his_sum, avg(22))
>>>
[20] 20.0
[20, 21] 20.5
[20, 21, 22] 21.0

可以看到,Avg_goods类实现了计算一个商品的历史平均销量,每当有新的值传入时,self.his_sum 变量就会添加保存,然后再计算平均值。
除了类实现,还可以用函数来实现,如下

2.2 函数实现计算某件商品的历史平均销量

Python之闭包_第1张图片

>>>
D:\Anconda3\python.exe C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py
[20] 20.0
[20, 21] 20.5
[20, 21, 22] 21.0
==================================================
20.0
20.5
21.0

可以看到结果和用类实现的一样,用函数也实现了计算商品的历史平均销量。
自由变量:指未在本地作用域绑定的变量。 如上面make_avg函数里面的his_sum变量,它在make_avg函数没有定义,但在avg_goods函数中定义了,所有它是一个自由变量。

其实上面的嵌套函数就是一个闭包函数,它延伸了变量his_sum的作用域,使得在函数make_avg中未定义该变量也能够调用his_sum变量。在非嵌套函数中,局部变量会随着函数被调用完后,而被释放,不能再被调用,如下

def fun1():
    a = 23

def fun2():
    print(a)#调用函数fun1中的变量a,会报错

fun2()
>>>
  File "C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py", line 50, in <module>
    fun2()
  File "C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py", line 48, in fun2
    print(a)#调用函数fun1中的变量a,会报错
NameError: name 'a' is not defined


2.3 闭包函数的组成要素

上面的闭包函数有以下特点
1) 闭包函数内嵌套有其它函数
2) 内嵌函数引用了外层函数的变量
3) 外层函数的返回值是嵌套函数的函数名字

以上3个也是判断闭包函数的三个要素,缺一不可。


2.4 判断闭包函数

可以通过打印 内层函数. __closure __ 的结果来判断是否是闭包函数,如果输出的 _closure_是一个cell对象则是闭包函数,输出的是None则不是闭包函数。如下

#闭包实现计算商品历史平均销量
def avg_goods():
    his_sum = []

    def make_avg(num):
        his_sum.append(num)
        total = sum(his_sum)
        return total / len(his_sum)
    print("判断是否是闭包:", make_avg.__closure__)
    return make_avg

avg = avg_goods()
D:\Anconda3\python.exe C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py
判断是否是闭包: (<cell at 0x00000266EE3501F8: list object at 0x00000266EE3C51C8>,)

2.5 闭包函数的相关元素查看

在用类实现计算商品的历史平均销量时,商品的销量值是存在self.his_sum列表中的,那么用函数实现时销量值是存在his_sum中的吗?其实不是,因为his_sum变量随着函数avg_goods函数被调用后它就不复存在了。
闭包函数的__code__属性(编译后的函数定义体)中保存着闭包函数的局部变量和自由变量的名称,如下

#闭包实现计算商品历史平均销量
def avg_goods():
    his_sum = []

    def make_avg(num):
        his_sum.append(num)
        total = sum(his_sum)
        return total / len(his_sum)

    return make_avg

avg = avg_goods()
print(avg(20))
print(avg(21))
print(avg(22))
print("局部变量:", avg.__code__.co_varnames)
print("自由变量:", avg.__code__.co_freevars)
>>>
20.0
20.5
21.0
局部变量: ('num', 'total')
自由变量: ('his_sum',)

自由变量的值是保存在__closure__[freevars].cell_contents 属性中的,如下

avg = avg_goods()
print("局部变量:", avg.__code__.co_varnames)
print("自由变量:", avg.__code__.co_freevars)
print("自由变量的值:", avg.__closure__[0].cell_contents)
print(avg(15))
print(avg(16))
print("自由变量的值:", avg.__closure__[0].cell_contents)
>>>
局部变量: ('num', 'total')
自由变量: ('his_sum',)
自由变量的值: []
15.0
15.5
自由变量的值: [15, 16]


三 nonlocal的使用

我们知道在函数的局部作用域中一般是不允许修改全局变量的,出除非使用global 关键字声明。如下

var = 34
def fun1():
    #在局部作用域修改全局变量,报错
    var += 1
    print(var)

fun1()
>>>
 File "C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py", line 50, in fun1
    var += 1
UnboundLocalError: local variable 'var' referenced before assignment

可以看到,在局部作用域修改全局变量时报错,但是当使用global关键字声明后再修改就不会报错,如下

var = 34
def fun1():
    #使用global声明
    global var

    var += 1
    print(var)

fun1()
>>>
35

对于闭包函数, 内层函数引用了外层函数的变量,但是不能修改外层变量的值,除非使用nonlocal 关键字声明,如同使用global 修改全局变量。如下
#闭包实现计算商品历史平均销量
def avg_goods():
    his_sum = 0
    total = 0

    def make_avg(num):
        his_sum += num
        total += 1
        return his_sum/total

    return make_avg

avg = avg_goods()
print(avg(10))
print(avg(11))
print(avg(12))
>>>
Traceback (most recent call last):
  File "C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py", line 21, in <module>
    print(avg(10))
  File "C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py", line 14, in make_avg
    his_sum += num
UnboundLocalError: local variable 'his_sum' referenced before assignment

可以看见上面报错和在局部修改全局变量的报错是一样的,如使用nonlocal 关键字声明后,再修改便不会报错,如下

#闭包实现计算商品历史平均销量
def avg_goods():
    his_sum = 0
    total = 0

    def make_avg(num):
        nonlocal his_sum, total
        his_sum += num
        total += 1
        return his_sum/total

    return make_avg

avg = avg_goods()
print(avg(10))
print(avg(11))
print(avg(12))
>>>
D:\Anconda3\python.exe C:/Users/admin/python-learning/python学习文件/python基础/CSDN.py
10.0
10.5
11.0

你可能感兴趣的:(python基础系列,python,闭包,nonlocal)