定义
**闭包是指在函数内部定义的函数,并且该函数内部可以访问外部函数的变量。**具体来说,当一个内部函数引用了外部函数的变量时,即使外部函数已经执行完毕,这些变量仍然会被保留在内存中。
Python中的闭包通常用于实现一些高级特性,如装饰器、生成器等。
闭包有以下特点:
1.内部函数可以访问外部函数的变量:闭包中的内部函数可以引用外部函数中定义的变量,即使外部函数已经执行完毕,这些变量仍然会保留在内存中。
2.外部函数返回内部函数:通常情况下,外部函数会将内部函数作为返回值返回给调用者,从而形成一个闭包。
3.闭包保持了对外部变量的引用:由于闭包中的内部函数引用了外部变量,所以这些外部变量不会被垃圾回收机制回收,而是保存在闭包中。
一、闭包
在Python中,闭包就是指一个函数(内部函数)能够引用其外部作用域中定义的非全局变量。简单来说,闭包就是由函数及其相关的引用环境组合而成的实体。
1.1 闭包的构成条件
1. 函数嵌套
2. 内部函数能够使用外部函数的变量(包括外部函数接收的参数)
3. 外部函数返回内部函数的引用。
调用和引用
从代码书写格式来看,函数调用就是在函数名后加上括号()
,引用就是不加括号;函数调用时会立即执行函数,而引用时不会执行。
从深层次来看,函数引用实际上就是将这个函数的内存地址赋给了某个变量。这样就可以通过某个变量来调用这个函数了。如下:
def say_hello():
print("Hello, World!")
# 将say_hello函数赋值给greeting变量
greeting = say_hello
# 调用greeting变量所引用的函数
greeting()
在上面的例子中,我们定义了一个名为say_hello
的函数,然后将其赋值给了greeting
变量。接着,我们通过调用greeting()
来调用被引用的say_hello
函数。
需要注意的是,在赋值过程中不要加上括号,因为加上括号会直接执行该函数并将结果赋给变量。而我们想要的是将整个函数作为对象进行传递和操作。
1.2 闭包的使用
案例一:小红询问小美到了什么地方
步骤:
- 定义外部函数,外部函数需要接收发送方参数(姓名)
- 定义内部函数,接收回答方参数(姓名)
- 将对话信息进行拼接
def outer(name):
# 定义内部函数,参数是 说话的信息
print(f'{name}:到北京了吗?')
def inner(name1):
# 内部函数中,将name和info进行拼接输出
print(f'{name1}:已经到了,放心吧。')
# 外部函数返回内部函数的地址
return inner
func = outer('小红')
func('小美')
案例二:两数相加
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
print(closure(5)) # 输出15
在上面的例子中,我们定义了一个名为say_hello
的函数,然后将其赋值给了greeting
变量。接着,我们通过调用greeting()
来调用被引用的say_hello
函数。
需要注意的是,在赋值过程中不要加上括号,因为加上括号会直接执行该函数并将结果赋给变量。而我们想要的是将整个函数作为对象进行传递和操作。
1.3 闭包修改外部函数变量
在函数内部是不能直接对外部变量进行修改的(不论是否是闭包),如果在函数内部要修改全局变量的话需要使用到global
关键字将使用的变量声明为全局变量才能够进行修改使用。而在闭包结构中,内部函数如果想要使用、修改外部函数的局部变量的话就需要使用nonlocal
关键字将其声明为外部函数的局部变量才可。如下:
def outer():
num = 10
def inner():
# num = 100 # 不是修改外部变量的值,重新定义的局部变量
nonlocal num # 声明使用外部变量 num 不重新定义
num = 100
print(f'调用inner之前:{num}')
inner()
print(f'调用inner之后:{num}')
return inner
func = outer()
func()
在上方代码中,内部函数中num
在使用nonlocal
声明之前进行赋值100,世纪上是对变量num的重新声明,同时其作用域也仅仅是在内部函数中。
二、装饰器
装饰器的本质就是一个闭包函数,能够在不修改已有函数(被装饰函数)源代码的情况下对函数进行额外功能的添加或修改其原有的功能。
装饰器本身是一个高阶函数,能够接收一个函数作为参数,并且返回一个新的函数。这个新的函数在调用原有的函数之前或之后会执行一些额外的函数,或者替换原有函数的实现。
2.1 装饰器的结构
通过上方的定义我们知道装饰器本质上就是闭包,那么在实现的时候首先要考虑的就是闭包的结构如何去实现,如回家时密码门需要验证密码,代码示例如下:
def login_check(fn):
def inner():
print("密码验证进行中....")
fn()
return inner
def home():
print("欢迎回家!!!")
上方代码给出了两个函数,其中home函数就是主函数,要回家首先要验证密码,而login_check函数就是验证密码的函数,也就是说login_check函数需要在home函数执行前执行,同时根据其执行的结果来决定home函数是否要正常执行,假设密码为123,上方代码可修改为如下:
def login_check(fn):
password = input("请输入密码:")
def inner():
print("密码验证进行中....")
nonlocal password
if password == "123":
fn()
else:
print("对不起,密码输入错误!")
return inner
def home():
print("欢迎回家!!!")
2.2 装饰器的使用
装饰器定义好之后,如果作用于指定的函数呢?
1.闭包调用法
首先,装饰器的本质还是函数,所以直接调用,然后将home函数作为参数传递到装饰器中,外部函数调用了之后返回了内部函数的调用,因此再添加一个括号就可,调用格式为:login_check(home)()
,完整代码如下:
def login_check(fn):
password = input("请输入密码:")
def inner():
print("密码验证进行中....")
nonlocal password
if password == "123":
fn()
else:
print("对不起,密码输入错误!")
return inner
def home():
print("欢迎回家!!!")
login_check(home)()
执行结果:
除此外,使用更多的还是语法糖的形式。
语法糖调用
装饰器的语法糖用法是在被装饰函数上按@装饰器名
的格式来对被装饰函数进行装饰,如下:
def login_check(fn):
password = input("请输入密码:")
def inner():
print("密码验证进行中....")
nonlocal password
if password == "123":
fn()
else:
print("对不起,密码输入错误!")
return inner
@login_check
def home():
print("欢迎回家!!!")
home()
使用语法糖的时候直接调用被装饰函数即可,效果与闭包调用的时候是一样的。
多个装饰器的使用
def login_check(fn):
password = input("请输入密码:")
def inner():
print("密码验证进行中....")
nonlocal password
if password == "123":
print("密码验证成功!")
fn()
else:
print("对不起,密码输入错误!")
return inner
def face_check(fn):
def inner():
print("记得换鞋哦~")
fn()
return inner
@login_check
@face_check
def home():
print("欢迎回家!!!")
home()
多个装饰器装饰同一个函数,装饰顺序是就近原则,谁离原函数近,就先装饰谁。
思考:装饰器要传参的话应如何实现?(结合闭包的特点来思考)