【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

相关文章

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Python安装时常见报错以及解决方案

《Python安装时常见报错以及解决方案》:本文主要介绍在安装Python、配置环境变量、使用pip以及运行Python脚本时常见的错误及其解决方案,文中介绍的非常详细,需要的朋友可以参考下... 目录一、安装 python 时常见报错及解决方案(一)安装包下载失败(二)权限不足二、配置环境变量时常见报错及