文章目录
一、闭包
闭包的理解: 闭包是函数和其周围状态的引用捆绑在一起形成的,闭包使得函数可以记住并访问它的词法作用域,即使函数是在当前词法作用域之外执行。
闭包其实就是函数内部定义的函数,并且这个内部函数可以访问外部函数的变量。
更简单理解:函数包着函数。
1.1 举个栗子
def outer_function():
x = 10
def inner_function():
print(x)
return inner_function
out = outer_function()
out()
输出结果:
10
其实就是返回了inner_function
这个内部函数,且这个内部函数可以访问outer_function
这个外部函数的变量。
1.2 可以用来干啥?
闭包的用途:
- 闭包可以用来创建函数工厂
- 闭包可以用来装饰函数
- 闭包可以用来实现回调函数
1.3 用闭包来创建函数工厂
在 Python 中,闭包(closure)是指一个函数捕获并保存了其外部作用域的变量,即使外部作用域已经执行完毕。利用闭包,可以创建函数工厂,即一个生成特定函数的函数。
示例:创建函数工厂
假设我们要创建一个函数工厂,生成将输入值乘以特定因子的函数。
def create_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
# 使用函数工厂创建特定函数
double = create_multiplier(2)
triple = create_multiplier(3)
# 调用生成的函数
print(double(5)) # 输出: 10
print(triple(5)) # 输出: 15
解释
create_multiplier
函数:这是一个函数工厂,接受一个参数factor
,并返回一个内部函数multiplier
。multiplier
函数:这是一个闭包,捕获了外部函数create_multiplier
的factor
变量。即使create_multiplier
执行完毕,multiplier
仍然可以访问factor
。- 生成特定函数:通过调用
create_multiplier(2)
,我们生成了一个将输入值乘以 2 的函数double
;通过调用create_multiplier(3)
,我们生成了一个将输入值乘以 3 的函数triple
。 - 调用生成的函数:
double(5)
返回10
,triple(5)
返回15
。
闭包的关键点
- 捕获变量:闭包捕获并保存了外部函数的变量,即使外部函数已经执行完毕。
- 灵活性:通过闭包,可以动态生成具有不同行为的函数。
另一个示例:创建计数器
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
# 使用函数工厂创建计数器
counter1 = create_counter()
counter2 = create_counter()
print(counter1()) # 输出: 1
print(counter1()) # 输出: 2
print(counter2()) # 输出: 1
print(counter2()) # 输出: 2
解释
create_counter
函数:这是一个函数工厂,返回一个内部函数counter
。counter
函数:这是一个闭包,捕获了外部函数create_counter
的count
变量。每次调用counter
时,count
都会增加 1。生成计数器:通过调用
create_counter()
,我们生成了两个独立的计数器counter1
和counter2
,它们各自维护自己的count
变量。调用计数器:
counter1
和counter2
分别计数,互不干扰。
总结
通过闭包,可以创建灵活的函数工厂,生成具有特定行为的函数。闭包的核心在于捕获并保存外部作用域的变量,使得生成的函数可以在后续调用中继续使用这些变量。
1.4 用闭包来实现回调函数
闭包(closure)在 Python 中非常适合用来实现回调函数(callback function)。回调函数是指一个函数作为参数传递给另一个函数,并在特定事件或条件发生时被调用。闭包可以捕获外部作用域的变量,使得回调函数能够访问和操作这些变量,从而实现更灵活的功能。
以下是一个使用闭包实现回调函数的示例:
示例:使用闭包实现回调函数
假设我们有一个事件处理器,当某个事件发生时,调用回调函数并传递事件的相关数据。我们可以使用闭包来创建回调函数,并在回调函数中访问外部作用域的变量。
def event_handler(callback):
def handle_event(event_data):
print(f"事件已触发,数据: {event_data}")
# 调用回调函数,并传递事件数据
callback(event_data)
return handle_event
# 创建一个回调函数
def my_callback(data):
print(f"回调函数接收到数据: {data}")
# 使用闭包创建事件处理器
handler = event_handler(my_callback)
# 模拟事件触发
handler("用户登录")
handler("文件下载完成")
输出结果:
事件已触发,数据: 用户登录
回调函数接收到数据: 用户登录
事件已触发,数据: 文件下载完成
回调函数接收到数据: 文件下载完成
代码解释
event_handler
函数:- 这是一个高阶函数,接受一个回调函数
callback
作为参数。 - 它返回一个内部函数
handle_event
,这是一个闭包,捕获了外部作用域的callback
变量。
- 这是一个高阶函数,接受一个回调函数
handle_event
函数:- 这是一个闭包,捕获了
callback
。 - 当事件触发时,
handle_event
被调用,并接收事件数据event_data
。 - 在
handle_event
中,调用回调函数callback
,并将事件数据传递给它。
- 这是一个闭包,捕获了
my_callback
函数:- 这是用户定义的回调函数,用于处理事件数据。
创建事件处理器:
- 通过调用
event_handler(my_callback)
,我们创建了一个事件处理器handler
,它会在事件触发时调用my_callback
。
- 通过调用
模拟事件触发:
- 调用
handler("用户登录")
和handler("文件下载完成")
,模拟事件触发,并调用回调函数处理事件数据。
- 调用
闭包的优势
捕获外部变量:
- 闭包可以捕获外部作用域的变量(如
callback
),使得回调函数可以访问这些变量。
- 闭包可以捕获外部作用域的变量(如
灵活性:
- 通过闭包,可以动态生成回调函数,并根据需要传递不同的参数。
封装性:
- 闭包将回调函数和相关的上下文封装在一起,使得代码更模块化和易于维护。
另一个示例:带状态的回调函数
闭包还可以用来实现带状态的回调函数,即回调函数可以记住并修改外部作用域的变量。
def create_counter_callback():
count = 0 # 外部作用域的变量
def callback(event_data):
nonlocal count # 声明 count 为非局部变量
count += 1
print(f"事件 {count}: {event_data}")
return callback
# 创建带状态的回调函数
counter_callback = create_counter_callback()
# 模拟事件触发
counter_callback("用户登录")
counter_callback("文件下载完成")
counter_callback("订单创建")
输出结果:
事件 1: 用户登录
事件 2: 文件下载完成
事件 3: 订单创建
代码解释
create_counter_callback
函数:- 这是一个函数工厂,返回一个带状态的回调函数
callback
。 callback
捕获了外部作用域的变量count
,并在每次调用时更新它。
- 这是一个函数工厂,返回一个带状态的回调函数
callback
函数:- 这是一个闭包,捕获了
count
变量。 - 每次调用时,
count
增加 1,并打印事件数据和当前计数。
- 这是一个闭包,捕获了
创建带状态的回调函数:
- 通过调用
create_counter_callback()
,我们创建了一个带状态的回调函数counter_callback
。
- 通过调用
模拟事件触发:
- 每次调用
counter_callback
时,count
都会增加,并打印事件数据和当前计数。
- 每次调用
总结
闭包非常适合用来实现回调函数,因为它可以捕获外部作用域的变量,使得回调函数能够访问和操作这些变量。通过闭包,可以实现:
- 动态生成回调函数。
- 带状态的回调函数。
- 封装回调函数和相关的上下文。
闭包的使用使得回调函数更加灵活和强大,适用于事件驱动编程、异步编程等场景。
二、装饰器
2.1 闭包实现装饰器
闭包用来实现 装饰器(Decorator),而装饰器本质上就是利用闭包的特性来增强或修改函数的行为。装饰器是 Python 中非常强大的工具,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。
装饰器的基本概念
装饰器是一个接受函数作为参数的高阶函数,它返回一个新的函数(通常是闭包),用于替换原函数。装饰器的核心思想是利用闭包捕获原函数,并在闭包中添加额外的逻辑。
示例:用闭包实现装饰器
以下是一个简单的装饰器示例,用于记录函数的执行时间:
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time() # 记录开始时间
result = func(*args, **kwargs) # 调用原函数
end_time = time.time() # 记录结束时间
print(f"函数 {func.__name__} 执行时间: {end_time - start_time:.4f} 秒")
return result
return wrapper
# 使用装饰器
@timer_decorator
def my_function(n):
time.sleep(n) # 模拟耗时操作
print(f"函数执行完毕,耗时 {n} 秒")
# 调用被装饰的函数,实参
my_function(2)
输出结果:
函数执行完毕,耗时 2 秒
函数 my_function 执行时间: 2.0021 秒
等同效果(方便理解)
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time() # 记录开始时间
result = func(*args, **kwargs) # 调用原函数
end_time = time.time() # 记录结束时间
print(f"函数 {func.__name__} 执行时间: {end_time - start_time:.4f} 秒")
return result
return wrapper
"""把装饰器注释掉
# 使用装饰器
@timer_decorator
"""
def my_function(n):
time.sleep(n) # 模拟耗时操作
print(f"函数执行完毕,耗时 {n} 秒")
# 如果不用装饰器,这个写法等同于装饰器
my_function = timer_decorator(my_function)
# 调用被装饰的函数,实参
my_function(2)
代码解释
timer_decorator
函数:- 这是一个装饰器函数,接受一个函数
func
作为参数。 - 它返回一个闭包
wrapper
,用于替换原函数。
- 这是一个装饰器函数,接受一个函数
wrapper
函数:- 这是一个闭包,捕获了外部作用域的
func
变量。 - 在
wrapper
中,添加了记录函数执行时间的逻辑。 - 调用原函数
func(*args, **kwargs)
,并返回其结果。
- 这是一个闭包,捕获了外部作用域的
使用装饰器:
- 通过
@timer_decorator
语法,将my_function
函数传递给timer_decorator
装饰器。 - 调用
my_function
时,实际上调用的是wrapper
函数。
- 通过
调用被装饰的函数:
- 调用
my_function(2)
时,会先执行wrapper
中的逻辑,记录时间并调用原函数。
- 调用
执行顺序:
my_function
--> 实际上调用timer_decorator
的返回函数wrapper
–> 再调到my_function
可以理解为:被装饰器装饰的函数,实际调用的时候,其实是走装饰器返回的函数,而当前函数真正调用的地方则是装饰器内部函数调用的地方(如果有)。
闭包在装饰器中的作用
- 捕获原函数:闭包捕获了外部作用域的
func
变量,使得装饰器可以在不修改原函数的情况下增强其功能。 - 动态增强功能:通过闭包,可以在运行时动态地为函数添加额外的逻辑(如日志记录、性能测试、权限检查等)。
另一个示例:带参数的装饰器
如果装饰器本身需要参数,可以通过嵌套闭包来实现。以下是一个带参数的装饰器示例:
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用带参数的装饰器
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
# 调用被装饰的函数
greet("Alice")
输出结果:
Hello, Alice!
Hello, Alice!
Hello, Alice!
代码解释
repeat
函数:- 这是一个带参数的装饰器工厂函数,接受参数
num_times
。 - 它返回一个装饰器函数
decorator
。
- 这是一个带参数的装饰器工厂函数,接受参数
decorator
函数:- 这是一个装饰器函数,接受一个函数
func
作为参数。 - 它返回一个闭包
wrapper
,用于替换原函数。
- 这是一个装饰器函数,接受一个函数
wrapper
函数:- 这是一个闭包,捕获了外部作用域的
func
和num_times
变量。 - 在
wrapper
中,重复调用原函数num_times
次。
- 这是一个闭包,捕获了外部作用域的
使用带参数的装饰器:
- 通过
@repeat(3)
语法,将greet
函数传递给repeat
装饰器工厂,并指定参数num_times=3
。 - 调用
greet("Alice")
时,实际上调用的是wrapper
函数,重复执行 3 次。
- 通过
总结
闭包是装饰器的核心实现机制。通过闭包,装饰器可以:
- 捕获原函数:在不修改原函数的情况下增强其功能。
- 动态添加逻辑:如日志记录、性能测试、权限检查等。
- 支持带参数的装饰器:通过嵌套闭包实现更复杂的功能。
装饰器是 Python 中非常强大的工具,广泛应用于 Web 框架(如 Flask、Django)、测试框架、日志记录等场景。掌握闭包和装饰器的使用,可以极大地提高代码的复用性和可维护性。
一句话总结: 装饰器可以不改变函数的定义,通过在函数名上方加上@
的方式来增加一些新的功能,比如加日志,加验证等等。
更多装饰器的理解可以参考:传送门
2.2 继承中使用装饰器
同样在继承中,子类重写父类的方法时,可以为其添加装饰器。
装饰器可以用于修改或扩展方法的行为。以下是一个示例:
# 定义一个装饰器
def my_decorator(func):
def wrapper(*args, **kwargs):
print("装饰器:在方法执行前做一些事情")
result = func(*args, **kwargs)
print("装饰器:在方法执行后做一些事情")
return result
return wrapper
# 父类
class Parent:
def greet(self):
print("父类的 greet 方法")
# 子类
class Child(Parent):
@my_decorator # 使用装饰器
def greet(self):
print("子类的 greet 方法")
# 创建子类实例并调用方法
child = Child()
child.greet()
输出结果:
装饰器:在方法执行前做一些事情
子类的 greet 方法
装饰器:在方法执行后做一些事情
解释:
Parent
类定义了一个greet
方法。Child
类继承了Parent
类,并重写了greet
方法。- 在
Child
类的greet
方法上使用了@my_decorator
装饰器。 - 调用
child.greet()
时,装饰器会在方法执行前后添加额外的行为。
总结:
子类重写父类方法时,可以为其添加装饰器,以扩展或修改方法的行为。
至此,python的闭包到装饰器就分享完毕,还有疑问欢迎留言讨论,这里是ThomasCai的python专题频道,我们下篇文章再见~
∼ O n e p e r s o n g o f a s t e r , a g r o u p o f p e o p l e c a n g o f u r t h e r ∼ \sim_{One\ person\ go\ faster,\ a\ group\ of\ people\ can\ go\ further}\sim ∼One person go faster, a group of people can go further∼