一文带你读懂python装饰器

系统 1958 0

文章目录

  • 前言
  • 一、函数
    • 1.1 函数作为对象传递
    • 1.2 函数作为参数传递
    • 1.3 函数可嵌套
    • 1.4 返回函数本身
  • 二、装饰器
    • 2.1 基础装饰器
    • 2.2 带参装饰器
    • 2.3 装饰器自定义参数
    • 2.4 类装饰器
    • 2.5 装饰器嵌套
  • 三、装饰器的应用
  • 四、总结

前言

我的个人网站:https://www.gentlecp.com

python中有一个很经典的用法就是装饰器,它用于在不修改原始函数的情况下,添加新的功能到原始函数中,但是这章内容比较难以理解,本文就从函数到装饰器以及装饰器在现实生产中的应用举出样例,希望能够帮助大家更好地理解装饰器到底有何用处

附:文章个人网站链接

一、函数

谈装饰器前先对函数要有一个深刻理解。在python中,万物皆对象,函数也不例外,我们创建一个函数将其打印出来,看看结果:

            
              
                def
              
              
                func
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is func: {}'
              
              
                .
              
              
                format
              
              
                (
              
              message
              
                )
              
              
                )
              
              
                print
              
              
                (
              
              func
              
                )
              
            
          

一文带你读懂python装饰器_第1张图片
可以看到返回值为一个内存地址,说明函数是对象,既然是对象,自然可以进行赋值传递,参数传递操作。

1.1 函数作为对象传递

看下面的例子:

            
              
                def
              
              
                func
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is func: {}'
              
              
                .
              
              
                format
              
              
                (
              
              message
              
                )
              
              
                )
              
              
    
func_object 
              
                =
              
               func
func_object
              
                (
              
              
                'Hello world'
              
              
                )
              
            
          

在这里插入图片描述
我们将func这个对象直接赋值给func_object,用func_object调用,结果成功输出。说明函数可作为对象传递

1.2 函数作为参数传递

看下面的例子:

            
              
                def
              
              
                func_a
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is func_a:{}'
              
              
                .
              
              
                format
              
              
                (
              
              message
              
                )
              
              
                )
              
              
                def
              
              
                func_b
              
              
                (
              
              func
              
                ,
              
              message
              
                )
              
              
                :
              
              
    func
              
                (
              
              message
              
                )
              
              

func_b
              
                (
              
              func_a
              
                ,
              
              
                'Hello world'
              
              
                )
              
            
          

在这里插入图片描述
我们将func_a作为func_b的参数传入func_b中,并在func_b中调用该函数,打印输出结果。说明函数可以作为参数传递。

1.3 函数可嵌套

这个大家应该都用过,就是在函数中有时候我们会重复利用一部分代码,而这部分代码在其他地方又不会用到,就可以将这部分整合成一个函数,然后进行调用,看下面的例子:

            
              
                def
              
              
                func_root
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                def
              
              
                func_node
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is func_node:{}'
              
              
                .
              
              
                format
              
              
                (
              
              message
              
                )
              
              
                )
              
              
                # 将func_node结果返回
              
              
                return
              
               func_node
              
                (
              
              message
              
                )
              
                
func_root
              
                (
              
              
                'Hello World'
              
              
                )
              
            
          

在这里插入图片描述
我们在func_root中又定义了func_node,并在func_root中返回func_node的处理结果。相当于在函数内部调用了内部的函数,说明函数可嵌套。
举一个形象的例子:
老板(func_root)收到外包商一个项目(‘Hello World’)。
外包商:你帮我打印一下Hello World
老板接收原材料(‘Hello World’)。
老板:我不会啊,那什么,小C你帮我打印一下。
然后将原材料(‘Hello World’)扔给小C(= =)。
小C:好的老板,打印了Hello World,写成了报告。
老板一看:哎哟,不错哦。
然后就将结果return给了外包商。

1.4 返回函数本身

前面是内部函数处理了结果,外部函数将结果返回,因为函数本身是对象,自然也可以作为return返回,看下面这个例子:

            
              
                def
              
              
                func_root
              
              
                (
              
              
                )
              
              
                :
              
              
                def
              
              
                func_node
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is func_node:{}'
              
              
                .
              
              
                format
              
              
                (
              
              message
              
                )
              
              
                )
              
              
                # 将func_node本身返回
              
              
                return
              
               func_node
func_object 
              
                =
              
               func_root
              
                (
              
              
                )
              
              
func_object
              
                (
              
              
                'Hello world'
              
              
                )
              
            
          

同样用上面的例子:
外包商(func_object):你帮我打印Hello World
老板(func_root):我不会啊,我把会打印的人叫来给你用吧,小C你过来一下
外包商:小C,你给我打印Hello World
小C(func_node):print(…)
在代码中,func_object通过func_root这个桥梁,获取到了func_node的地址,这时候它就可以通过()的形式调用func_node的功能。

二、装饰器

2.1 基础装饰器

我们先看一个最基础的装饰器:

            
              
                def
              
              
                my_decorator
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                "I'm wrapper in my_decorator"
              
              
                )
              
              
        func
              
                (
              
              
                )
              
              
                return
              
               wrapper

@my_decorator

              
                def
              
              
                hello
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'hello world'
              
              
                )
              
              
hello
              
                (
              
              
                )
              
            
          

其中@my_decorator称为语法糖,其作用等价于

            
              hello 
              
                =
              
               my_decorator
              
                (
              
              hello
              
                )
              
            
          

在这里插入图片描述
从结果可以看出,hello成功执行了自己的功能(打印hello world),装饰器装饰了hello函数,并在其基础上添加了功能(打印I’m wrapper in my_decorator)。装饰器一般有两层函数,外层my_decorator用于@装饰在其他函数上,它返回内层函数(wrapper)的地址,让被装饰函数可以直接获取到该地址。
前面说过,有了函数地址,自然可以调用函数功能,所以wrapper可以看作被装饰函数的加强版函数,在其内部必然要调用被装饰函数,且添加一些想添加的代码功能,这使得添加的功能和原始功能互不干扰。
举个例子:
小C:我只会打印hello world,别的我不学,哼~
老板:什么?这么菜怎么完成任务?不行,我给你个神器,这个神器可以自动打印I’m wrapper in my_decorator,也不用你学什么了。但是你要把它戴到头上。以后我布置打印任务的时候,我会发命令给这个神器,这个神器再提示你怎么做知道吗?
小C:好的,老板!
老板:神器啊神器,开始打印吧~
神器:打印I’m wrapper in my_decorator完成,小C,打印hello world
小C:好嘞,打印hello world。
有人可能要问了,为什么不直接用一个函数wrapper,将hello传入其中,然后调用呢?
注意!装饰器的初衷是不影响原始函数的代码和功能,也就是说。假设原始函数hello是一个接口,别人一直用hello作为接口调用,如果你用wrapper接收hello,那么接口的名称就要改动成wrapper,外面的人并不知道这个,还是用hello调用,就会导致出错!而用装饰器的形式,你发现没有,hello接口没有变,但是新功能已经添加进去了。

2.2 带参装饰器

被装饰函数难免会有参数传入,如何将这些参数一并传入到装饰器中呢?看下面的代码:

            
              
                def
              
              
                my_decorator
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                "I'm wrapper in my_decorator"
              
              
                )
              
              
        func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                return
              
               wrapper

@my_decorator

              
                def
              
              
                hello
              
              
                (
              
              name
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'hello world '
              
              
                +
              
              name
              
                )
              
              
    
hello
              
                (
              
              
                'CP'
              
              
                )
              
            
          

这里用到了 args,**kwargs,这样就可以接收任意数量或类型的参数,关于 args,**kwargs网上有很多解释的文章,这里不多赘述。可以看到成功地进行了参数传递。

2.3 装饰器自定义参数

前面装饰器一直是接收被装饰函数的参数,那么如果装饰器自己要定义参数呢?例如定义装饰器参数num,用于指定装饰器调用的次数,看下面的代码:

            
              
                def
              
              
                repeat
              
              
                (
              
              num
              
                )
              
              
                :
              
              
                def
              
              
                my_decorator
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                )
              
              
                :
              
              
                for
              
               i 
              
                in
              
              
                range
              
              
                (
              
              num
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                "I'm wrapper in my_decorator"
              
              
                )
              
              
                func
              
                (
              
              
                )
              
              
                return
              
               wrapper
    
              
                return
              
               my_decorator

@repeat
              
                (
              
              
                5
              
              
                )
              
              
                def
              
              
                hello
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'hello world'
              
              
                )
              
              
     
hello
              
                (
              
              
                )
              
            
          

repeat装饰器传入了参数5,用于将他内部的函数执行5次
一文带你读懂python装饰器_第2张图片

2.4 类装饰器

类也可以作为装饰器使用,它依赖于函数__call__,实际上,每次调用类的实例,函数__call__便执行一次。看下面的代码:

            
              
                class
              
              
                CountClass
              
              
                :
              
              
                def
              
              
                __init__
              
              
                (
              
              self
              
                ,
              
               func
              
                )
              
              
                :
              
              
        self
              
                .
              
              func 
              
                =
              
               func
        self
              
                .
              
              calls 
              
                =
              
              
                0
              
              
                def
              
              
                __call__
              
              
                (
              
              self
              
                ,
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
              
        self
              
                .
              
              calls 
              
                +=
              
              
                1
              
              
                print
              
              
                (
              
              
                'calls: {}'
              
              
                .
              
              
                format
              
              
                (
              
              self
              
                .
              
              calls
              
                )
              
              
                )
              
              
                return
              
               self
              
                .
              
              func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              

@CountClass

              
                def
              
              
                hello
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                "hello world"
              
              
                )
              
              

hello
              
                (
              
              
                )
              
              
hello
              
                (
              
              
                )
              
            
          

一文带你读懂python装饰器_第3张图片
本质上作用与函数装饰器相同,根据自己的需要选择用函数装饰器还是类装饰器

2.5 装饰器嵌套

装饰器说到底还是函数,因此装饰器本身也可以被装饰,看下面的例子:

            
              
                import
              
               functools


              
                def
              
              
                my_decorator1
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is decorator1'
              
              
                )
              
              
        func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                return
              
               wrapper



              
                def
              
              
                my_decorator2
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                'Here is decorator2'
              
              
                )
              
              
        func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                return
              
               wrapper


@my_decorator1
@my_decorator2

              
                def
              
              
                hello
              
              
                (
              
              message
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              message
              
                )
              
              
    
hello
              
                (
              
              
                'hello world'
              
              
                )
              
            
          

一文带你读懂python装饰器_第4张图片
由结果可以看出来,执行的顺序是​decorator1 -> decorator2 -> hello

三、装饰器的应用

装饰器的应用场景有很多,例如登录验证,日志记录,合理性检查等。下面以登录验证为例:假设一个网站有两个功能,登录和评论,而评论功能必须要先检查用户是否登录,登录才能使用否则调用评论会提示需要登录,看下面代码:

            
              IS_LOGIN 
              
                =
              
              
                False
              
              
                # 全局变量作为是否登录的标志
              
              
USER 
              
                ,
              
               PWD 
              
                =
              
              
                'CP'
              
              
                ,
              
              
                '123456'
              
              
                # 假设只有一个用户
              
              
                def
              
              
                require_login
              
              
                (
              
              func
              
                )
              
              
                :
              
              
                def
              
              
                wrapper
              
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                :
              
              
                if
              
               IS_LOGIN
              
                :
              
              
                # 验证通过,可以评论
              
              
            func
              
                (
              
              
                *
              
              args
              
                ,
              
              
                **
              
              kwargs
              
                )
              
              
                else
              
              
                :
              
              
                # 验证不通过
              
              
                print
              
              
                (
              
              
                '验证失败,您未登录'
              
              
                )
              
              
            login
              
                (
              
              
                )
              
              
                return
              
               wrapper


              
                # 登录 
              
              
                def
              
              
                login
              
              
                (
              
              
                )
              
              
                :
              
              
                global
              
               IS_LOGIN
    
              
                while
              
              
                True
              
              
                :
              
              
                print
              
              
                (
              
              
                '请输入登录用户名:'
              
              
                )
              
              
        user 
              
                =
              
              
                input
              
              
                (
              
              
                )
              
              
                print
              
              
                (
              
              
                '请输入密码:'
              
              
                )
              
              
        pwd 
              
                =
              
              
                input
              
              
                (
              
              
                )
              
              
                # 这里为了简便没有做密码输入加密处理,实际开发需要做
              
              
                if
              
               user 
              
                ==
              
               USER 
              
                and
              
               pwd 
              
                ==
              
               PWD
              
                :
              
              
                print
              
              
                (
              
              
                '登录成功'
              
              
                )
              
              
            IS_LOGIN 
              
                =
              
              
                True
              
              
                break
              
              
                else
              
              
                :
              
              
                print
              
              
                (
              
              
                '用户名或密码输入错误,请重新输入'
              
              
                )
              
              
                # 评论功能
              
              
@require_login

              
                def
              
              
                comment
              
              
                (
              
              
                )
              
              
                :
              
              
                print
              
              
                (
              
              
                '欢迎使用评论功能,请输入你要评论的内容:'
              
              
                )
              
              
    com 
              
                =
              
              
                input
              
              
                (
              
              
                )
              
              
                print
              
              
                (
              
              
                '你的评论是:{}'
              
              
                .
              
              
                format
              
              
                (
              
              com
              
                )
              
              
                )
              
              

comment
              
                (
              
              
                )
              
              
comment
              
                (
              
              
                )
              
            
          

运行结果:
一文带你读懂python装饰器_第5张图片
我们定义两个函数和一个装饰器函数,login用于登录,comment用于评论,comment被require_login装饰器装饰,在require_login装饰器中会判断全局变量IS_LOGIN来检查用户是否已经登录,如果登录则执行comment的功能,如果未登录,则提示用户进行登录。
第一次调用comment()提示验证失败,您未登录,并让用户进行登录操作,登录成功后,进入评论的核心功能,用户可以进行评论。
通过上面的例子我们可以发现,comment还是那个comment,我们并没有对其修改,但是让其使用的时候多了一层验证,而且这种方式在不改变接口的同时降低了代码耦合性,使得程序员维护成本大大降低,所以是一种非常值得学习、掌握的方法。

四、总结

关于装饰器的使用,我大概就总结了以上内容,希望能够帮助到你更好地理解装饰器,如果有任何疑问的地方,欢迎在评论区留言或私信给我,我会尽力解答~
建议: 看完文章后一定要自己动手尝试一下才能更好地掌握,不要想当然以为自己看了就会了,动手才是将东西变成自己的的唯一途径!!!


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论