python装饰器原理源码示例分析
作者:攻城狮白玉 发布时间:2022-03-25 18:31:04
前言
最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡。你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡。但是你喜欢的女孩子还是你喜欢的女孩子。如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该可以给你玩,该打电话打电话,该玩游戏玩游戏,该收藏攻城狮白玉的博客就收藏攻城狮白玉的博客。而你的手机就变成了带手机壳的手机。
装饰器就是python的一个拦路虎,你干或者不 * ,它都在那里。如果你想学会高级的python用法,装饰器就是你这个武松必须打倒的一只虎
本文的环境如下:
win10
python3.7
一、什么是装饰器
装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。
装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
二、为什么要用装饰器
使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。
函数也可以作为函数的参数进行传递的。
通过下面这个简单的例子可以更直观知道函数名是如何直接作为参数进行传递
def baiyu():
print("我是攻城狮白玉")
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
func = baiyu # 这里是把baiyu这个函数名赋值给变量func
func() # 执行func函数
print('------------')
blog(baiyu) # 把baiyu这个函数作为参数传递给blog函数
执行结果如下所示:
接下来,我想知道这baiyu
和blog
两个函数分别的执行时间是多少,我就把代码修改如下:
import time
def 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这个函数名赋值给变量func
func() # 执行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 time
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
def count_time(func):
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
if __name__ == '__main__':
baiyu = count_time(baiyu) # 因为装饰器 count_time(baiyu) 返回的时函数对象 wrapper,这条语句相当于 baiyu = wrapper
baiyu() # 执行baiyu()就相当于执行wrapper()
这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数,把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。
当然,这里的wrapper函数名是可以自定义的,只要你定义的函数名,跟你return的函数名是相同的就好了
四、装饰器的语法糖
你如果看过其他python项目里面的代码里,难免会看到@符号,这个@符号就是装饰器的语法糖。因此上面简单的装饰器还是可以通过语法糖来实现的,这样就可以省去
baiyu = count_time(baiyu)
这一句代码,而直接调用baiyu()这个函数
换句话说,其实使用装饰器的是,默认传入的参数就是被装饰的函数。
import time
def 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 time
def 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 time
def 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 wrapper
return 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 time
class BaiyuDecorator:
def __init__(self, func):
self.func = func
print("执行类的__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 = arg1
self.arg2 = arg2
def __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 wrapper
def BaiyuDecorator_2(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器2')
return wrapper
def 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)))
来源:https://blog.csdn.net/zhh763984017/article/details/120072425
猜你喜欢
- MD5(Message-Digest Algorithm 5) 模块用于计算信息密文(信息摘要),得出一个128位的密文。sha模块跟md5
- objectobject 是 Python 为所有对象提供的父类,默认提供一些内置的属性、方法;可以使用 dir 方法查看新式类以 obje
- 今天分享 3 个 Python 编程小技巧,来看看你是否用过?1、如何按照字典的值的大小进行排序我们知道,字典的本质是哈希表,本身是无法排序
- 这篇主要探讨 ** 和 * 前缀运算符,**在变量之前使用的*and **运算符.一个星(*):表示接收的参数作为元组来处理两个星(**):
- 前言可能很多小伙伴会因为pycharm全是英文而烦恼吧,本博主呢也是一个英语刚过四级的小白,深知大家的难处,所以会奉上最详细的修改中文的教程
- 使用pandas读取xml文件报错“ Unsupported format, or corrupt file: Expected BOF r
- 近日,2018年最具就业前景的7大编程语言排行榜出炉了。这次的编程语言排行榜是由CodingDojo(编码道场)发布。在此次的最有“钱”途的
- 使用Requests测试带签名的接口部分业务为了安全需要,需要对接口请求数据做签名校验,一般制定一下规则1、业务方接入系统,需申请业务ID以
- Sjoerd Visscher 发现了一个简洁的 方法 让样式在 IE 中作用到未知的元素上——仅需 JS 创建此未知元素即可:docume
- 一、概述dba在工作中避不开的两个问题,sql使用绑定变量到底会有多少的性能提升?数据库的审计功能如果打开对数据库的性能会产生多大的影响?最
- ChainMap是Python collections模块中的一个成员,它用来将多个map组合到一起。Chain是链条的意思,字面意思就是把
- WinHttp; // Microsoft WinHTTP Services, version 5.1Alias HTTPREQUEST_P
- ASP中RegExp是什么 '名字字符检验Public Function CheckName(Str) &nbs
- 如下所示:import urllibimport urllib2 import osimport timeimport re import
- 自带验证器1、UniqueValidator用于验证(唯一)unique=True的字段,常用参数:queryset: required,用
- 问题:编写一个在1,2,…,9(顺序不能变)数字之间插入+或-或什么都不插入,使得计算结果总是100的程序,并输出所有的可能性。例如:1 +
- EM算法实例通过实例可以快速了解EM算法的基本思想,具体推导请点文末链接。图a是让我们预热的,图b是EM算法的实例。这是一个抛硬币的例子,H
- 一直以来,Web 字体背着单调的恶名,因为想在网页上显示一种字体,该字体文件本身必须存在于用户的电脑中,由于这个限制,加上 Web 用户可能
- 前言利用Python docx模块,可以很方便地打开和修改Word 2007及以后的文档。本文简单地介绍了如何使用python修改word文
- 本文实例讲述了javascript设置页面背景色及背景图片的方法。分享给大家供大家参考,具体如下:<!DOCTYPE HTML PUB