• Python装饰器


    今天我们来聊一聊装饰器,什么是装饰器呢?

    举个例子:早上你一丝不挂的起来,想今天要穿什么衣服来装扮一下自己。这里衣服就相当于装饰器,而你赤裸裸的身体就是要被装饰的函数或者是类。

    函数装饰器

    Python装饰器是非常不错的特性,熟练掌握装饰器会让你的编程思路更加宽广,程序也更加pythonic。下面就让我们一起来探讨一下python的装饰器吧。

    装饰器的存在是为了适用两个场景,一个是增强被装饰函数的行为,另一个是代码重用。

    先看一个例子,直观的感受一下:

     1     import time
     2 
     3     def out_wrapper(func):
     4 
     5         def inner_wrapper():
     6 
     7             start_time = time.time()
     8 
     9             func()
    10 
    11             stop_time = time.time()
    12 
    13             print('Used time {}'.format(stop_time-start_time))
    14 
    15         return inner_wrapper
    16 
    17     @out_wrapper
    18 
    19     def test1():
    20 
    21         time.sleep(1)
    22 
    23         print('I am test1!')

    输出:

    1 I am test1!
    2 Used time 1.0000572204589844

    这个装饰器是用来计算函数执行时间的。原本test1函数只是休眠1秒,然后输出字符串,但是在使用装饰器(out_wrapper)后,它的功能多了一项:输出执行时间。 这是一个最简单的装饰器,实现了 “增强被装饰函数的行为”。而我们需要思考的是为什么装饰器是这个样子的? 那是因为行为良好的装饰器必须要遵守两个原则:

    1、不能修改被装饰函数的代码;

    2、不能修改被装饰函数的调用方式;

    这并不难以理解,因为在生产环境中如果我们要给某个函数添加功能,最好不要修改该函数的源码,因为可能造成意想不到的影响,或者这个代码是一个大神写的,你根本不知从何改起;同时你也不能修改其调用方式,因为你不知道程序中有多少地方调用了此函数。

    那么我们从函数和函数名说起吧。

    1     def func(name):
    2 
    3         print('I am {}!'.format(name))
    4 
    5     func('li')
    6 
    7     y = func
    8 
    9     y('liu')

    输出:

    1 I am li!
    2 I am liu!

    定义函数func,调用函数func,将函数名func赋值给y,调用y。y=func 表明:函数名可以赋值给变量,并且并不影响调用。

    其实和整数、数字是一样的:

    1 a = 1
    2 b = a
    3 print(a, b) 
    

    明白了这一点,下面再说说高阶函数: 高阶函数满足如下两个条件中的任意一个: a. 可以接收函数名作为实参; b. b.返回值中可以包含函数名;

    其实python标准库中的map和filter等函数就是高阶函数。

    1     l = [1, 2, 4]
    2 
    3     r = map(lambda x: x*3, l)
    4 
    5     for i in r:
    6 
    7         print(i)

    自定义一个能返回函数的函数,也是高阶函数

    1     def f(l):
    2 
    3         return map(lambda x: x*5, l)
    4 
    5     a = f(l)
    6 
    7     for i in a:
    8 
    9         print(i)

    有了这些基础,我们就可以尝试实现一下类似装饰器的功能了。

     1     def out(func):
     2 
     3         print('Add a function.')
     4 
     5         return func
     6 
     7     def test1():
     8 
     9         time.sleep(1)
    10 
    11         print('I am test1!')
    12 
    13     temp = out(test1)
    14 
    15     temp()

    输出:

    1     Add a function.
    2     I am test1!

    还是第一个例子中的test1函数,我们定义了一个函数out,out接收一个函数名然后直接返回该函数名。这样,我们实现了不修改原函数test1,并且添加了一个新功能的需求,但是缺陷就是调用方式改变了。如何解决这个问题呢?其实很简单,相信 a = a * 3 这样的表达式我们都见过,那么上述代码中的temp = out(test1) 同样可以修改为 test1 = out(test1),这样我们就完美的解决了问题:既添加了新功能又没有修改原函数和其调用方式。修改后的代码如下:

     1     def out(func):
     2 
     3         print('Add a function.')
     4 
     5         return func
     6 
     7     def test1():
     8 
     9         time.sleep(1)
    10 
    11         print('I am test1!')
    12 
    13     test1 = out(test1)
    14 
    15     test1()

    只是美中不足的事每次需要使用装饰器的时候,都要在写一句类似test1 = out(test1) 的代码。python为了简化这种情况,提供了一个语法糖@,在每个被装饰的函数上方使用这个语法糖就可以省掉这一句代码test1 = out(test1)。如下:

     1     def out(func):
     2 
     3         print('Add a function.')
     4 
     5         return func
     6 
     7     @out
     8 
     9     def test1():
    10 
    11         time.sleep(1)
    12 
    13         print('I am test1!')
    14 
    15     # test1 = out(test1)
    16 
    17     test1()

    至此,我们搞清楚了装饰器的工作原理,但是对比开篇的例子,还是有些不一样。这又是为什么呢? 开篇例子实现的是输出被装饰函数的执行时间,那么必须在函数执行之前记录一下时间,函数执行之后记录一下时间,这样才能计算出函数的执行时间,但是我们现在是直接返回了函数名,这样函数调用后我们就没办法做任何事情了,所以此时我们需要在嵌套一层函数,将实现额外功能的部分写在内层函数中,然后将这个内层函数返回即可。这也是为什么装饰器都是嵌套函数的原因。 另外,开篇的例子并没有返回值,也没有参数,要对既有参数又有返回值的函数进行装饰的话,还需要进一步完善。 能够处理返回值的装饰器:

     1     import time
     2 
     3     def out_wrapper(func):
     4 
     5         def inner_wrapper():
     6 
     7             start_time = time.time()
     8 
     9             result = func()
    10 
    11             stop_time = time.time()
    12 
    13             print('Used time {}'.format(stop_time - start_time))
    14 
    15             return result
    16 
    17         return inner_wrapper
    18 
    19     @out_wrapper
    20 
    21     def test1():
    22 
    23         time.sleep(1)
    24 
    25         print('I am {test1}!')
    26 
    27         return 'test1 return'
    28 
    29     x = test1()
    30 
    31     print(x)

    输出:

    1 I am {test1}!
    2 Used time 1.0000572204589844
    3 test1 return    

    能够处理参数的装饰器:

     1     def out_wrapper(func):
     2 
     3         def inner_wrapper(*args, **kwargs):
     4 
     5             start_time = time.time()
     6 
     7             result = func(*args, **kwargs)
     8 
     9             stop_time = time.time()
    10 
    11             print('Used time {}'.format(stop_time - start_time))
    12 
    13             return result
    14 
    15         return inner_wrapper
    16 
    17     @out_wrapper
    18 
    19     def test1(args):
    20 
    21         time.sleep(1)
    22 
    23         print('I am {}!'.format(args))
    24 
    25         return 'test1 return'
    26 
    27     x = test1('li')
    28 
    29     y = test1('liu')
    30 
    31     print(x, y)

    输出:

    1     I am li!
    2     Used time 1.0000569820404053
    3     I am liu!
    4     Used time 1.0000572204589844
    5     test1 return test1 return

    总结:装饰器的本质是函数,其参数是另一个函数(被装饰的函数)。 装饰器通常会额外处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。行为良好的装饰器可以重用,以减少代码量。

  • 相关阅读:
    git线上操作
    IDEA快捷方式
    Java 四种线程池
    java 获取当前天之后或之前7天日期
    如何理解AWS 网络,如何创建一个多层安全网络架构
    申请 Let's Encrypt 通配符 HTTPS 证书
    GCE 部署 ELK 7.1可视化分析 nginx
    使用 bash 脚本把 AWS EC2 数据备份到 S3
    使用 bash 脚本把 GCE 的数据备份到 GCS
    nginx 配置 https 并强制跳转(lnmp一键安装包)
  • 原文地址:https://www.cnblogs.com/songtao1600/p/9048245.html
Copyright © 2020-2023  润新知