【python小课堂专栏】python小课堂29 - 进阶必修之装饰器

2023-10-24 11:38

本文主要是介绍【python小课堂专栏】python小课堂29 - 进阶必修之装饰器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

前言

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

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

装饰器的定义

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

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


import timedef 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 wrapperdef 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,之前的基础篇讲过关键词参数,详见:python小课堂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(咪哥杂谈)。

这篇关于【python小课堂专栏】python小课堂29 - 进阶必修之装饰器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/274891

相关文章

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

Python 字符串占位

在Python中,可以使用字符串的格式化方法来实现字符串的占位。常见的方法有百分号操作符 % 以及 str.format() 方法 百分号操作符 % name = "张三"age = 20message = "我叫%s,今年%d岁。" % (name, age)print(message) # 我叫张三,今年20岁。 str.format() 方法 name = "张三"age

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

29 哈希

目录 unordered系列关联式容器底层结构模拟实现 1. unordered系列关联式容器 在c++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2N log2​N,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能将元素找到,因此在c++11中,stl又提供了4个un

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

python 喷泉码

因为要完成毕业设计,毕业设计做的是数据分发与传输的东西。在网络中数据容易丢失,所以我用fountain code做所发送数据包的数据恢复。fountain code属于有限域编码的一部分,有很广泛的应用。 我们日常生活中使用的二维码,就用到foutain code做数据恢复。你遮住二维码的四分之一,用手机的相机也照样能识别。你遮住的四分之一就相当于丢失的数据包。 为了实现并理解foutain

flask 中使用 装饰器

因为要完成毕业设计,我用到fountain code做数据恢复。 于是在github上下载了fountain code的python原代码。 github上的作者用flask做了fountain code的demo。 flask是面向python的一个网站框架。 里面有用到装饰器。 今天笔试的时候,我也被问到了python的装饰器。