zoukankan      html  css  js  c++  java
  • 装饰器(语法糖)

    一、装饰器

      执行outer函数,将index作为参数传递,

      将outer函数的返回值,重新赋值给index

      装饰器可以在函数执行前和执行后执行其他的附加功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),装饰器的功能非常强大,但是理解起来有些困难,因此我尽量用最简单的例子一步步的说明这个原理。

      写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

    • 封闭:已实现的功能代码块
    • 开放:对扩展开发

    1、不带参数的装饰器:

      假设我定义了一个函数f,想要在不改变原来函数定义的情况下,在函数运行前打印出start,函数运行后打印出end,要实现这样一个功能该怎么实现?看下面如何用一个简单的装饰器来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 使用@语法放在函数的定义上面 相当于执行 f=outer(f),此时f赋值成为了一个新的outer函数,
    # 此时f函数就指向了outer函数的返回值inner,inner是一个函数名,定义在oute函数里面
    # 原来的f是函数名可简单理解为一个变量,作为outer函数的参数传递进去了 此时参数func相当于f
    def outer(func):                    # 定义一个outer函数作为装饰器
        def inner():            # 如果执行inner()函数的话步骤如下:
            print('start')              # 1、首先打印了字符‘start’,
            r=func()                    # 2、执行func函数,func函数相当于def f(): print('中') 
            print('end')                # 3、接着函数打印‘end’
            return r                    # 4、将func函数的结果返回
        return inner
       
    @outer
    def f():              # f=outer(f)=innner
        print('中')
       
    f()                   # f()相当于inner(),执行inner函数的步骤看上面定义处的注释<BR>#打印结果顺序为   start 中 end

    2、包含任意参数的装饰器:

      在实际中,我们的装饰器可能应用到不同的函数中去,这些函数的参数都不一样,那么我们怎么实现一个对任意参数都能实现功能的装饰器?还记得我写函数那篇博客中,就写一种可以接受任意参数的函数,下面来看看如何将其应用到装饰器中去。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #其实只要将上面一种不带参数的装饰器修改一下就可以了
    #修改也很简单,只需将inner和func的参数改为 (*args,**kwargs)
    #其他实现的过程和上面一种一样,就不再介绍了
    def outer(func):
        def inner(*args,**kwargs):
            print('start')
            r=func(*args,**kwargs)    # 这里func(*args,**kwargs)相当于f(a,b)
            print('end')
            return r
        return inner
       
    @outer
    def index(a,b):
        print(a+b)
    m=index(1,4)                    # f(1,4)相当于inner(1,4) 这里打印的结果为 start 5 end <br>print m
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    1、outer函数加载
    2、遇到outer装饰器
    3、执行outer函数,将index函数(被装饰的函数)作为参数传递func=index()
    4、加载inner函数,
    5、将outer函数的返回值inner(这是一个函数),重新赋值给index(也就是说此时的index指向inner函数)
    6、执行inner函数
    7、打印‘start’
    8、执行res = func(a1, a2)(也就是执行被装饰器装饰的函数(index函数))
    9、将index函数的返回值赋值给res
    10、打印‘end’
    11、将inner函数的返回值 res 赋值给index函数
    12、打印m

      

    3、使用两个装饰器:

      当一个装饰器不够用的话,我们就可以用两个装饰器,当然理解起来也就更复杂了,当使用两个装饰器的话,首先将函数与内层装饰器结合然后在与外层装饰器相结合,要理解使用@语法的时候到底执行了什么,是理解装饰器的关键。这里还是用最简单的例子来进行说明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    def outer2(func2):
        def inner2(*args,**kwargs):
            print('开始')
            r=func2(*args,**kwargs)
            print('结束')
            return r
        return inner2
       
    def outer1(func1):
        def inner1(*args,**kwargs):
            print('start')
            r=func1(*args,**kwargs)
            print('end')
            return r
        return inner1
       
    @outer2                                # 这里相当于执行了 f=outer1(f)  f=outer2(f),步骤如下
    @outer1                                #1、f=outer1(f) f被重新赋值为outer1(1)的返回值inner1,
    def f():                               #    此时func1为 f():print('f 函数')
        print('f 函数')                     #2、f=outer2(f) 类似f=outer2(inner1) f被重新赋值为outer2的返回值inner2
                                           #    此时func2 为inner1函数 inner1里面func1函数为原来的 f():print('f 函数')
                                                                                
    f()                                    # 相当于执行 outer2(inner1)() 
    >>开始                                  # 在outer函数里面执行,首先打印 ‘开始 ’
    >>start                                # 执行func2 即执行inner1函数 打印 ‘start’
    >>f 函数                               # 在inner1函数里面执行 func1 即f()函数,打印 ‘f 函数’ 
    >>end                                  # f函数执行完,接着执行inner1函数里面的 print('end')
    >>结束                                 # 最后执行inner2函数里面的 print('结束')

    4、带参数的装饰器:

      前面的装饰器本身没有带参数,如果要写一个带参数的装饰器怎么办,那么我们就需要写一个三层的装饰器,而且前面写的装饰器都不太规范,下面来写一个比较规范带参数的装饰器,下面来看一下代码,大家可以将下面的代码自我运行一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import functools
       
    def log(k=''):                                        #这里参数定义的是一个默认参数,如果没有传入参数,默认为空,可以换成其他类型的参数
        def decorator(func):
            @functools.wraps(func)                        #这一句的功能是使被装饰器装饰的函数的函数名不被改变,
            def wrapper(*args, **kwargs):
                print('start')
                print('{}:{}'.format(k, func.__name__))    #这里使用了装饰器的参数k
                = func(*args, **kwargs)
                print('end')
                return r
            return wrapper
        return decorator
       
    @log()                        # fun1=log()(fun1) 装饰器没有使用参数
    def fun1(a):
        print(a + 10)
       
    fun1(10)
    # print(fun1.__name__)        # 上面装饰器如果没有@functools.wraps(func)一句的话,这里打印出的函数名为wrapper
       
    @log('excute')                # fun2=log('excute')(fun2) 装饰器使用给定参数
    def fun2(a):
        print(a + 20)
    fun2(10)

     双层装饰器:

     一个函数可以被多个装饰器装饰吗? 比如两个装饰器 

     会先将 函数交个最下层@装饰器将处理结果在交给其上一层@装饰器  即先交给w2 再交给w1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def w1(func):
        def inner(*args,**kwargs):
            # 验证1
            # 验证2
            # 验证3
            return func(*args,**kwargs)
        return inner
      
    def w2(func):
        def inner(*args,**kwargs):
            # 验证1
            # 验证2
            # 验证3
            return func(*args,**kwargs)
        return inner
      
      
    @w1
    @w2
    def f1(arg1,arg2,arg3):
        print 'f1'

    双层装饰器原理

     

  • 相关阅读:
    (转载)自己实现spring
    重装mysql步骤
    华为过滤字符串(java)
    华为 去掉最大最小值
    Class.forName()数据库驱动
    线程中Join的使用例子
    基数排序的总结
    javaweb要点复习 jsp和servlet
    Qt实现360安全卫士10.0界面(编译时出现的一些问题)
    VS2010 添加资源文件后,出现 “LNK1123: 转换到 COFF 期间失败: 文件无效或损坏”错误
  • 原文地址:https://www.cnblogs.com/pyxuexi/p/13871051.html
Copyright © 2011-2022 走看看