概念介绍:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
闭包就是内层函数,对外层函数(非全局)的变量的引用,叫闭包 可以让一个局部变量常驻内存,防止其他程序修改这个变量
初始闭包
为了说明闭包中引用的变量的性质,可以看一下下面的这个例子:
python
def f2(func):
def f1():
x = func()
return x + 1
return f1
def func():
print("输出func()函数")
return 1
dec = f2(func)
print(dec())
print(func())
输出结果为:
还可以将 f1( ) 函数的定义移动到 f2( )函数中,这样在 f2( ) 函数外的作用 域就不能直接调用 f1( ) 函数
闭包的概念:
如果内层函数引用了外层函数的变量(包括其参数),并且外层函数 返回内层函数名,这种函数架构称为闭包。
闭包满足的如下3个条件:
内层函数的定义嵌套在外层函数中。
内层函数引用外层函数的变量
外层函数返回内层函数名
闭包陷阱:
python
def my_func(*args):
fs = []
for i in range(3):
def func():
return i * i
fs.append(func)
return fs
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())
上面这段代码可谓是典型的错误使用闭包的例子。程序的结果并不是我们想象的结果0,1,4。实际结果全部是4。
这个例子中,my_func返回的并不是一个闭包函数,而是一个包含三个闭包函数的一个list。这个例子中比较特殊的地方就是返回的所有闭包函数均引用父函数中定义的同一个自由变量。
但这里的问题是为什么for循环中的变量变化会影响到所有的闭包函数?尤其是我们上面刚刚介绍的例子中明明说明了同一闭包的不同实例中引用的自由变量互相没有影响的。而且这个观点也绝对的正确。
那么问题到底出在哪里?应该怎样正确的分析这个错误的根源。
其实问题的关键就在于在返回闭包列表list之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数my_func返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。所以要避免这个情况。
可以通过下面这种方式进行0、1、4的输出,从而使得这个返回s的外部函数不冲突
python
def my_func(*args):
fs = []
for i in range(3):
def func(i = i):
return i * i
fs.append(func)
return fs
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())
闭包的应用:
闭包(closure)是指在一个函数内部定义的函数,并且这个内部函数可以访问到其外部函数的局部变量。Python中闭包的应用场景很多,主要可以用于以下几个方面:
保持状态信息:闭包可以用来保持某些信息的状态,使得函数每次调用时可以记住上次调用的结果或状态。这在某些需要记忆或记录状态的场景中非常有用。
python
def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
c = counter()
print(c()) # 输出 1
print(c()) # 输出 2
print(c()) # 输出 3
在这个例子中,内部函数inner
形成了闭包,可以访问并修改外部函数counter
的局部变量count
,从而实现了计数的功能。
封装私有变量:通过闭包,可以创建私有变量或函数,这些变量和函数对外部是不可见的,从而实现类似于面向对象编程中私有成员的效果。
python
def private_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
def decrement():
nonlocal count
count -= 1
return count
return increment, decrement
inc, dec = private_counter()
print(inc()) # 输出 1
print(inc()) # 输出 2
print(dec()) # 输出 1
在这个例子中,increment
和decrement
函数形成了闭包,可以访问并修改private_counter
函数的局部变量count
,但这个count
对外部是不可见的。
实现装饰器:装饰器本质上就是闭包,它可以在不修改原函数代码的情况下,给函数添加额外的功能
python
def logger(func):
def wrapper(*args, **kwargs):
print(f'Calling function {func.__name__} with args {args} and kwargs {kwargs}')
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
return a + b
print(add(3, 5)) # 输出 Calling function add with args (3, 5) and kwargs {},以及 8
这里的logger
函数是一个闭包,它接受一个函数作为参数,返回一个新的函数wrapper
,实现了在调用add
函数时打印日志的功能。