Python函数式编程——闭包
1.什么是闭包
什么是闭包
:一个函数定义中引入了函数定义以外的变量,并且该函数可以在其定义以外被执行,这样的一个函数称为闭包。
闭包的三个条件,缺一不可
"""
1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3)外部函数必须返回内嵌函数——必须返回那个内部函数
"""
闭包代码实现:
# -*- coding: utf-8 -*-
# @File : 闭包.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 15:58
# 定义一个函数
def fun_a(num_a):
# 在函数内部再定义⼀个函数
# 并且这个内部函数⽤到了外部的变量,这个函数以及⽤到外部函数的变量及参数叫 闭包
def fun_b(num_b):
print('内嵌函数fun_b的参数是:%s,外部函数fun_a的参数是:%s' % (num_b, num_a))
return num_a + num_b
# 这里返回的就是闭包的结果
return fun_b
# 给fun_a函数赋值,这个10就是传参给fun_a
ret = fun_a(10)
# 注意这里的10其实是赋值给fun_b
print(ret(10))
# 注意这里的90其实是赋值给fun_b
print(ret(90))
此时,内部函数对外部函数作⽤域⾥变量的引⽤(⾮全局变量),则称内部函数为闭包。
2.闭包修改外部变量
python
交互环境idle
>>> def counter(start = 0):
count = [start]
def incr():
count[0] += 1
return count[0]
return incr
>>> c1 = counter(5)
>>> print(c1())
6
>>> print(c1())
7
>>> print(c2())
51
>>> print(c2())
52
>>>
当一个函数在本地作用域找不到变量申明时会向外层函数寻找,这在函数闭包中很常见但是在本地作用域中使用的变量后,还想对此变量进行更改就会报错。
看一段代码:
# -*- coding: utf-8 -*-
# @File : 闭包修改外部变量.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:30
# 闭包修改外部变量的值
def test1():
c = 1
# c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识
def add1():
print(c) # 1
c += 1
return c # 2
return add1
print(test1()())
报错信息:
此时,如果我在函数内加一行nonlocal c
就可解决这个问题
代码:
# -*- coding: utf-8 -*-
# @File : 闭包修改外部变量.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:30
# 闭包修改外部变量的值
def test1():
c = 1
# c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识
def add1():
nonlocal c
print(c) # 1
c += 1
return c # 2
return add1
print(test1()())
nonlocal
声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量(介于局部变量和全局变量之间的一种变量)。
使用
nonlocal
的好处是,在为函数添加状态时不用额外地添加全局变量,因此可以大量地调用此函数并同时记录着多个函数状态,每个函数都是独立、独特的。
3.闭包的应用
闭包实现 y = a*x + b
# -*- coding: utf-8 -*-
# @File : 闭包的应用.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:18
# y = a*x + b
def create_line(a, b):
def line(x):
return a * x + b
return line
line1 = create_line(1, 1) # a:1 b:1
line2 = create_line(4, 5) # a:4 b:5
print(line1(5)) # 6
print(line2(5)) # 25
从这段代码中,函数line
与变量a,b
构成闭包。在创建闭包的时候,我们通过create_line
的参数a,b
说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1
和y = 4x + 5
)。我们只需要变换参数a,b
,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提⾼代码可复⽤性的作⽤。
如果没有闭包,我们需要每次创建函数的时候同时说明
a,b,x
。这样,我们就需要更多的参数传递,也减少了代码的可移植性。
1.闭包优化了变量,原来需要类对象完成的⼯作,闭包也可以完成
2.由于闭包引⽤了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
4.闭包的陷阱
函数内部函数,引用外部函数参数或值,进行内部函数运算执行,并不是完全返回一个函数,也有可能是一个在外部函数的值,我们还需要知道返回的函数不会立刻执行,而是直到调用了函数才会执行。
看代码:
# -*- coding: utf-8 -*-
# @File : 闭包的陷阱.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 17:09
def fun_a():
fun_list = []
for i in range(1, 4):
def fun_b():
return i * i
fun_list.append(fun_b)
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 9 9 9
这里创建了一个fun_a
函数,外部函数的参数fun_list
定义了一个列表,在进行遍历,循环函数fun_b
,引用外部变量i 计算返回结果,加入列表,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了,但是实际结果并不是我们想要的1
,4
,9
,而是9
,9
,9
,这是为什么呢?这是因为,返回的函数引用了变量
i
,但不是立刻执行。等到3
个函数都返回时,它们所引用的变量i
已经变成了3
,每一个独立的函数引用的对象是相同的变量,但是返回的值时候,3
个函数都返回时,此时值已经完整了运算,并存储,当调用函数,产生值不会达成想要的,返回函数不要引用任何循环变量,或者将来会发生变化的变量,但是如果一定需要呢,如何修改这个函数呢?
我们fun_b(
)把这里的参数i
赋值给x
就可以解决
# -*- coding: utf-8 -*-
# @File : 闭包的陷阱.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 17:09
def fun_a():
fun_list = []
for i in range(1, 4):
def fun_b(x=i):
return x ** 2
fun_list.append(fun_b)
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 1 4 9
可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,那我们就可以完成下面的代码:
# -*- coding: UTF-8 -*- #
def fun_a():
def fun_c(i):
def fun_b():
return i * i
return fun_b
fun_list = []
for i in range(1, 4):
# f(i)立刻被执行,因此i的当前值被传入f()
fun_list.append(fun_c(i))
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 1 4 9