百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

python小课堂29 - 进阶必修之装饰器

off999 2024-12-03 00:12 11 浏览 0 评论

前言

装饰器(Decorators)是 Python 的一个重要部分,通俗的说:它们是修改其他函数的功能的函数,使用装饰器有助于让代码更简短,也更 Pythonic(Python范儿)。大多数初学者不知道在哪儿使用它们,跟随笔者的步伐来看下装饰器如何使用吧!

PS:装饰器和闭包一样,并不是说少了装饰器对于Python编写代码来说就不能完成相应的需求功能了,装饰器是在编程中的设计模式中抽象出来的一个重要思想,许多框架都使用到了装饰器。

装饰器的定义

在这里,笔者首先不会直接给出装饰器的定义,因为它本身的含义就很抽象,并不像上一次介绍的高阶函数,了解入参和结果就能使用了。依然遵循老规矩,采用 场景 + 示例 的模式一步步推导出装饰器的定义来。

场景1:假设现在有一个函数,这个函数里面什么也不干,仅仅是让它休眠2秒,现在的需求是,如何才能不修改原有函数的基础上,直接对原来的函数增加一个打印耗时的功能呢?(实际上这是写出良好代码的设计原则,之前有提到过,开闭原则,对修改是封闭的,对拓展是开放的,非专业人员混个眼熟啦~)思考尝试想一下或者写一下试试,再看下面的程序:

import time


def sleep_func():
 time.sleep(2)


def time_spend_func(func):
 before = time.time()
 print(f"开始执行前时间:{before}")
 func()
 after = time.time()
 print(f'开始执行后时间:{after}')
 print(f'耗时时间:{int(after - before)}s')


time_spend_func(sleep_func)

推导步骤1

讲解一下上面的程序,使用 Python 自带的 time 模块,我们可以调用 sleep 方法让程序休眠,参数是以秒为单位,可以自行 Ctrl 点进去看下官方注释。

在场景设定下,不修改原来的代码,想看原来的函数一共耗时多少,所以可以新增一个专门计算耗时的函数,姑且称之为 time_spend_func 。在其里面使用 time.time() 方法可以得到当前时间的时间戳形式。(时间戳 timestamp ,一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。)

可以看到最后调用新增程序时,笔者将原有函数以参数的形式传入了计算耗时的函数中(这点在闭包时已经介绍过了,详见小课堂26)。传入原有函数的目的,是为了在新增函数中调用原有函数的逻辑,也就是睡上 2s。

最终耗时的具体时间,通过时间戳的数字进行减法得到的是 float 型,将之转为数字整型即可显示最终耗时结果。

这样的实现方法是没有对原有函数进行修改的,虽然确实符合场景设定,但是它本身是存在缺点的!大家可以想一下,新增函数所实现的功能确实属于新增功能,但这个功能原应该“依赖”于原有函数,这句话怎么理解呢?消耗时间这个动作是属于原有函数本身的,而现在的新增函数是在调用原有函数的前后强制新增了一段记录时间的代码

如果还不能理解,来看下上面的写法和下面的写法有什么不同吗?

def sleep_func():
 time.sleep(2)


before = time.time()
print(f"开始执行前时间:{before}")
sleep_func()
after = time.time()
print(f'开始执行后时间:{after}')
print(f'耗时时间:{int(after - before)}s')

无非就是取消了函数的写法,强制在调用原函数的前后加上了时间的逻辑!

好好体会一下,明白了以后,我们可以通过装饰器来解决这一缺点,使新增功能原本就属于原有函数,继续看推导2。

推导步骤2:

接下来看下 Python 中的装饰器如何书写呢?

def decorators(func):
 def wrapper():
 before = time.time()
 print(f"开始执行前时间:{before}")
 func()
 after = time.time()
 print(f'开始执行后时间:{after}')
 print(f'耗时时间:{int(after - before)}s')
 return wrapper

def sleep_func():
 time.sleep(2)

f = decorators(sleep_func)
f()

先仔细看一下代码,decorator 是装饰器的意思,wrapper 是包装的意思。看完代码的同学,乍一看结构是不是与之前提到的闭包结构非常相似!只是少了一个所谓的“环境变量”....

解释一下这段代码,定义一个外层函数 decorator 装饰器,内层函数定义一个 wrapper 的包装函数,其中外层函数接受外界传入的函数作为参数,并且在内部函数 wrapper 中进行调用,而其余的打印耗时的逻辑依然没有修改。

这么一看,肯定有人会有疑问,这样的写法意义在哪里?最终调用还是和推导1一样,调用装饰器函数,并且将具体的休眠函数作为参数传入,最后还多出一步f的调用。没错,如果你想到了这里,恭喜你,说明走心了!这样的写法确实不是最完美的,笔者写到这里仅仅是定义了一个装饰器而已,但是并没有使之起到装饰的作用,下面继续看推导3。

推导步骤3

有了前两步的推导,第三步就是要介绍一下Python的语法糖了!

语法糖(syntactic sugar)是指编程语言中可以更容易的表达一个操作的语法,它可以使程序员更加容易去使用这门语言:操作可以变得更加清晰、方便,或者更加符合程序员的编程习惯。

在Python中,通过 @ 符号来表示装饰器!而 @ 则是Python的语法糖之一,具体用法如下,终极装饰器的示例:

def decorators(func):
 def wrapper():
 before = time.time()
 print(f"开始执行前时间:{before}")
 func()
 after = time.time()
 print(f'开始执行后时间:{after}')
 print(f'耗时时间:{int(after - before)}s')

 return wrapper

@decorators
def sleep_func():
 time.sleep(2)

sleep_func()

解释一下代码,在推导2的基础上,只需要将 @装饰器函数的名称 加在原有函数的“头上”,便可完成所谓的装饰器装饰,最终调用原有函数即可执行。看到这样的写法,知道为什么叫装饰器了吧~因为是装饰在原有函数的“头上”来做装饰物的呀![罒ω罒]......

了解Java的人可能见过这样的写法,这种写法在Java中被叫做注解。但是意义却与Python中的装饰器截然不同。

推导了三个步骤,最终带出了装饰器的定义,大家有没有看出来最终装饰器的好处呢?下面来说明一下:

1. 使用类似闭包的方式来定义装饰器,通过 @ + 装饰器名称 来装饰已有函数。

2. 装饰器的好处,符合了开闭原则,不用修改原有代码,只需新增。

3. 终极装饰器的写法,不会改变原有函数的调用,原来怎么调用,加上装饰器后依然可以使用原来的方法调用,此处指 sleep_func() 的调用。

4. 装饰器还起到了代码复用的作用,因为一个装饰器是可以修饰多个函数的。

装饰器多参数

在上面提到了装饰器还有代码复用的作用,来看下具体示例就会明白为什么这么说了,场景如下:

在推导1~3的场景下,假设我们的 sleep_func 函数休眠时间是由外界传入的,而非固定值,如何修改下推导3中的装饰器呢?

@decorators
def sleep_func(seconds):
 time.sleep(seconds)
 print(f'休眠函数休眠时间:{seconds}')

修改休眠函数,如上,然后直接调用,来看下结果:

可以看到,报错啦,错误信息大概意思是 wrapper() 必须给定一个参数,因为我们调用的函数传入了一个参数3秒,所以在装饰器中的内层函数需与之同步,修改为如下代码:

def decorators(func):
 def wrapper(seconds):
 before = time.time()
 print(f"开始执行前时间:{before}")
 func(seconds)
 after = time.time()
 print(f'开始执行后时间:{after}')
 print(f'耗时时间:{int(after - before)}s')

 return wrapper

@decorators
def sleep_func(seconds):
 time.sleep(seconds)
 print(f'休眠函数休眠时间:{seconds}')

sleep_func(3)

在 wrapper 函数以及内部调用的 func 两处同步加入 seconds 形参,再次调用:

但是这样的写法太死板了,一点也不灵活,假如在 sleep_func 函数中,业务逻辑变了,其中有多个参数传递,那装饰器中岂不是也要修改无数次了?而且每对应一个函数,不同参数个数就需要新建一个装饰器,何谈代码的复用?!

所以,多参数的情况依然可以进行完善,使之达到一个通用的状态。这里要普及一个Python小知识点了,知道这两个小知识点后对后续写出灵活性代码非常有用!

*args 和 **kwargs 的妙用

举两个小例子,一看便了解Python中这两个写法的用途了!

1. *args 用法

def print_words(*args):
 print(type(args))
 for word in args:
 print(word)

print_words(1, '呵呵', True, 1.02333)

在定义函数时,可以使用 *args 作为形参,代表着可以接受无穷多个参数,以上面为例,可以看到在定义得打印函数中,调用时传入了各种类型的参数,最终通过 for 循环遍历打印,可见 *args 本身类型是一个 tuple 类型。需要注意的是,这里的写法 * 是必须的,args 作为形参名称,可修改。

2. **kwargs 用法

def print_words(**kwargs):
 print(type(kwargs))
 print(kwargs)

print_words(a=1, e='呵呵', f=True, b=1.02333)

**kwargs,之前的基础篇讲过关键词参数,详见:小课堂16。

而此种写法作为形参传入,最终实参调用时,传入到函数内部解析之后其实就是字典形式,关键词作为字典的key,值则作为 value。

3. *args 和 **kwargs 混合使用

def print_words(*args, **kwargs):
 print(type(args))
 print(args)
 print(type(kwargs))
 print(kwargs)

print_words(1, 23, 4, '----分割线----', a=1, e='呵呵', f=True, b=1.02333)

混合使用,效果更佳!这样定义函数的形参非常灵活,可以看到许多第三方库的底层都是利用了这样的写法。。思路拉回到正题,认识到这种写法的好处,将之用到我们的装饰器中去。

装饰器最终灵活版

用到我们的装饰器中去。示例代码如下:

def decorators(func):
 def wrapper(*args, **kwargs):
 before = time.time()
 print(f"开始执行前时间:{before}")
 func(*args, **kwargs)
 after = time.time()
 print(f'开始执行后时间:{after}')
 print(f'耗时时间:{int(after - before)}s')

 return wrapper

@decorators
def sleep_func(seconds, a, b, **kwargs):
 time.sleep(seconds)
 print(f'休眠函数休眠时间:{seconds}')
 print(a, b)
 print(kwargs)

sleep_func(3, '我是a', '我是b', c='我是{"c":"c"}', d='我是{"d":"d"}')

注意标记红框的位置,在调用 func 时,传入的都是带星号的形参,只有使用时才将星号去掉!不要记混啦,建议多多练习!写到这里,最终完美版的装饰器也就诞生了,整篇下来通过一步步的推导将装饰器梳理了一遍!在说最后一句,什么叫做可以复用的装饰器,在上面的基础上在多加一个新的函数,使用相同的装饰器:

@decorators
def sleep_func_new(words):
 print(words)
 time.sleep(1)


sleep_func_new('调用了sleep_func_new')

总结

感觉一写技术文章,不知不觉就超长了.....又是一篇非常详细,非常长的文章,目测上千字啦,学到这里,关于Python的基础与进阶基本上告离段落了,大家看了笔者的文章有木有成长一些呢?从开文到现在,无论是从文字功底还是文字的排版,对于笔者而言都有不少进步,通过各大平台推广,每天公众号的后台人数也在一点点增加,只要有读者看,就是继续写下去的最大动力!

接下来的文章跟实战挂钩了,将之前所有知识点串联在一起最好的方式就是通过实战去训练,而学习Python最有趣的就是关于爬虫的知识了!下一章通过爬虫带你快速入门,串联所有学过的知识以及网络信息采集的开篇!敬请期待....


有想交流Python的同学,欢迎关注公号:migezatan(咪哥杂谈)。

相关推荐

独家 | 5 个Python高级特性让你在不知不觉中成为Python高手

你已经使用Python编程了一段时间,编写脚本并解决各种问题。是你的水平出色吗?你可能只是在不知不觉中利用了Python的高级特性。从闭包(closure)到上下文管理器(contextmana...

Python装饰器

Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...

中高阶Python常规用法--上下文管理器

Python以简单性和通用性著称,是一种深受全球开发人员喜爱的编程语言。它提供了大量的特性和功能,使编码成为一种愉快的体验。在这些功能中,一个经常被新手忽视的强大工具是上下文管理器。上下文管理器是高...

Python小案例67- 装饰器

Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...

python常用的语法糖

概念Python的语法糖(SyntacticSugar)是指那些让代码更简洁、更易读的语法特性,它们本质上并不会增加新功能,但能让开发者更高效地编写代码。推导式写法推导式是Python最经典的...

python - 常用的装饰器 decorator 有哪些?

python编程中使用装饰器(decorator)工具,可以使代码更简洁清晰,提高代码的重用性,还可以为代码维护提供方便。对于python初学者来说,根据装饰器(decorator)的字面意思并不...

python数据缓存怎么搞 ?推荐一个三方包供你参考,非常简单好用。

1.数据缓存说明数据缓存可以说也是项目开发中比不可少的一个工具,像我们测试的系统中,你都会见到像Redis一样的数据缓存库。使用缓存数据库的好处不言而喻,那就是效率高,简单数据直接放在缓存中...

用于时间序列数据的Graphite监视工具

结合第三方工具,Graphite为IT性能监控提供了许多好处。本文介绍其核心组件,包括Carbon、Whisper以及安装的基本准则。Graphite监视工具可实时或按需,大规模地绘制来自多个来源的时...

Python3+pygame实现的坦克大战

一、显示效果二、代码1.说明几乎所有pygame游戏,基本都遵循一定的开发流程,大体如下:初始化pygame创建窗口while循环检测以及处理事件(鼠标点击、按键等)更新UI界面2.代码创建一个m...

Python之鸭子类型:一次搞懂with与上下文装饰器

引言在鸭子类型的理念的基础之上,从关注类型,转变到关注特性和行为。结合Python中的魔法函数的体系,我们可以将自定义的类型,像内置类型一样被使用。今天这篇文章中,接着该话题,继续聊一下with语法块...

Python必会的50个代码操作

学习Python时,掌握一些常用的程序操作非常重要。以下是50个Python必会的程序操作,主要包括基础语法、数据结构、函数和文件操作等。1.HelloWorldprint("Hello,...

一文掌握Python 中的同步和异步

同步代码(Sync)同步就像在一个流水线上工作,每个任务都等待前一个任务完成。示例:机器A切割钢板→完成后,机器B钻孔→完成后,机器C上色。在Python中,同步代码看起来像这样:im...

python 标注模块timeit: 测试函数的运行时间

在Python中,可以使用内置的timeit模块来测试函数的运行时间。timeit模块提供了一个简单的接口来测量小段代码的执行时间。以下是使用timeit测试函数运行时间的一般步骤:导入...

Python带你找回童年的万花尺

还记得小时候的万花尺吧?这么画:一点也不费脑筋,就可以出来这么多丰富多彩的复杂几何图形。具体而言,可以用万花尺玩具(如图2-1所示)来绘制数学曲线。这种玩具由两个不同尺寸的塑料齿轮组成,一大一小。小的...

Python 时间模块深度解析:从基础到高级的全面指南

直接上干货一、时间模块核心类介绍序号类名说明1datetime.datetime表示一个具体的日期和时间,结合了日期和时间的信息。2datetime.date表示一个具体的日期。3datetime.t...

取消回复欢迎 发表评论: