闭包,是函数式编程中的一个较难理解的概念,JavaScript、Python、Perl、Object-C等很多语言都支持闭包。
说白了, 闭包就是函数嵌套函数,在函数中定义并返回另一个函数。
例如:
def func1():def func2():
pass
return func2
通常情况下,我们的函数直接定义在模块中,模块中的变量称为全局变量,用户及任何函数都可以直接使用并修改。
如果把模块比作大厅的话,那么闭包就是包间。闭包中的变量,则只有内部函数能直接使用。
例如:
printer1 = '打印机1' # 全局变量,用户及所有函数可以使用和修改def room1(): # 闭包函数
printer2 = '打印机2' # 闭包函数中的变量称为 自由变量
def a():
pc1 = '电脑1' # 局部变量,只有当前函数使用
print('我可以使用全局变量', printer1)
print('我可以使用自由变量', printer2)
print('我可以使用自己的局部变量', pc1)
return a # 不return 函数 a,则用户没办法使用这个函数
def b():
pc1 = '电脑2' # 局部变量,只有当前函数使用
print('我可以使用全局变量', printer1)
print('我可以使用自己的局部变量', pc2)
a = room1() # 调用闭包函数返回内部函数
a() # 内部函数可以使用room1中的变量printer2
b() # 调用普通函数b,b没办法使用room1中的变量
作用域链的原则是内部可以使用外部变量,外部不能使用内部变量。
使用闭包函数可以为函数开辟一个独立空间,其中的变量外部无法直接使用,只能通过函数a间接使用。因此闭包可以起到隐藏变量的作用,可以防止变量被修改和破坏。
为什么闭包函数room1中的变量printer2不是局部变量而称为“自由变量”呢?
我们知道,函数调用结束后即终止,其中的局部变量会被清理。如调用b函数结束后,其中的局部变量pc1就会清理回收(下次调用重新创建)。
而闭包函数room1调用后得到a函数,此时room1函数已经调用结束,但由于a函数还要使用变量printer2,因此printer2不会被清理,而是一直为a函数保留着。
因此,闭包会加大内存开销,甚至造成内存泄露。
然而隐藏和保护变量是很重要的,比如一个存钱和取钱的功能,两个都使用需要一个名为balance的余额变量,如果使用全局变量,示例如下:
balance = 0 # 初始余额def save(amount): # 存钱
global balance # 非容器全局变量,需要显示声明其为全局变量才能修改
if amount > 0:
balance += amount # 余额增加
print('存入 %d 当前余额 %d' %(amount, balance))
else:
print('amount必须大于0')
def draw(amount): # 取钱
global balance
if amount > 0:
if balance > amount:
balance -= amount # 余额减少
print('取出 %d 当前余额 %d' % (amount, balance))
else:
print('余额不足')
else:
print('amount必须大于0')
amount = 1000000 # 直接修改余额
draw(200)
对于非容器(如列表、字典)类型的变量balance,不能直接进行修改。
在函数中如果直接使用如balance += amount则会视为新建了一个同名的局部变量。
因此需要使用global balance进行声明其为全局变量。
使用全局变量的危险是,用户可以直接修改amount的值,不经过save存钱,便可任意draw取钱。
我们可以使用闭包来隐藏balance变量,例如:
def bank():balance = 0 # 初始余额
def save(amount): # 存钱
nonlocal balance # 声明不是save中的局部变量,即bank中的自由变量balance
if amount > 0:
balance += amount # 余额增加
print('存入 %d 当前余额 %d' %(amount, balance))
else:
print('amount必须大于0')
def draw(amount): # 取钱
nonlocal balance # 声明是bank中的自由变量
if amount > 0:
if balance > amount:
balance -= amount # 余额减少
print('取出 %d 当前余额 %d' % (amount, balance))
else:
print('余额不足')
else:
print('amount必须大于0')
return save, draw # 返回两个函数,以使得用户可以使用这两个函数
save, draw = bank() # 使用闭包得到两个功能函数
save(100) # 存钱
draw(200) # 取钱 (显示余额不足)
同全局变量一样,如果save、draw中直接修改balance,同样会视为新建了一个同名的局部变量,此时需要使用nonlocal来声明其不是局部变量。
为什么我们在Python中很少用到闭包?因为Python还支持类和对象呀,使用类可以轻松实现独立空间和变量私有问题。
在Python中,闭包常用作装饰器,在一个装饰器函数中定义一个新的函数来替代原函数。