【Python】一文弄懂python装饰器(附源码例子)

article/2025/8/27 22:11:00

目录

前言

一、什么是装饰器

二、为什么要用装饰器

三、简单的装饰器

四、装饰器的语法糖@

五、装饰器传参

六、带参数的装饰器

七、类装饰器

八、带参数的类装饰器

九、装饰器的顺序

总结

写在后面


前言

最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡。你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡。但是你喜欢的女孩子还是你喜欢的女孩子。如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该可以给你玩,该打电话打电话,该玩游戏玩游戏,该收藏攻城狮白玉的博客就收藏攻城狮白玉的博客。而你的手机就变成了带手机壳的手机。

装饰器就是python的一个拦路虎,你干或者不干它,它都在那里。如果你想学会高级的python用法,装饰器就是你这个武松必须打倒的一只虎。

本文的环境如下:

win10,python3.7

一、什么是装饰器

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。

装饰器的使用符合了面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:

  1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

二、为什么要用装饰器

使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。

函数也可以作为函数的参数进行传递的。

通过下面这个简单的例子可以更直观知道函数名是如何直接作为参数进行传递

def baiyu():print("我是攻城狮白玉")def blog(name):print('进入blog函数')name()print('我的博客是 https://blog.csdn.net/zhh763984017')if __name__ == '__main__':func = baiyu  # 这里是把baiyu这个函数名赋值给变量funcfunc()  # 执行func函数print('------------')blog(baiyu)  # 把baiyu这个函数作为参数传递给blog函数

执行结果如下所示:

 接下来,我想知道这baiyublog两个函数分别的执行时间是多少,我就把代码修改如下:

import timedef baiyu():t1 = time.time()print("我是攻城狮白玉")time.sleep(2)print("执行时间为:", time.time() - t1)def blog(name):t1 = time.time()print('进入blog函数')name()print('我的博客是 https://blog.csdn.net/zhh763984017')print("执行时间为:", time.time() - t1)if __name__ == '__main__':func = baiyu  # 这里是把baiyu这个函数名赋值给变量funcfunc()  # 执行func函数print('------------')blog(baiyu)  # 把baiyu这个函数作为参数传递给blog函数

 

 上述的改写已经实现了我需要的功能,但是,当我有另外一个新的函数【python_blog_list】,具体如下:

def python_blog_list():print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)https://blog.csdn.net/zhh763984017/article/details/119063252 ''')print('''【Python】除了多线程和多进程,你还要会协程https://blog.csdn.net/zhh763984017/article/details/118958668 ''')print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)https://blog.csdn.net/zhh763984017/article/details/118773313 ''')print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)https://blog.csdn.net/zhh763984017/article/details/118634945 ''')

也需要计算函数执行时间的,那按之前的逻辑,就是改写如下:

def python_blog_list():t1 = time.time()print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)https://blog.csdn.net/zhh763984017/article/details/119063252 ''')print('''【Python】除了多线程和多进程,你还要会协程https://blog.csdn.net/zhh763984017/article/details/118958668 ''')print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)https://blog.csdn.net/zhh763984017/article/details/118773313 ''')print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)https://blog.csdn.net/zhh763984017/article/details/118634945 ''')print("执行时间为:", time.time() - t1)

如果也要这样子写的话,不就重复造轮子了吗?虽说人类的本质是鸽王和复读机,但作为一个优秀的cv攻城狮(ctrl+c和ctrl+v)肯定是要想办法偷懒的呀

 

装饰器,就是可以让我们拓展一些原有函数没有的功能。

三、简单的装饰器

基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。

import timedef baiyu():print("我是攻城狮白玉")time.sleep(2)def count_time(func):def wrapper():t1 = time.time()func()print("执行时间为:", time.time() - t1)return wrapperif __name__ == '__main__':baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapperbaiyu()  # 执行baiyu()就相当于执行wrapper()

这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。

当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了

四、装饰器的语法糖@

你如果看过其他python项目里面的代码里,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去

baiyu = count_time(baiyu)

这一句代码,而直接调用baiyu()这个函数

换句话说,其实使用装饰器的是,默认传入的参数就是被装饰的函数。

import timedef count_time(func):def wrapper():t1 = time.time()func()print("执行时间为:", time.time() - t1)return wrapper@count_time
def baiyu():print("我是攻城狮白玉")time.sleep(2)if __name__ == '__main__':# baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapper# baiyu()  # 执行baiyu()就相当于执行wrapper()baiyu()  # 用语法糖之后,就可以直接调用该函数了

五、装饰器传参

当我们的被装饰的函数是带参数的,此时要怎么写装饰器呢?

上面我们有定义了一个blog函数是带参数的

def blog(name):print('进入blog函数')name()print('我的博客是 https://blog.csdn.net/zhh763984017')

此时我们的装饰器函数要优化一下下,修改成为可以接受任意参数的装饰器

def count_time(func):def wrapper(*args,**kwargs):t1 = time.time()func(*args,**kwargs)print("执行时间为:", time.time() - t1)return wrapper

此处,我们的wrapper函数的参数为*args和**kwargs,表示可以接受任意参数

这时我们就可以调用我们的装饰器了。

import timedef count_time(func):def wrapper(*args, **kwargs):t1 = time.time()func(*args, **kwargs)print("执行时间为:", time.time() - t1)return wrapper@count_time
def blog(name):print('进入blog函数')name()print('我的博客是 https://blog.csdn.net/zhh763984017')if __name__ == '__main__':# baiyu = count_time(baiyu)  # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于  baiyu = wrapper# baiyu()  # 执行baiyu()就相当于执行wrapper()# baiyu()  # 用语法糖之后,就可以直接调用该函数了blog(baiyu)

六、带参数的装饰器

前面咱们知道,装饰器函数也是函数,既然是函数,那么就可以进行参数传递,咱们怎么写一个带参数的装饰器呢?

前面咱们的装饰器只是实现了一个计数,那么我想在使用该装饰器的时候,传入一些备注的msg信息,怎么办呢?咱们一起看一下下面的代码

import timedef count_time_args(msg=None):def count_time(func):def wrapper(*args, **kwargs):t1 = time.time()func(*args, **kwargs)print(f"[{msg}]执行时间为:", time.time() - t1)return wrapperreturn count_time@count_time_args(msg="baiyu")
def fun_one():time.sleep(1)@count_time_args(msg="zhh")
def fun_two():time.sleep(1)@count_time_args(msg="mylove")
def fun_three():time.sleep(1)if __name__ == '__main__':fun_one()fun_two()fun_three()

咱们基于原来的count_time函数外部再包一层用于接收参数的count_time_args,接收回来的参数就可以直接在内部的函数里面调用了。上述代码执行效果如下:

 

七、类装饰器

上面咱们一起学习了怎么写装饰器函数,在python中,其实也可以同类来实现装饰器的功能,称之为类装饰器。类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比我们装饰器函数的写法更加简单。

当我们将类作为一个装饰器,工作流程:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法
import timeclass BaiyuDecorator:def __init__(self, func):self.func = funcprint("执行类的__init__方法")def __call__(self, *args, **kwargs):print('进入__call__函数')t1 = time.time()self.func(*args, **kwargs)print("执行时间为:", time.time() - t1)@BaiyuDecorator
def baiyu():print("我是攻城狮白玉")time.sleep(2)def python_blog_list():time.sleep(5)print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)https://blog.csdn.net/zhh763984017/article/details/119063252 ''')print('''【Python】除了多线程和多进程,你还要会协程https://blog.csdn.net/zhh763984017/article/details/118958668 ''')print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)https://blog.csdn.net/zhh763984017/article/details/118773313 ''')print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)https://blog.csdn.net/zhh763984017/article/details/118634945 ''')@BaiyuDecorator
def blog(name):print('进入blog函数')name()print('我的博客是 https://blog.csdn.net/zhh763984017')if __name__ == '__main__':baiyu()print('--------------')blog(python_blog_list)

八、带参数的类装饰器

当装饰器有参数的时候,__init__() 函数就不能传入func(func代表要装饰的函数)了,而func是在__call__函数调用的时候传入的。

 

class BaiyuDecorator:def __init__(self, arg1, arg2):  # init()方法里面的参数都是装饰器的参数print('执行类Decorator的__init__()方法')self.arg1 = arg1self.arg2 = arg2def __call__(self, func):  # 因为装饰器带了参数,所以接收传入函数变量的位置是这里print('执行类Decorator的__call__()方法')def baiyu_warp(*args):  # 这里装饰器的函数名字可以随便命名,只要跟return的函数名相同即可print('执行wrap()')print('装饰器参数:', self.arg1, self.arg2)print('执行' + func.__name__ + '()')func(*args)print(func.__name__ + '()执行完毕')return baiyu_warp@BaiyuDecorator('Hello', 'Baiyu')
def example(a1, a2, a3):print('传入example()的参数:', a1, a2, a3)if __name__ == '__main__':print('准备调用example()')example('Baiyu', 'Happy', 'Coder')print('测试代码执行完毕')

建议各位同学好好比对一下这里的代码和不带参数的装饰器代码的区别,加深理解。

九、装饰器的顺序

一个函数可以被多个装饰器进行装饰,那么装饰器的执行顺序是怎么样的呢?咱们执行一下下面的代码就清楚了

def BaiyuDecorator_1(func):def wrapper(*args, **kwargs):func(*args, **kwargs)print('我是装饰器1')return wrapperdef BaiyuDecorator_2(func):def wrapper(*args, **kwargs):func(*args, **kwargs)print('我是装饰器2')return wrapperdef BaiyuDecorator_3(func):def wrapper(*args, **kwargs):func(*args, **kwargs)print('我是装饰器3')return wrapper@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():print("我是攻城狮白玉")if __name__ == '__main__':baiyu()

由输出结果可知,在装饰器修饰完的函数,在执行的时候先执行原函数的功能,然后再由里到外依次执行装饰器的内容。

我们带三个装饰器的函数的代码如下:

 

@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():print("我是攻城狮白玉")

上述的代码可以看作如下代码,就能理解为何是由里到外执行了

baiyu = BaiyuDecorator_1 (BaiyuDecorator_2 (BaiyuDecorator_3(baiyu)))

总结

本文由浅入深介绍了python的装饰器,并且通过代码展现了如何自己手写装饰器函数和类装饰器。

写在后面

如果觉得有用的话,麻烦一键三连支持一下攻城狮白玉并把本文分享给更多的小伙伴。你的简单支持,我的无限创作动力


http://chatgpt.dhexx.cn/article/pxRXE1Xh.shtml

相关文章

偏微分方程数值解程序设计与实现——数学基础

常用算子符号 梯度算子 R d \mathbb{R}^d Rd空间中标量函数 u ( x ) u(\bf{x}) u(x),其梯度算子定义如下: g r a d u ( x ) ∇ u ( x ) [ ∂ u ∂ x 0 ∂ u ∂ x 1 ⋮ ∂ u ∂ x d − 1 ] grad u(\mathbf{x})\nabla u(\mathbf{x}) \begin{bmatrix} …

偏微分方程的数值解(六): 偏微分方程的 pdetool 解法

偏微分方程的数值解系列博文: 偏微分方程的数值解(一):定解问题 & 差分解法 偏微分方程的数值解(二): 一维状态空间的偏微分方程的 MATLAB 解法 偏微分方程的数值解(三): 化工应用实例 ----------触煤反应装置内温度及转换率的分布 偏微分方程的数值解(四):…

微分方程数值解

一阶问题举例: 高阶问题举例 : 常微分方程数值解:向前欧拉方法之一阶问题 clc,clear,close all; a0;%初始时刻 b2*pi;%结束时刻 n100;%离散点数量 x00;%初值 h(b-a)/n;%步长 xx0 [0:n]*h;%离散点数组 funinline(sin(x)y,x,y); y01; %计算 y(…

常微分方程数值解法1

1.牛顿迭代法 多数方程不存在求根公式,因此求精确根非常困难,甚至不可能,从而寻找方程的近似根就显得特别重要。牛顿迭代法使用函数 的泰勒级数的前面几项来寻找方程 的根。牛顿迭代法是求方程根的重要方法之一,其最大…

差分、偏微分方程的解法

这里写目录标题 微分方程数值求解——有限差分法matlab代码差分法的运用(依旧是连续变量——>离散网格点) PDE求解思路demo1demo2 微分方程数值求解——有限差分法 差分方法又称为有限差分方法或网格法,是求偏微分方程定解问题的数值解中…

微分方程数值解法(2)——椭圆型方程的有限差分法

此处参考教材为李荣华的《微分方程数值解法》 使用工具:Matlab 1. 算法:矩形网格上5点差分格式 2. 算法 I.需要求解的函数 function [v,vx,vy,f,aa,bb,cc,dd]u2D(x,y,ft)% ft为方程编号,u1D为精确解函数u(t),注意与…

基础数学(8)——常微分方程数值解法

文章目录 期末考核方式基础知识解析解(公式法)解析解例题(使用公式法,必考)解析解的局限性 数值解数值解的基本流程 显示Euler法显示欧拉(差值理解)显示欧拉(Taylor展开理解&#xf…

微分方程数值解法(1)——常微分方程初值问题的数值解法

此处参考教材为李荣华的《微分方程数值解法》 使用工具:Matlab 1. 算法 注: 最后一行应为k4,上面为笔误 2. 算法 I.需要求解的函数 function ff1D(t,u,ft)% ft为方程编号,u1D为精确解函数u(t),注意与f1D对应右端项函数f(t,u(t))switch ftcase 1 %P10…

偏微分方程数值解法pdf_天生一对,硬核微分方程与深度学习的联姻之路

机器之心原创 作者:蒋思源 微分方程真的能结合深度神经网络?真的能用来理解深度神经网络、推导神经网络架构、构建深度生成模型?本文将从鄂维南、董彬和陈天琦等研究者的工作中,窥探微分方程与深度学习联袂前行的路径。 近日&…

椭圆型偏微分方程数值解法

一、 一维椭圆方程数值解 matlab代码&#xff1a; function chap2_fdm_elliptic_1D % 一维椭圆方程求解(常微分方程边值问题) % -u q(x)u f(x), 0<x<1, 取q(x) x, f(x) (x-1)exp(x) % u(0) 1, u(1) e; 边界条件 % 真解为 u exp(x)N 20; h 1/N; x_al…

Python小白的数学建模课-11.偏微分方程数值解法

偏微分方程可以描述各种自然和工程现象&#xff0c; 是构建科学、工程学和其他领域的数学模型主要手段。 偏微分方程主要有三类&#xff1a;椭圆方程&#xff0c;抛物方程和双曲方程。 本文采用有限差分法求解偏微分方程&#xff0c;通过案例讲解一维平流方程、一维热传导方程…

偏微分方程数值解法pdf_单摆-微分方程浅谈

引子[1] 单摆&#xff0c;这个在中学物理都学过的东西&#xff0c;应该是非常熟悉了。 图片来源-维基百科 小角度简单摆 若最高处( )的绳子和最低处(速度最大值)的绳子的角度为 &#xff0c;则可使用下列公式算出它的振动周期。 公式证明 摆球受力分析 绳与对称线夹角为 &…

偏微分方程数值解法pdf_数值模拟偏微分方程的三种方法:FDM、FEM及FVM

偏微分方程数值模拟常用的方法主要有三种:有限差分方法(FDM)、有限元方法(FEM)、有限体积方法(FVM),本文将对这三种方法进行简单的介绍和比较。 一.有限差分方法 有限差分方法(Finite Difference Methods)是数值模拟偏微分方程最早采用的方法,至今仍被广泛运用。该方法包括区…

正圆锥体空间方程_数值模拟偏微分方程的三种方法:FDM、FEM及FVM

偏微分方程数值模拟常用的方法主要有三种:有限差分方法(FDM)、有限元方法(FEM)、有限体积方法(FVM),本文将对这三种方法进行简单的介绍和比较。 一.有限差分方法 有限差分方法(Finite Difference Methods)是数值模拟偏微分方程最早采用的方法,至今仍被广泛运用。该方法包括区…

抖音图标

抖音图标&#xff1a; 1.创建一张画布&#xff0c;再用圆角矩形工具创建一个圆角矩形 2.新建一个图层&#xff0c;用椭圆选框工具创建一个圆&#xff0c;再alt键从选区减去一个圆 3.再填充一个颜色&#xff0c;再剪切1/4圆接着粘贴拖拽至右上方 4.新建一个图层&#xff0c;用…

抖音图标——ps

抖音图标 1.用圆角矩形工具画个圆角&#xff08;空格键可以移动&#xff09;&#xff0c;填充为黑色&#xff0c;把此图层转换为珊格式化 2.再新建一个图层&#xff0c;用椭圆选框工具画个换个环&#xff08;用alt会出现&#xff0c;shift会出现加号&#xff09; 3.Ctrlx剪…

抖音软件分析

前几天看了看关于短视频软件的火的一些话题&#xff0c;就去看了看关于抖音的&#xff0c;对于抖音是那几个方面&#xff0c;自己也去做了一些分析&#xff0c;首先是在两个方面去做的一个理解&#xff0c;一个是软件制作&#xff0c;一个是商业运营。 软件制作 在抖音的软件…

仿抖音视频自动播放html,vue 仿抖音视频播放切换

一、第一部分html页面的准备 {{item.title}} {{item.introduction}} 二、数据说明部分 data() {let u = navigator.userAgent; return {showSlide: 0, allLoaded: false, //数据是否全部加载完 page: 1, isLoading: true, option: {}, current: 0, videoList: [], isVideoShow:…

抖音账号官方认证

介绍 认证功能入口 【我】—【创作者服务中心】— 【官方认证】 抖音黄V是什么&#xff1f; 抖音黄V是抖音平台对个人能力与专业性的认可。换句话讲&#xff0c;黄V即能体现个人身份标签又可以获得官方在内容发布的“豁免权”。如未认证的用户去进行科普&#xff0c;轻则警…

仿抖音首页界面

目录 效果图 顶部相关代码 顶部效果图 内容相关代码 内容效果图 底部导航栏相关代码 底部导航栏效果图 完整代码 html css js ​flexible.js 要想做出抖音短视频的首页界面&#xff0c;我们要引用swiper插件、还需要用到iconfont图标&#xff08;可自行到官网上下载…