跳至主要內容

闭包、装饰器

pptg大约 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