闭包、装饰器
大约 4 分钟
1. 闭包
闭包是函数式编程的重要概念,也是理解装饰器的基础。
闭包 = 内部函数 + 外部变量, 是一个能"记住"它被创建时的环境的函数,即使那个环境已经不再存在
闭包三要素:
- 嵌套函数:函数内部定义另一个函数
- 引用外部变量:内部函数引用外部函数的变量
- 返回内部函数:外部函数返回内部函数
# 嵌套函数
def outer():
x = 10 # 外部变量
def inner():
print(f"inner: x = {x}") # 内部函数访问外部变量
return inner # 返回内部函数
# 使用闭包
my_func = outer() # outer已经执行完毕,按说x应该消失了
my_func() # 但是输出: inner: x = 10 - x被记住了!
闭包可以使用nonlocal修改外部变量
def create_accumulator():
total = 0
def add(value):
nonlocal total # 必须声明才能修改
total += value
return total
return add
acc = create_accumulator()
print(acc(10)) # 10
print(acc(20)) # 30
print(acc(5)) # 35
2. 匿名函数
匿名函数,是一种不需要使用def定义、没有名称的函数,也叫做Lambda函数
# lambda 参数: 表达式
# 普通函数
def add(x, y):
return x + y
# 匿名函数
lambda x, y: x + y
3. 高阶函数
高阶函数是操作函数的函数,需要满足以下条件之一:
- 接收函数作为参数
def apply_operation(numbers, operation):
"""对列表中的每个元素应用操作"""
results = []
for num in numbers:
results.append(operation(num))
return results
# 使用普通函数
def square(x):
return x ** 2
def double(x):
return x * 2
numbers = [1, 2, 3, 4, 5]
print(apply_operation(numbers, square)) # [1, 4, 9, 16, 25]
print(apply_operation(numbers, double)) # [2, 4, 6, 8, 10]
# 使用匿名函数
print(apply_operation(numbers, lambda x: x + 10)) # [11, 12, 13, 14, 15]
- 返回函数作为结果
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
# 使用lambda版本
def create_multiplier_lambda(factor):
return lambda x: x * factor
double_lambda = create_multiplier_lambda(2)
print(double_lambda(5)) # 10
python内置的高阶函数: map、filter、sorted、reduce等
4. 装饰器
装饰器 = 语法糖 + 高阶函数 +闭包,可以在不修改原函数的情况下,为函数添加新功能
在高阶函数的例子中发现,函数其实也是对象,可以作为参数和返回值进行传递,因此可以手动实现一下装饰器功能:
def original_function():
print("原始功能")
def decorator(func):
def wrapper():
print("装饰器 - 前")
func() # 调用原函数
print("装饰器 - 后")
return wrapper
# 手动装饰
decorated_function = decorator(original_function)
decorated_function()
@只是上面手动装饰的语法糖
def decorator(func):
def wrapper():
print("装饰器 - 前")
func()
print("装饰器 - 后")
return wrapper
# 使用@语法糖
@decorator
def original_function():
print("原始功能")
# 这完全等价于:
# original_function = decorator(original_function)
original_function() # 调用被装饰后的函数
装饰器会掩饰原函数的元信息,需要使用functools.wraps来保留原函数信息
import functools
def better_decorator(func):
@functools.wraps(func) # 保留原函数信息
def wrapper(*args, **kwargs):
print(f"执行 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@better_decorator
def example():
"""这是一个示例函数"""
pass
print(f"函数名: {example.__name__}") # 输出: example
# 如果不加的话会输出
print(example.__name__) # 输出: wrapper(错误!)
如果需要给装饰器传递参数的话,就需要再嵌套一层,也就是用闭包的方式来传递
def repeat(times):
"""装饰器工厂,接收参数"""
print(f"1. 装饰器工厂被调用,times={times}")
def actual_decorator(func):
print(f"2. 实际装饰器被调用,接收函数: {func.__name__}")
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"4. wrapper执行,将重复 {times} 次")
for i in range(times):
print(f"第 {i+1} 次执行:")
result = func(*args, **kwargs)
return result
print("3. 实际装饰器返回wrapper")
return wrapper
return actual_decorator
@repeat(3)
def say_hello(name):
print(f"Hello, {name}!")
print("=== 开始调用 ===")
say_hello("Alice")
1. 装饰器工厂被调用,times=3
2. 实际装饰器被调用,接收函数: say_hello
3. 实际装饰器返回wrapper
=== 开始调用 ===
4. wrapper执行,将重复 3 次
第 1 次执行:
Hello, Alice!
第 2 次执行:
Hello, Alice!
第 3 次执行:
Hello, Alice!
类也可以作为装饰器,需要实现__call__方法
__call__就是让对象变成"可调用"的,其实例可以像函数一样使用 () 来调用
class ClassDecorator:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"第 {self.call_count} 次调用 {self.func.__name__}")
return self.func(*args, **kwargs)
@ClassDecorator
def multiply(a, b):
return a * b
print(multiply(3, 4)) # 第 1 次调用 multiply → 12
print(multiply(5, 6)) # 第 2 次调用 multiply → 30