Python 中文社区
全球 Python 中文开发者的
精神部落
当前浏览器不支持播放音乐或语音, 请在微信或其他浏览器中播放 Don't Let Me Down The Chainsmokers - Don't Let Me Down
Python 装饰器是非常不错的特性, 熟练掌握装饰器会让你的编程思路更加宽广, 程序也更加 pythonic. 下面就让我们一起来探讨一下 python 的装饰器吧.
装饰器的存在是为了适用两个场景, 一个是增强被装饰函数的行为, 另一个是代码重用.
先看一个例子, 直观的感受一下:
- import time
- def out_wrapper(func):
- def inner_wrapper():
- start_time = time.time()
- func()
- stop_time = time.time()
- print('Used time {}'.format(stop_time-start_time))
- return inner_wrapper
- @out_wrapper
- def test1():
- time.sleep(1)
- print('I am test1!')
输出:
- I am test1!
- Used time 1.0000572204589844
这个装饰器是用来计算函数执行时间的. 原本 test1 函数只是休眠 1 秒, 然后输出字符串, 但是在使用装饰器 (out_wrapper) 后, 它的功能多了一项: 输出执行时间. 这是一个最简单的装饰器, 实现了 "增强被装饰函数的行为". 而我们需要思考的是为什么装饰器是这个样子的? 那是因为行为良好的装饰器必须要遵守两个原则:
1, 不能修改被装饰函数的代码;
2, 不能修改被装饰函数的调用方式;
这并不难以理解, 因为在生产环境中如果我们要给某个函数添加功能, 最好不要修改该函数的源码, 因为可能造成意想不到的影响, 或者这个代码是一个大神写的, 你根本不知从何改起; 同时你也不能修改其调用方式, 因为你不知道程序中有多少地方调用了此函数.
那么我们从函数和函数名说起吧.
- def func(name):
- print('I am {}!'.format(name))
- func('li')
- y = func
- y('liu')
输出:
- I am li!
- I am liu!
定义函数 func, 调用函数 func, 将函数名 func 赋值给 y, 调用 y.y=func 表明: 函数名可以赋值给变量, 并且并不影响调用.
这其实和整数, 数字是一样的:
- a = 1
- b = a
- print(a, b)
明白了这一点, 下面再说说高阶函数: 高阶函数满足如下两个条件中的任意一个: a. 可以接收函数名作为实参; b. b. 返回值中可以包含函数名;
其实 python 标准库中的 map 和 filter 等函数就是高阶函数.
- l = [1, 2, 4]
- r = map(lambda x: x*3, l)
- for i in r:
- print(i)
自定义一个能返回函数的函数, 也是高阶函数
- def f(l):
- return map(lambda x: x*5, l)
- a = f(l)
- for i in a:
- print(i)
有了这些基础, 我们就可以尝试实现一下类似装饰器的功能了.
- def out(func):
- print('Add a function.')
- return func
- def test1():
- time.sleep(1)
- print('I am test1!')
- temp = out(test1)
- temp()
输出:
Add a function.
I am test1!
还是第一个例子中的 test1 函数, 我们定义了一个函数 out,out 接收一个函数名然后直接返回该函数名. 这样, 我们实现了不修改原函数 test1, 并且添加了一个新功能的需求, 但是缺陷就是调用方式改变了. 如何解决这个问题呢? 其实很简单, 相信 a = a * 3 这样的表达式我们都见过, 那么上述代码中的 temp = out(test1) 同样可以修改为 test1 = out(test1), 这样我们就完美的解决了问题: 既添加了新功能又没有修改原函数和其调用方式. 修改后的代码如下:
- def
- out(func):
- print('Add a function.'
- )
- return func
- def
- test1():
- time.sleep(
- 1)
- print('I am test1!'
- )
- test1 =
- out(test1)
- test1()
只是美中不足的事每次需要使用装饰器的时候, 都要在写一句类似 test1 = out(test1) 的代码. python 为了简化这种情况, 提供了一个语法糖 @, 在每个被装饰的函数上方使用这个语法糖就可以省掉这一句代码 test1 = out(test1). 如下:
- def out(func):
- print('Add a function.')
- return func
- @out
- def test1():
- time.sleep(1)
- print('I am test1!')
- # test1 = out(test1)
- test1()
至此, 我们搞清楚了装饰器的工作原理, 但是对比开篇的例子, 还是有些不一样. 这又是为什么呢? 开篇例子实现的是输出被装饰函数的执行时间, 那么必须在函数执行之前记录一下时间, 函数执行之后记录一下时间, 这样才能计算出函数的执行时间, 但是我们现在是直接返回了函数名, 这样函数调用后我们就没办法做任何事情了, 所以此时我们需要在嵌套一层函数, 将实现额外功能的部分写在内层函数中, 然后将这个内层函数返回即可. 这也是为什么装饰器都是嵌套函数的原因. 另外, 开篇的例子并没有返回值, 也没有参数, 要对既有参数又有返回值的函数进行装饰的话, 还需要进一步完善. 能够处理返回值的装饰器:
- import
- time
- def
- out_wrapper(func):
- def inner_wrapper():
- start_time = time.time()
- result = func()
- stop_time = time.time()
- print('Used time {}'
- .format(stop_time - start_time))
- return result
- return inner_wrapper
- @out_wrapper
- def
- test1():
- time.sleep(
- 1)
- print('I am {test1}!'
- )
- return 'test1 return'
- x = test1()
- (x)
输出:
- I am {test1}!
- Used time 1.0000572204589844
- test1 return
能够处理参数的装饰器:
- def out_wrapper(func):
- def inner_wrapper(*args, **kwargs):
- start_time = time.time()
- result = func(*args, **kwargs)
- stop_time = time.time()
- print('Used time {}'.format(stop_time - start_time))
- return result
- return inner_wrapper
- @out_wrapper
- def test1(args):
- time.sleep(1)
- print('I am {}!'.format(args))
- return 'test1 return'
- x = test1('li')
- y = test1('liu')
- print(x, y)
输出:
- I am li!
- Used time 1.0000569820404053
- I am liu!
- Used time 1.0000572204589844
- test1 return test1 return
总结: 装饰器的本质是函数, 其参数是另一个函数(被装饰的函数). 装饰器通常会额外处理被装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象. 行为良好的装饰器可以重用, 以减少代码量.
来源: https://juejin.im/entry/5afd5ae26fb9a07ab458d832