要先看python高阶函数
闭包
闭包的必要条件:
- 外部函数必须 返回 一个 函数对象
- 返回的函数对象内,必须引用 外部函数 的 形参 或 局部变量
返回的函数就称为: 闭包
注意:
- 不是全局变量
- 返回的函数对象内,不一定要return
闭包的作用
- 实现装饰器(后面会讲)
- 可以保存函数局部变量
改造上一节的例子
def create_add(i,j):
def fun():
return i+j
'''
注意返回的是fun,后面没有小括号
有小括号表示调用函数
'''
return fun
说明:
在函数create_add()内定义了 内部函数fun() ,在fun()函数中 引用了外部函数 create_add()的 形参 或者 局部变量 ,并将 fun返回
注意:
返回的是 fun,后面 不能有小括号 ,有小括号 表示 调用函数
测试调用:
#调用create_add()时,返回的并不是求和结果,而是求和函数
add=create_add(1,1)
#打印函数的内存地址
print(add)
#后面加小括号表示调用
print(add())
另一种调用方式:
print(create_add(1,1)())
print(create_add(2,2)())
查看闭包引用的外部变量
函数对象的__closure__属性返回的是一个元组对象,包含了 闭包 引用的 全部 外部变量
print(add.__closure__)
将闭包函数改造如下:
def create_add(i,j):
def fun():
print("i的地址:%x"%id(i))
print("j的地址:%x"%id(j))
return i+j
'''
注意返回的是fun,后面没有小括号
有小括号表示调用函数
'''
return fun
总结:
内部函数 使用了 外部函数 的 形参 或 局部变量 ,那么 内部函数 就称为 闭包 closure
可以保存函数局部变量
见下面的代码,在函数内部对全局变量进行修改
sum=0#全局变量
def test(i):
#通过global关键字表示sum为全局变量
global sum
sum+=i
test(1)
print(sum)
test(2)
print(sum)
test(3)
print(sum)
上面的代码使用了全局变量,做为模块,被其他功能import时,会引起变量名冲突,可以通过闭包方式隐藏掉。
见如下代码:
def test():
sum=0#局部变量
def fun(i):
#通过nonlocal关键字表示sum为外部函数定义的变量
nonlocal sum
sum+=i
return sum
return fun
#调用执行
t=test()
print(t(1))
print(t(2))
print(t(3))
总结:
test()函数的局部变量sum,可以保存住数据
闭包的注意事项
看下面的代码,并心算出结果,再编写下面的代码,查看执行结果
i=0
def create_fun():
def fun():
return i+i
return fun
def create_fun2(j):
def fun2():
return j+j
return fun2
f=create_fun()
f2=create_fun2(i)
i=1
print(f())
print(f2())
说明:
#只是返回fun函数的引用,但并没有运行fun函数
f=create_fun()
'''
将变量i(值为0)传给create_fun2函数,并返回fun2函数的引用,并没有运行fun2函数
注意:fun2函数内部引用外部函数的形参j,此时j的值就是0
'''
f2=create_fun2(i)
i=1
'''
执行fun函数,fun函数引用全局变量i,此时i的值是1,所以结果是2
'''
print(f())
'''
执行fun2函数,fun2函数内的变量j,值为0,所以结果是0
'''
print(f2())
经典的for循环闭包问题
看下面的代码,内部函数引入外部函数的局部变量,进行循环运算
返回的是list,里面的元素是函数
def create_fun():
funs=[]
for item in range(0,3):
def fun():
return item+item
funs.append(fun)
return funs
执行下面代码,心算运行结果,并做演练
funs=create_fun()
print(funs[0]())
print(funs[1]())
print(funs[2]())
结果 都是4
错误原因:
返回的fun函数引用了 外部函数 的 局部变量 ,但fun函数 不是立刻执行的 。等到 3个函数都返回时,它们所引用的 变量item已经变成了2,因此最终结果都是4
注意:
闭包引用外部函数的内部变量要小心
解决方式1-三层函数
通过 三层函数 解决,创建 第二层内部函数 (有形参),原闭包函数放入该第二层函数内(即:第三层内部函数),第三层函数内引用第二层函数的 形参,在for语句中执行第二层函数,无论该循环中变量如何更改,第二层函数 形参的值不变
def create_fun():
'''
再创建带形参的函数,包住真正运算的函数,并返回真正运算的函数。
函数有三个:外部、中部、内部
'''
def f(i):
def fun():
return i+i
return fun
funs=[]
for item in range(0,3):
'''
让中部函数立刻执行,因此循环变量item的当前值传入中部函数(),真正添加的是内部函数
'''
funs.append(f(item))
return funs
解决方式2-通过列表生成式
def create_fun():
def wrapper(j):
def fun():
return j + j
return fun
funs=[ wrapper(item) for item in range(0,3) ]
return funs
测试调用
funs=create_fun()
print(funs[0]())
print(funs[1]())
print(funs[2]())
练习
改造统计课表作业,作业使用类实现,现在改用纯函数实现,并且不要使用全局变量(作为模块被其他代码import时,会引起变量名冲突)