zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    dinic模板
    匈牙利算法(codevs2776)
    线段树(codevs1082)
    KM模板
    kmp模板,线性完成pos
    (一)Python入门-2编程基本概念:03引用的本质-栈内存和堆内存-内存示意图
    (一)Python入门-2编程基本概念:04标识符-帮助系统简单实用-命名规则
    (一)Python入门-2编程基本概念:05变量的声明-初始化-删除变量-垃圾回收机制
    (一)Python入门-2编程基本概念:06链式赋值-系列解包赋值-常量
    (一)Python入门:05Python程序格式-缩进-行注释-段注释
  • 原文地址:https://www.cnblogs.com/songtao1600/p/9048245.html
Copyright © 2011-2022 走看看