一文解读python的高阶功能:从闭包到装饰器的理解

发布于:2025-03-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

在这里插入图片描述

一、闭包

闭包的理解: 闭包是函数和其周围状态的引用捆绑在一起形成的,闭包使得函数可以记住并访问它的词法作用域,即使函数是在当前词法作用域之外执行。

闭包其实就是函数内部定义的函数,并且这个内部函数可以访问外部函数的变量。

更简单理解:函数包着函数

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. 闭包可以用来创建函数工厂
  2. 闭包可以用来装饰函数
  3. 闭包可以用来实现回调函数

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

解释

  1. create_multiplier 函数:这是一个函数工厂,接受一个参数 factor,并返回一个内部函数 multiplier
  2. multiplier 函数:这是一个闭包,捕获了外部函数 create_multiplierfactor 变量。即使 create_multiplier 执行完毕,multiplier 仍然可以访问 factor
  3. 生成特定函数:通过调用 create_multiplier(2),我们生成了一个将输入值乘以 2 的函数 double;通过调用 create_multiplier(3),我们生成了一个将输入值乘以 3 的函数 triple
  4. 调用生成的函数double(5) 返回 10triple(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

解释

  1. create_counter 函数:这是一个函数工厂,返回一个内部函数 counter

  2. counter 函数:这是一个闭包,捕获了外部函数 create_countercount 变量。每次调用 counter 时,count 都会增加 1。

  3. 生成计数器:通过调用 create_counter(),我们生成了两个独立的计数器 counter1counter2,它们各自维护自己的 count 变量。

  4. 调用计数器counter1counter2 分别计数,互不干扰。

总结

通过闭包,可以创建灵活的函数工厂,生成具有特定行为的函数。闭包的核心在于捕获并保存外部作用域的变量,使得生成的函数可以在后续调用中继续使用这些变量。

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("文件下载完成")

输出结果:

事件已触发,数据: 用户登录
回调函数接收到数据: 用户登录
事件已触发,数据: 文件下载完成
回调函数接收到数据: 文件下载完成

代码解释

  1. event_handler 函数

    • 这是一个高阶函数,接受一个回调函数 callback 作为参数。
    • 它返回一个内部函数 handle_event,这是一个闭包,捕获了外部作用域的 callback 变量。
  2. handle_event 函数

    • 这是一个闭包,捕获了 callback
    • 当事件触发时,handle_event 被调用,并接收事件数据 event_data
    • handle_event 中,调用回调函数 callback,并将事件数据传递给它。
  3. my_callback 函数

    • 这是用户定义的回调函数,用于处理事件数据。
  4. 创建事件处理器

    • 通过调用 event_handler(my_callback),我们创建了一个事件处理器 handler,它会在事件触发时调用 my_callback
  5. 模拟事件触发

    • 调用 handler("用户登录")handler("文件下载完成"),模拟事件触发,并调用回调函数处理事件数据。

闭包的优势

  1. 捕获外部变量

    • 闭包可以捕获外部作用域的变量(如 callback),使得回调函数可以访问这些变量。
  2. 灵活性

    • 通过闭包,可以动态生成回调函数,并根据需要传递不同的参数。
  3. 封装性

    • 闭包将回调函数和相关的上下文封装在一起,使得代码更模块化和易于维护。

另一个示例:带状态的回调函数

闭包还可以用来实现带状态的回调函数,即回调函数可以记住并修改外部作用域的变量。

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: 订单创建

代码解释

  1. create_counter_callback 函数

    • 这是一个函数工厂,返回一个带状态的回调函数 callback
    • callback 捕获了外部作用域的变量 count,并在每次调用时更新它。
  2. callback 函数

    • 这是一个闭包,捕获了 count 变量。
    • 每次调用时,count 增加 1,并打印事件数据和当前计数。
  3. 创建带状态的回调函数

    • 通过调用 create_counter_callback(),我们创建了一个带状态的回调函数 counter_callback
  4. 模拟事件触发

    • 每次调用 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)

代码解释

  1. timer_decorator 函数

    • 这是一个装饰器函数,接受一个函数 func 作为参数。
    • 它返回一个闭包 wrapper,用于替换原函数。
  2. wrapper 函数

    • 这是一个闭包,捕获了外部作用域的 func 变量。
    • wrapper 中,添加了记录函数执行时间的逻辑。
    • 调用原函数 func(*args, **kwargs),并返回其结果。
  3. 使用装饰器

    • 通过 @timer_decorator 语法,将 my_function 函数传递给 timer_decorator 装饰器。
    • 调用 my_function 时,实际上调用的是 wrapper 函数。
  4. 调用被装饰的函数

    • 调用 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!

代码解释

  1. repeat 函数

    • 这是一个带参数的装饰器工厂函数,接受参数 num_times
    • 它返回一个装饰器函数 decorator
  2. decorator 函数

    • 这是一个装饰器函数,接受一个函数 func 作为参数。
    • 它返回一个闭包 wrapper,用于替换原函数。
  3. wrapper 函数

    • 这是一个闭包,捕获了外部作用域的 funcnum_times 变量。
    • wrapper 中,重复调用原函数 num_times 次。
  4. 使用带参数的装饰器

    • 通过 @repeat(3) 语法,将 greet 函数传递给 repeat 装饰器工厂,并指定参数 num_times=3
    • 调用 greet("Alice") 时,实际上调用的是 wrapper 函数,重复执行 3 次。

总结

闭包是装饰器的核心实现机制。通过闭包,装饰器可以:

  1. 捕获原函数:在不修改原函数的情况下增强其功能。
  2. 动态添加逻辑:如日志记录、性能测试、权限检查等。
  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 方法
装饰器:在方法执行后做一些事情

解释:

  1. Parent 类定义了一个 greet 方法。
  2. Child 类继承了 Parent 类,并重写了 greet 方法。
  3. Child 类的 greet 方法上使用了 @my_decorator 装饰器。
  4. 调用 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