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

news/2025/3/15 12:00:14/

在这里插入图片描述

文章目录

  • 一、闭包
    • 1.1 举个栗子
      • 输出结果:
    • 1.2 可以用来干啥?
    • 1.3 用闭包来创建函数工厂
      • 示例:创建函数工厂
      • 解释
      • 闭包的关键点
      • 另一个示例:创建计数器
      • 解释
      • 总结
    • 1.4 用闭包来实现回调函数
      • 示例:使用闭包实现回调函数
      • 输出结果:
      • 代码解释
      • 闭包的优势
      • 另一个示例:带状态的回调函数
      • 输出结果:
      • 代码解释
      • 总结
  • 二、装饰器
    • 2.1 闭包实现装饰器
      • 装饰器的基本概念
      • 示例:用闭包实现装饰器
      • 输出结果:
      • 等同效果(方便理解)
      • 代码解释
      • 执行顺序:
      • 闭包在装饰器中的作用
      • 另一个示例:带参数的装饰器
      • 输出结果:
      • 代码解释
      • 总结
    • 2.2 继承中使用装饰器
      • 输出结果:
      • 解释:
      • 总结:

一、闭包

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

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

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

1.1 举个栗子

def outer_function():x = 10def inner_function():print(x)return inner_functionout = outer_function()
out()

输出结果:

10

其实就是返回了inner_function这个内部函数,且这个内部函数可以访问outer_function这个外部函数的变量。

1.2 可以用来干啥?

闭包的用途:

  1. 闭包可以用来创建函数工厂
  2. 闭包可以用来装饰函数
  3. 闭包可以用来实现回调函数

1.3 用闭包来创建函数工厂

在 Python 中,闭包(closure)是指一个函数捕获并保存了其外部作用域的变量,即使外部作用域已经执行完毕。利用闭包,可以创建函数工厂,即一个生成特定函数的函数。

示例:创建函数工厂

假设我们要创建一个函数工厂,生成将输入值乘以特定因子的函数。

python">def create_multiplier(factor):def multiplier(x):return x * factorreturn 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

闭包的关键点

  • 捕获变量:闭包捕获并保存了外部函数的变量,即使外部函数已经执行完毕。
  • 灵活性:通过闭包,可以动态生成具有不同行为的函数。

另一个示例:创建计数器

python">def create_counter():count = 0def counter():nonlocal countcount += 1return countreturn 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)。回调函数是指一个函数作为参数传递给另一个函数,并在特定事件或条件发生时被调用。闭包可以捕获外部作用域的变量,使得回调函数能够访问和操作这些变量,从而实现更灵活的功能。

以下是一个使用闭包实现回调函数的示例:

示例:使用闭包实现回调函数

假设我们有一个事件处理器,当某个事件发生时,调用回调函数并传递事件的相关数据。我们可以使用闭包来创建回调函数,并在回调函数中访问外部作用域的变量。

python">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. 封装性

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

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

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

python">def create_counter_callback():count = 0  # 外部作用域的变量def callback(event_data):nonlocal count  # 声明 count 为非局部变量count += 1print(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 中非常强大的工具,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。

装饰器的基本概念

装饰器是一个接受函数作为参数的高阶函数,它返回一个新的函数(通常是闭包),用于替换原函数。装饰器的核心思想是利用闭包捕获原函数,并在闭包中添加额外的逻辑。


示例:用闭包实现装饰器

以下是一个简单的装饰器示例,用于记录函数的执行时间:

python">import timedef 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 resultreturn wrapper# 使用装饰器
@timer_decorator
def my_function(n):time.sleep(n)  # 模拟耗时操作print(f"函数执行完毕,耗时 {n} 秒")# 调用被装饰的函数,实参
my_function(2)

输出结果:

函数执行完毕,耗时 2 秒
函数 my_function 执行时间: 2.0021 秒

等同效果(方便理解)

import timedef 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 resultreturn 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 变量,使得装饰器可以在不修改原函数的情况下增强其功能。
  • 动态增强功能:通过闭包,可以在运行时动态地为函数添加额外的逻辑(如日志记录、性能测试、权限检查等)。

另一个示例:带参数的装饰器

如果装饰器本身需要参数,可以通过嵌套闭包来实现。以下是一个带参数的装饰器示例:

python">def repeat(num_times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn 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 继承中使用装饰器

同样在继承中,子类重写父类的方法时,可以为其添加装饰器

装饰器可以用于修改或扩展方法的行为。以下是一个示例:

python"># 定义一个装饰器
def my_decorator(func):def wrapper(*args, **kwargs):print("装饰器:在方法执行前做一些事情")result = func(*args, **kwargs)print("装饰器:在方法执行后做一些事情")return resultreturn 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


http://www.ppmy.cn/news/1579304.html

相关文章

uni-app打包h5并部署到nginx,路由模式history

uni-app打包有些坑,当时运行的基础路径填写了./,导致在二级页面刷新之后,页面直接空白。就只能换一个路径了,nginx也要跟着改,下面是具体步骤。 manifest.json配置web 运行路径写/h5/,或者写你们网站的目…

系统可观测性(5)OpenTelemetry基础使用

系统可观测性(5)OpenTelemetry基础概念 Author: Once Day Date: 2025年3月12日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 本文档翻译整理自《OpenTelemetry Docs》&a…

K8S学习之基础二十八:k8s中的configMap

k8s中的configMap ​ configMap是k8s的资源对象,简称cm,用于保存非机密性的配置,数据可以用key/value键值对形式保存,也可以通过文件形式保存 ​ 在部署服务的时候,每个服务都有自己的配置文件,如果一台服…

ELK(Elasticsearch、Logstash、Kbana)安装及Spring应用

Elasticsearch安装及Spring应用 一、引言二、基本概念1.索引(Index)2.类型(Type)3.文档(Document)4.分片(Shard)5.副本(Replica) 二、ELK搭建1.创建挂载的文件…

JVM崩溃时产生的文件 hs_err.pid.log

hs_err.pid.log hs_err.pid.log:当jvm崩溃时,会生成一个hs_err_pid.log文件,并且把它存放到程序目录下,可以通过该文件来定位导致jvm崩溃的原因。 jvm崩溃,是由jvm自身的bug或者本地方法执行错误引起的,本…

clickhouse清除system 表数据释放磁盘空间

注:clickhouse 默认系统有以下几个 log 表,如下 system.asynchronous_metric_log system.metric_log system.part_log system.query_log system.query_thread_log system.session_log system.trace_log 如果不想看原文直接执行以下语句即可。 ALTER table syst…

Centos7阿里云yum源

#Step1:下载repository 没有wget命令 就用curl wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo #Step2:安装epel基础组件源 没有wget命…

微店商品详情页的常见结构及爬虫解析方法

微店作为知名的电商平台,其商品详情页的结构设计通常会围绕用户体验和商品展示效果展开。以下是一些常见的微店商品详情页结构特点及其对应的爬虫解析方法: 一、常见结构 (一)页面头部 LOGO和店招:通常位于页面顶部&…