Python 使用装饰器 decorator 修改函数行为

news2025/1/3 4:35:04

Python 使用装饰器 decorator 修改函数行为

  • 使用装饰器修改函数行为
    • 使用带有返回值和参数的被装饰函数
    • 创建一个可以接受参数的装饰器
    • 使用多个装饰器

使用装饰器修改函数行为

Python中装饰器(decorator)的概念基于 Decorator 设计模式,这是一种结构化设计模式。此模式允许您向对象添加新行为,而无需更改对象实现中的任何内容。这个新行为被添加到特殊的包装器对象中。

在 Python 中,装饰器是一种特殊的高阶函数,它使开发者能够在现有函数(或方法)中添加新功能,而无需添加或更改函数中的任何内容。通常,这些装饰器被添加到函数定义之前。装饰器可用于实现应用程序的许多功能,但在数据验证、日志记录、缓存、调试、加密和事务管理等方面尤其流行。

在 Python 中,任何实现了特殊方法 __call__() 的对象,都被称作可调用对象。因此,从最基本的意义上讲,装饰器就是一个返回可调用对象的可调用对象。在 Python 中,一些事物都是对象,函数也是对象。

这里,只以讲解函数返回函数为例。

基本上,装饰器接收一个函数,添加一些功能并返回一个可调用对象。下面的代码演示了一个 add_timestamps装饰器,它返回了一个内部函数。内部函数在执行作为参数的函数之前和之后,输出当前时间:

from datetime import datetime

def add_timestamps(myfunc):
    def _add_timestamps():
        print(datetime.now())
        myfunc()
        print(datetime.now())
    return _add_timestamps


@add_timestamps
def hello_world():
    print('Hello World!')

hello_world()

上面的代码的输出结果是:

2024-12-30 14:54:17.568101
Hello World!
2024-12-30 14:54:17.568101

在这个例子中, @ 符号用来装饰函数,它等价于:

hello = add_timestamps(hell_world)
hello()

这就是 Python 解释器在看到 @ 符号之后做的事情——我们显示地调用了装饰器函数add_timestmaps,传递给它一个hello_world,它返回了它的内部函数。内部函数在被装饰的函数hell_world的功能上,添加了新功能。

还有一个问题。我们在调试程序的时候,需要更详细的函数调用信息,然而,使用了装饰器后,当我们在被装饰的函数上调用 help 函数时,或者查看 __doc____name__ 属性时,我们得到的,都不是被装饰函数的信息:

from datetime import datetime

def add_timestamps(myfunc):
    def _add_timestamps():
        print(datetime.now())
        myfunc()
        print(datetime.now())
    return _add_timestamps


@add_timestamps
def hello_world():
    """你好,世界"""
    print('Hello World!')

hello_world()

help(hello_world)
print(hello_world.__name__)
print(hello_world.__doc__)

上面代码的输出结果是:

2024-12-30 15:00:07.785346
Hello World!
2024-12-30 15:00:07.785346
Help on function _add_timestamps in module __main__:

_add_timestamps()

_add_timestamps
None

这显示不是我们想要的,我们仍然需要被装饰函数自己的信息。一个简单的解决方法是使用来自于 functools 模块的 wraps 装饰器。我们重新修订一下上面的代码:

from datetime import datetime
from functools import wraps

def add_timestamps(myfunc):
    @wraps(myfunc)
    def _add_timestamps():
        print(datetime.now())
        myfunc()
        print(datetime.now())
    return _add_timestamps


@add_timestamps
def hello_world():
    """你好,世界"""
    print('Hello World!')

hello_world()

help(hello_world)
print(hello_world.__name__)
print(hello_world.__doc__)

上面代码的输出结果是:

2024-12-30 15:06:49.581314
Hello World!
2024-12-30 15:06:49.581314
Help on function hello_world in module __main__:

hello_world()
    你好,世界

hello_world
你好,世界

这才是我们想要的,我们又找回了被装饰函数的所有信息。

使用带有返回值和参数的被装饰函数

当被装饰函数带有实参时,装饰这样的函数需要一些额外的技巧。一个技巧是在内部包装函数中使用*args**kwargs。这将使内部函数接受任意数量的位置化和关键字参数。下面是一个带有参数和返回值的装饰函数的简单示例:

from functools import wraps

def power(func):
    @wraps(func)
    def inner_calc(*args, **kwargs):
        print("内部函数")
        result = func(*args, **kwargs)
        return result
    return inner_calc

@power
def power_base2(n):
    return 2 ** n

print(power_base2(3))

上面代码的输出结果是:

内部函数
8

在上面的例子中,内部函数inner_calc接受两个形参,*args**kwargs。被装饰器函数 power_base2 有一个返回值,它在 inner_calc 里执行完毕后,可以直接通过 inner_calc 把返回值返回给调用装饰器的代码。

创建一个可以接受参数的装饰器

前面例子中讲到的装饰器,是标准装饰器。标准装饰器是一个函数,它接受一个函数作为实参,返回一个内部函数,执行装饰工作。然而,当一个装饰器有自己的过次页参时,事情就有所不同了。这种装饰器构建在标准装饰器上面。简单地说,带有实参的装饰器实际上返回的是标准装饰器。下面的例子演示了这种带有参数的装饰器:

from functools import wraps

def power_calc(base):
    def inner_decorator(func):
        @wraps(func)
        def inner_calc(*args, **kwargs):
            exponent = func(*args, **kwargs)
            return base ** exponent
        return inner_calc
    return inner_decorator

@power_calc(base = 3)
def power_n(n):
    return n

print(power_n(3))
print(power_n(4))

上面代码的输出结果是:

27
81

上面代码的做的工作是:

  • power_calc 装饰器接受一个实参 base,返回 inner_decorator,这是一个标准装饰器;
  • inner_decorator 接受一个函数作为实参,返回 inner_calc,它做真正的计算工作;
  • inner_calc 函数调用被装饰函数获取指数 exponent,然后使用由外部装饰器传递进来的 base,一起计算结果。内部函数闭包,使得 baseinner_calc 可见。

使用多个装饰器

在一个函数上,可以使用多个装饰器,这可以通过链接装饰器实现。被链接的装饰器可以相同也可以不相同。通过在函数定义之前一个接一个地放置装饰器可以实现链接装饰器。下面是一个例子。

from datetime import datetime
from functools import wraps

def add_timestamps(func):
    @wraps(func)
    def inner_func(*args, **kwargs):
        res = "{}:{}\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), func(*args, **kwargs))
        return res
    return inner_func

def file(func):
    @wraps(func)
    def inner_func(*args, **kwargs):
        res = func(*args, **kwargs)
        with open("log.txt", "a") as f:
            f.write(res)
        return res
    return inner_func

def console(func):
    @wraps(func)
    def inner_func(*args, **kwargs):
        res = func(*args, **kwargs)
        print(res)
        return res
    return inner_func


@file
@add_timestamps
def log1(msg):
    return msg

@file
@console
@add_timestamps
def log2(msg):
    return msg

@console
@add_timestamps
def log3(msg):
    return msg

log1("这是一个只写到文件中的日志")
log2("这是一个写到文件和控制台的日志")
log3("这是一个只写到控制台的日志")

在上面的代码中,我们实现了三个装饰器:

  • file:这个装饰器把被装饰函数提供的信息写到文件里;
  • console:这个装饰器把信息写到控制台上;
  • add_timestamps:添加时间的装饰器。这个装饰器一定要紧跟着被装饰函数,否则在输出信息中能添加上时间。

上面代码的输出结果是:

2024-12-30 16:39:58:这是一个写到文件和控制台的日志

2024-12-30 16:39:58:这是一个只写到控制台的日志

值得一提的是,装饰器在简化代码和以简洁的方式添加行为方面非常有用,但其代价是在执行过程中产生额外的开销。装饰器的使用应仅限于其收益足以补偿任何开销成本的情况。

<完>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2268375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国产文本编辑器EverEdit - 如何让输出窗口的日志具有双击跳转到文件指定行的功能

1 开发参考&#xff1a;编写脚本时如何向输出窗口打印可跳转到文件位置的日志 1.1 应用场景 编写脚本时&#xff0c;有时对文本进行分析&#xff0c;需要将提示信息打印到输出窗口&#xff0c;同时希望将文本的行、列信息也打印在日志中&#xff0c; 最好是双击日志信息可以跳…

《云原生安全攻防》-- K8s安全配置:CIS安全基准与kube-bench工具

在本节课程中&#xff0c;我们来了解一下K8s集群的安全配置&#xff0c;通过对CIS安全基准和kube-bench工具的介绍&#xff0c;可以快速发现K8s集群中不符合最佳实践的配置项&#xff0c;及时进行修复&#xff0c;从而来提高集群的安全性。 在这个课程中&#xff0c;我们将学习…

3、redis的集群模式

主从模式 哨兵模式 集群 主从模式&#xff1a;这是redis高可用的基础&#xff0c;哨兵模式和集群都是建立在此基础之上。 主从模式和数据库的主从模式是一样的&#xff0c;主负责写入&#xff0c;然后把写入的数据同步到从&#xff0c; 从节点只能读不能写&#xff0c;rea…

计算机图形学知识点汇总

一、计算机图形学定义与内容 1.图形 图形分为“图”和“形”两部分。 其中&#xff0c;“形”指形体或形状&#xff0c;存在于客观世界和虚拟世界&#xff0c;它的本质是“表示”&#xff1b;而图则是包含几何信息与属性信息的点、线等基本图元构成的画面&#xff0c;用于表达…

自动化测试模型(一)

8.8.1 自动化测试模型概述 在自动化测试运用于测试工作的过程中&#xff0c;测试人员根据不同自动化测试工具、测试框架等所进行的测试活动进行了抽象&#xff0c;总结出线性测试、模块化驱动测试、数据驱动测试和关键字驱动测试这4种自动化测试模型。 线性测试 首先&#…

医疗数仓数据仓库设计

医疗数仓数据仓库设计 数据仓库构建流程数据调研明确数据域构建业务总线矩阵明确统计指标交易主题医生主题用户主题评价主题 维度模型设计汇总模型设计 数据仓库构建流程 数据仓库分层规划 优秀可靠的数仓体系&#xff0c;需要良好的数据分层结构。合理的分层&#xff0c;能够…

Go-知识 注释

Go-知识 注释 行注释块注释包注释结构体&接口注释函数&方法注释废弃注释文档 在 go 语言中注释有两种&#xff0c;行注释和块注释 行注释 使用双斜线 // 开始&#xff0c;一般后面紧跟一个空格。行注释是Go语言中最常见的注释形式&#xff0c;在标准包中&#xff0c;…

1230作业

思维导图 作业 将广播发送和接收端实现一遍&#xff0c;完成一个发送端发送信息&#xff0c;对应多个接收端接收 自实验 //广播发送端 #include <myhead.h> #define G_PORT 8765 #define G_IP "192.168.124.255" int main(int argc, const char *argv[]) {//…

U盘格式化工具合集:6个免费的U盘格式化工具

在日常使用中&#xff0c;U盘可能会因为文件系统不兼容、数据损坏或使用需求发生改变而需要进行格式化。一个合适的格式化工具不仅可以清理存储空间&#xff0c;还能解决部分存储问题。本文为大家精选了6款免费的U盘格式化工具&#xff0c;并详细介绍它们的功能、使用方法、优缺…

Windows系统使用Koodo Reader轻松搭建在线私人图书馆远程看书

文章目录 前言1. Koodo Reader 功能特点1.1 开源免费1.2 支持众多格式1.3 多平台兼容1.4 多端数据备份同步1.5 多功能阅读体验1.6 界面简洁直观 2. Koodo Reader安装流程2.1 安装Git2.2 安装Node.js2.3 下载koodo reader 3. 安装Cpolar内网穿透3.1 配置公网地址3.2 配置固定公网…

开关电源调试思维导图

开关电源辐射发射问题调试一直以来都是工程师们非常头疼的事情&#xff0c;也不知道如何下手&#xff0c;今天就通过几个思维导图把开关电源辐射发射问题调试的思路呈现给广大工程师们&#xff0c;希望能照亮大家辐射调试的黑暗道路。 01、反激电路辐射发射问题调试思维导图 0…

【brainpan靶场渗透】

文章目录 一、基础信息 二、信息收集 三、反弹shell 四、提权 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机 IP&#xff1a;192.168.20.155 二、信息收集 似乎开放了9999&#xff0c;10000端口&#xff0c;访问页面没有太多内容&#xff0c;扫描一下目录 dirs…

使用MFC编写一个paddleclas预测软件

目录 写作目的 环境准备 下载编译环境 解压预编译库 准备训练文件 模型文件 图像文件 路径整理 准备预测代码 创建预测应用 新建mfc应用 拷贝文档 配置环境 界面布局 添加回cpp文件 修改函数 报错1解决 报错2未解决 修改infer代码 修改MFCPaddleClasDlg.cp…

html+css+js网页设计 美食 美食家6个页面

htmlcssjs网页设计 美食 美食家6个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…

【第四期书生大模型实战营基础岛】L1G5000——XTuner 微调个人小助手认知任务

基础任务————使用 XTuner 微调 InternLM2-Chat-7B 实现自己的小助手认知&#xff0c;如下图所示&#xff08;图中的尖米需替换成自己的昵称&#xff09;&#xff0c;记录复现过程并截图。 环境配置与数据准备 步骤 0. 使用 conda 先构建一个 Python-3.10 的虚拟环境 cd …

android知识巩固(二.非线性数据结构)

非线性结构:是从逻辑结构上划分,其元素存在一对多或者多对多的相互关系 1.前言 在前一章中,我们了解了数据结构的基本思想,学习了部分基本的线性数据结构,了解了计算机是如何表示和存储数据的,良好的数据结构思想有助于我们写出性能优良的应用 2.目录 目录.png 3.非线性数据结构…

列车票务信息系统|Java|SSM|JSP|

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html 5⃣️数据库可…

LabVIEW中实现多个Subpanel独立调用同一个VI

在LabVIEW中&#xff0c;如果需要通过多个Subpanel同时调用同一个VI并让这些VI实例独立运行&#xff0c;可以通过以下方法实现&#xff1a; 1. 问题背景 LabVIEW默认的VI是以单实例方式运行的。当将同一个VI加载到多个Subpanel时&#xff0c;会因为共享同一内存空间而导致冲突…

鱼眼相机模型与去畸变实现

1.坐标系说明 鱼眼相机模型涉及到世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换关系。对于分析鱼眼相机模型&#xff0c;假定世界坐标系下的坐标点,经过外参矩阵的变换转到相机坐标系&#xff0c;相机坐标再经过内参转换到像素坐标&#xff0c;具体如下 进一步进…

基于eBPF的微服务网络安全(Cilium 1)

一些开源的kubernetes工具已经开始使用eBPF&#xff0c;这些工具大多数与网络&#xff0c;监控和安全相关。 本文不会涵盖eBPF的方方面面&#xff0c;只作为一个入门指南&#xff0c;包括Linux内核的BPF概念&#xff0c;到将该功能加入到微服务环境的优势&#xff0c;以及当前…