资讯专栏INFORMATION COLUMN

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

liuchengxu / 3648人阅读

摘要:装饰器的使用符合了面向对象编程的开放封闭原则。三简单的装饰器基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。函数体就是要实现装饰器的内容。类装饰器的实现是调用了类里面的函数。类装饰器的写法比我们装饰器函数的写法更加简单。

目录

前言

一、什么是装饰器

二、为什么要用装饰器

三、简单的装饰器

四、装饰器的语法糖@

五、装饰器传参

六、带参数的装饰器

七、类装饰器

八、带参数的类装饰器

九、装饰器的顺序

总结

写在后面


前言

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

装饰器就是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这个函数名赋值给变量func    func()  # 执行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这个函数名赋值给变量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 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 = wrapper    baiyu()  # 执行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_timedef 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_timedef 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 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 timeclass 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)@BaiyuDecoratordef 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 """)@BaiyuDecoratordef 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 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_3def baiyu():    print("我是攻城狮白玉")if __name__ == "__main__":    baiyu()

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

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

 

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

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

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

总结

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

写在后面

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119072.html

相关文章

  • [python] 初探'函数式编程'

    摘要:前言继续向下看廖大教程,看到了函数式编程这一节,当时是觉得没啥用直接跳过了,这次准备要仔细看一遍了,并记录下一些心得。 前言 继续向下看廖大教程,看到了函数式编程这一节,当时是觉得没啥用直接跳过了,这次准备要仔细看一遍了,并记录下一些心得。 函数式编程 上学期有上一门叫 人工智能 的课,老师强行要我们学了一个叫做 prolog 的语言,哇那感觉确实难受,思维方式完全和之前学过的不一样,...

    xcc3641 评论0 收藏0
  • 深入理解Python中的ThreadLocal变量(下)

    摘要:具体怎么实现的呢,思想其实特别简单,我们在深入理解中的变量上一文的最后有提起过,就是创建一个全局字典,然后将线程或者协程标识符作为,相应线程或协程的局部数据作为。 在上篇我们看到了 ThreadLocal 变量的简单使用,中篇对python中 ThreadLocal 的实现进行了分析,但故事还没有结束。本篇我们一起来看下Werkzeug中ThreadLocal的设计。 Werkzeug...

    dadong 评论0 收藏0
  • python

    Python装饰器为什么难理解? 无论项目中还是面试都离不开装饰器话题,装饰器的强大在于它能够在不修改原有业务逻辑的情况下对代码进行扩展,权限校验、用户认证、日志记录、性能测试、事务处理、缓存等都是装饰器的绝佳应用场景,它能够最大程度地对代码进行复用。 但为什么初学者对装饰器的理解如此困难,我认为本质上是对Py… Python 实现车牌定位及分割 作者用 Python 实现车牌定位及分割的实践。 ...

    chenatu 评论0 收藏0
  • 简单理解Python装饰

    摘要:下面我们一起抛去无关概念,简单地理解下的装饰器。用函数实现装饰器装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。 来源:http://www.lightxue.com/under...        Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。       刚接触装饰器,会...

    Meils 评论0 收藏0
  • 详解Python装饰

    摘要:概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。是一个非常简单的装饰器加强包。 Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello(): ...

    DandJ 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<