zoukankan      html  css  js  c++  java
  • python装饰器详解

    装饰器详解

    装饰器的具体定义:

    1、把要装饰的方法作为输入参数;

    2、在函数体内可以进行任意的操作(可以想象其中会有很多应用场景);

    3、只要确保最后返回一个可执行的函数即可(可以是原来的输入参数函数,也可以是一个新函数)。

    装饰器其实就是一个闭包,把一个函数当做参数后返回一个替代版函数,闭包是装饰器的核心。

    简单解释下闭包的特点:

    一个函数返回的函数对象,这个函数对象执行的话依赖非函数内部的变量值,这个时候,函数返回的实际内容如下:
    1 函数对象
    2 函数对象需要使用的外部变量和变量值
    以上就是闭包
    闭包必须嵌套在一个函数里,必须返回一个调用外部变量的函数对象,才是闭包

    闭包的解释:https://www.cnblogs.com/xiaxiaoxu/p/9785687.html

    先看个例子了解下装饰器的必要性:

    #encoding=utf-8

    import time
    def now():
        print "current time is %s" %time.strftime("%Y-%m-%d %H-%M-%S")

    res=now
    res()

    结果:

    现在如果我们想给now()函数增加一些别的功能,比如在调用该函数前后自动打印一些日志,但又不希望修改原now()的定义,这时候我们的装饰器就配上用场了。

    本质上,decorator就是一个返回函数的高阶函数。所以我们需要定义一个能打印日志的的decorator

    Python2.4以后,支持使用标识符@将装饰器应用到函数上,只需要在函数的定义前加上@和装饰器的名称即可

    代码:

    #encoding=utf-8

    import time

    #定义装饰器
    def log(func):
        def wrapper(*args,**kw):
            print "call func is %s" %func.__name__
            return func(*args,**kw)
        return wrapper


    @log
    def now():
        now = time.strftime("%Y-%m-%d %H-%M-%S")
        print "current time is %s" %now


    now()

    结果:

    分析:

    观察log函数,因为它是一个decorator(装饰器),所以接受一个函数作为参数,并返回一个函数。

    调用now()函数时,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志。

    把@log放到now()函数的定义处,相当于执行了如下语句:

    now = log(now)

    由于log()是一个decorator,返回一个函数,所以,原本的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

    wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。

    在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

    正因为有了(*args, **kw)这样的参数格式,这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法(元祖形式,字典形式),

    同时能够用传递给它的参数(函数)对被装饰的方法进行调用。这样就能处理参数个数不同的函数了。

    在now()函数里加个延时,看下装饰器运行的过程

    #encoding=utf-8

    import time

    #定义装饰器
    def log(func):
        def wrapper(*args,**kw):
            print "call func is %s" %func.__name__
            start_time=time.time()
            func(*args,**kw)
            print "elapse time:",time.time()-start_time
            return ""
        return wrapper


    @log#相当于now=log(now)
    def now():
        now = time.strftime("%Y-%m-%d %H-%M-%S")
        print "current time is %s" %now
        time.sleep(3)


    now()#相当于log(now)(),即wapper()

    结果:

    从结果可以看到,在打印第二行之后,程序等待了3秒后打印了第三行,即在执行wapper()函数时,执行now()函数时等了3秒,然后接着往后执行的打印elapse time部分

    装饰器分类:

    装饰器分为无参数decorator和有参数decorator

    无参数decorator:生成一个新的装饰器函数

    有参数decorator:装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰。

    装饰器的理解步骤:

    第一步-最简单的函数,准备附加额外功能

    代码示例:

    #encoding=utf-8
    '''示例1: 最简单的函数,表示调用了两次'''


    def myfunc():
        print "myfunc() called"

    myfunc()
    myfunc()

    结果:

    第二步:使用装饰函数在函数执行前和执行后分别附加额外功能

    代码:

    #encoding=utf-8

    """替换函数(装饰)
    装饰函数的参数是被装饰的函数对象,返回原函数对象
    替换的过程: myfunc=deco(myfunc)就是装饰的实质性语句"""

    def deco(func):
        print "before myfunc() called."
        func()
        print "  after myfunc() called."
        return func

    def myfunc():
        print " myfunc() called."

    myfunc=deco(myfunc)
    myfunc()
    myfunc()

    结果:

    注意:

    结果中前三行是在myfunc=deco(myfunc)这步打印的,即deco(myfunc)的执行结果,后边两行是两次myfunc()的结果

    注释掉最后两行:

    def deco(func):
        print "before myfunc() called."
        func()
        print "  after myfunc() called."
        return func

    def myfunc():
        print " myfunc() called."

    myfunc=deco(myfunc)
    #myfunc()
    #myfunc()

    结果:结果是deco(myfunc)打印的结果

    装饰器函数中并没有定义新的函数,而是直接返回了原函数,所以myfunc函数执行的是原函数的代码

    第三步-使用@来装饰函数

    代码:

    #encoding=utf-8

    """使用语法@来装饰函数,相当于'myfunc=deco(myfunc)'
    这个过程等价于第二步的程序"""

    def deco(func):
        print "before myfunc() called."
        func()
        print "  after myfunc() called."
        return func

    @deco#等价于myfunc=deco(myfunc)
    def myfunc():
        print " myfunc() called."


    myfunc()
    myfunc()

    结果:

    原理上一步的程序

    第四步-使用内嵌包装函数来确保每次新函数都被调用

     代码:

    #encoding=utf-8

    """内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象"""
    def deco(func):
        def _deco():
            print "before myfunc() called."
            func()
            print "  after myfunc() called."
            #不需要返回func,实际上应返回原函数的返回值
        return _deco#返回的是闭包,因为需要外部传函数变量func

    @deco#相当于myfunc=deco(myfunc),返回的是_deco函数对象和它要引用的myfunc函数对象
    def myfunc():
        print " myfunc() called."
        return "ok"

    myfunc()
    myfunc()
    结果:

    这里需要重点关注@deco这句,它就是myfunc=deco(myfunc),deco(myfunc)返回的是_deco函数对象和它要引用的myfunc函数对象,即闭包;

    也就是说经过@deco这句,myfunc这个函数对象被重新指向了新的函数,这个函数返回的是_deco这个函数和它要调用的原来的myfunc函数;

    敲黑板:此时,myfunc指向新的函数对象(包括_deco函数对象和原来的myfunc函数对象),

    那么后边加个括号myfunc()就是新的函数对象的调用,相当于_deco(原来的myfunc函数对象)吗,它的执行过程就是下边三句话:

     print "before myfunc() called."
     func()#原来的myfunc函数的调用
     print "  after myfunc() called."

    所以执行两次myfunc(),就会把这个过程执行两次,即是结果展现的那样

    装饰器的作用是增加函数的功能,减少重复代码,比如在装饰器函数里加上函数执行时间,

    那么在使用装饰器的函数时都会打印出执行的时间,例如:

    代码:

    #encoding=utf-8

    import time
    def deco(func):
        def _deco():
            time1=time.time()
            func()#函数执行部分
            time2=time.time()
            print "function invoked time elapse:",time2-time1
            #不需要返回func,实际上应返回的是原函数的返回值
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc():
        print " myfunc() called."
        time.sleep(0.1)
        return "ok"

    @deco#等价于myfunc=deco(yourfunc)
    def yourfunc():
        print " your func() called."
        time.sleep(0.1)
        return "ok"

    yourfunc()#此时yourfunc指向新函数,这句等价于deco(yourfunc)(),即新函数的调用(_deco()),在新函数中执行原来的yourfunc()
    print "*"*50
    myfunc()#此时myfunc指向新函数,这句等价于deco(myfunc)(),即新函数的调用(_deco()),在新函数中执行原来的myfunc()

    结果:

    注意注释部分

    在装饰器里加列表的操作

    代码:

    #encoding=utf-8

    import time
    def deco(func):
        def _deco():
            list1=[]
            func(list1)#函数执行部分
            print list1
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc(list1):
        list1.append(1)
        return "ok"

    @deco#等价于myfunc=deco(yourfunc)
    def yourfunc(list1):
        list1.append(2)
        return "ok"

    yourfunc()#等价于deco(yourfunc)()
    print "*"*50
    myfunc()#等价于deco(myfunc)()

    结果:

    装饰器和装饰器之间是独立的,每次使用装饰器时,被装饰的原有函数对象都会被初始化

    第五步:对带参数的函数进行装饰

    如果被装饰的函数有参数,那么装饰器的内嵌装饰函数也要携带对应的参数,并且把结果返回,因为内嵌装饰函数是要运行被装饰函数的,被装饰函数需要参数,那么内嵌装饰函数就要带上需要的参数

    代码:

    #encoding=utf-8

    """对带参数的函数进行装饰,内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象"""
    def deco(func):
        def _deco(a,b):
            print "before myfunc() called."
            ret=func(a,b)
            print "  after myfunc() called. result: %s" %ret
            return ret
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc(a,b):
        print " myfunc(%s,%s) called." %(a,b)
        return a+b

    myfunc(1,2)
    myfunc(3,4)

    结果:

    后两句前边加print:

    print myfunc(1,2)
    print myfunc(3,4)
    结果:

    第六步:对参数数量不确定的函数进行装饰

    代码:

    #encoding=utf-8
    """对参数数量不确定的函数进行装饰,
    参数用(*args,**kwargs),自动适应变化参数和命名参数"""
    def deco(func):
        def _deco(*arg,**kw):
            print "before myfunc() called."
            ret=func(*arg,**kw)
            print "  after myfunc() called.result: %s" %ret
            return ret
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc(a,b,c,d):
        print " myfunc(%s,%s) called." %(a,b)
        return a+b

    print myfunc(1,2,3,4)#等价于deco(myfunc)(1,2,3,4)
    print myfunc(3,4,1,2)#等价于deco(myfunc)(3,4,1,2)
    结果:

     注意:
    print myfunc(1,2,3,4)#等价于deco(myfunc)(1,2,3,4),这里传参数时是传了4个的,只是函数用的是前两个

    把参数调用做个修改:四个都用到

    #encoding=utf-8
    """对参数数量不确定的函数进行装饰,
    参数用(*args,**kwargs),自动适应变化参数和命名参数"""
    def deco(func):
        def _deco(*arg,**kw):
            print "before myfunc() called."
            ret=func(*arg,**kw)
            print "  after myfunc() called.result: %s" %ret
            return ret
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc(a,b,c,d):
        print " myfunc(%s,%s,%s,%s) called." %(a,b,c,d)
        return a+b+c+d

    print myfunc(1,2,3,4)#等价于deco(myfunc)(1,2,3,4)
    print myfunc(3,4,1,2)#等价于deco(myfunc)(3,4,1,2)
    结果:

    可以看到,四个参数都用到了

    参数中有列表和字典:

    代码:#encoding=utf-8

    def deco(func):
        def _deco(*arg,**kw):
            print "before myfunc() called."
            ret=func(*arg,**kw)
            print "  after myfunc() called. result: %s" %ret
            return ret        
        return _deco

    @deco#等价于myfunc=deco(myfunc)
    def myfunc(a,*arg,**kw):
        print " myfunc(%s,%s,%s) called."%(a,(arg if arg else ""),(kw if kw else ""))
        result=a
        for i in arg:
            result +=i
        for k in kw:
            result +=kw[k]
        return result

    print myfunc(1,2,3,x=3)#等价于deco(myfunc)(1,2,3,x=4)
    print myfunc(3,4,1,x=2)#等价于deco(myfunc)(3,4,1,x=2)
    结果:

    修改参数:

    print myfunc(1,2,3)#等价于deco(myfunc)(1,2,3)#不加字典
    print myfunc(3,4,1,x=2)#等价于deco(myfunc)(3,4,1,x=2)

    结果:

    第七步,装饰器带参数

    代码:

    #encoding=utf-8

    """在第四步例子基础上,让装饰器带参数,
    和上一步示例相比在外层多了一层包装"""

    def deco(arg):
        def _deco(func):
            def __deco():
                print "before %s called [%s]."%(func.__name__,arg)
                func()
                print "  after %s called [%s]."%(func.__name__,arg)
            return __deco
        return _deco

    @deco("mymodule")#等价于myfunc=deco("mymodule")(myfunc)
    def myfunc():
        print "myfunc() called."

    @deco("module2")#等价于myfunc=deco("module2")(myfunc)
    def myfunc2():
        print "myfunc2() called."

    myfunc()
    myfunc2()
    结果:

    分析:

    @deco("mymodule")
    def myfunc():
    对于这两行,不管@deco()里面有没有参数,它的意思都是把myfunc函数对象指向一个新函数,这个新函数中调用了原来的myfunc,即myfunc应该指向deco(myfunc),

    只不过这里特殊的地方在于,deco函数本身传了一个参数arg,即"mymodule",然后在它里面的函数_deco()中,把myfunc作为参数传进去了,之后在_deco()里面的__deco()函数中做了调用,并且返回了__deco,之后又把_deco返回,最终myfunc这个函数对象指向的是_deco这个函数对象以及它所需要的参数(闭包);

    拆解一下闭包的层次:

    再贴下代码:
    def deco(arg):
        def _deco(func):
            def __deco():
                print "before %s called [%s]."%(func.__name__,arg)
                func()
                print "  after %s called [%s]."%(func.__name__,arg)
            return __deco
        return _deco

    我们知道闭包是一个函数和这个函数需要的外层的参数,这个两个东西合在一起返回,就是一个闭包;

    那么从最里层来看,__deco()这个函数和它需要的参数func是作为一个闭包;

    再往上一层,_deco()这个函数和它需要的外层参数arg也是一个闭包;

    从这个函数来看,返回了两个函数对象,也可以看出,是有两个闭包存在的。

    那么调用myfunc函数的过程就是这样:

    myfunc函数对象指向的是deco()这个函数把mymodule和myfunc做为参数传进去后返回的一个函数对象(_deco)以及mymodule和myfunc这两个参数

    那么myfunc()就是返回的函数对象的执行,即整个deco函数的执行,执行过程中调用了原来的myfunc函数

    所以结果就是图中那样的

    python内置装饰器

    Python中内置的装饰器有有三个:

    staticmethod:定义实例方法为静态方法

    classmethod:定义实例方法为类方法

    property:对类属性的操作

    装饰器顺序:

    同时对一个函数使用多个不同的装饰器进行装饰时,这个时候装饰器的顺序就很重要了。

    代码示例:

    @A

    @B

    @C

    def f():

        pass

    等价于:

    f = A(B(C(f)))

  • 相关阅读:
    Angular6在自定义指令中使用@HostBingDing() 和@HostListener()
    升级到Angular6后对老版本的RXJS代码做相应的调整
    关于Angular6版本升级和RXJS6新特性的讲解
    ANGULAR 使用 ng build --prod 编译报内存错误的解决办法
    在js内生成PDF文件并下载的功能实现(不调用后端),以及生成pdf时换行的格式不被渲染,word-break:break-all
    在js中获取页面元素的属性值时,弱类型导致的诡异事件踩坑记录,
    前端使用mobx时,变量已经修改了,为什么组件还是没变化,map类型变量,对象类型变量的值获取问题(主要矛盾发生在组件使用时)
    在Java中发送http的post请求,设置请求参数等等
    spring定时任务注解@Scheduled的记录
    js获取dom元素的子元素,父元素,兄弟元素小记
  • 原文地址:https://www.cnblogs.com/xiaxiaoxu/p/9786491.html
Copyright © 2011-2022 走看看