还是决定写一篇关于python装饰器的文章. 装饰器实在是太常用,也太好用的东西了. 这篇文章会从函数开始, 一步步解释装饰器. 因为装饰器实际是也只是高阶函数而已.
1. 函数
函数就比较基础了, 就像一个黑箱, 传入参数, 出来返回值. 比如这样
>>> def incr(num):
... return num + 1
...
>>> incr(21)
22
2. 函数内部定义函数
python支持在函数内部定义函数, 比如:
def parent():
print("Printing from the parent() function")
def first_child():
print("Printing from the first_child() function")
def second_child():
print("Printing from the second_child() function")
second_child()
first_child()
这个也很好理解, 输出结果是这样的:
>>> parent()
Printing from the parent() function
Printing from the second_child() function
Printing from the first_child() function
在函数内部定义的函数, 作用域只在函数内部. 当然, 我们可以再来个复杂点的例子.
>>> def hello(name):
... def say_hello():
... print("Hello", name)
... say_hello()
...
>>> hello('world')
Hello world
注意, 这里在hello
函数里面定义了一个函数 say_hello
, say_hello
这里面用到一个变量name, 是函数hello传入的参数, 这个在装饰器中会用到. 大家自己理解下.
3. 一切皆对象
python中一切皆是对象, 所有的类, 生成器, 变量, 甚至函数也是一样. 比如我们上面定义的incr
函数:
>>> a = incr
>>> a
<function incr at 0x7f5485f77050>
>>> a(7)
8
>>>
那既然是对象, 就可以作为参数传进函数中, 比如说:
>>> def print_incr(fun, num):
... num = fun(num)
... print(num+10)
>>> print_incr(incr, 10)
21
同样的道理, 函数既然可以作为参数, 也可以作为返回值, 比如:
>>> def left_or_right(direction):
...
... def left():
... print('<-')
...
... def right():
... print('->')
...
... if direction == 'left':
... return left
...
... elif direction == 'right':
... return right
...
>>> left_or_right('left')
<function left at 0x7f5485f73cb0>
>>> fun = left_or_right('left')
>>> fun()
<-
4. 简单的装饰器
装饰器听得很多了, 那到底装饰器是个什么东西? 听起来这么高端? 其实很简单, 我们玩个补全句子的文字游戏, 括号括起来的是定语
- 装饰器是函数.
- 装饰器是 (参数是函数, 返回值也是函数的) 函数.
写装饰器的时候大家请一定牢记这两句
那么我们开始写第一个装饰器了.
def decorator(func):
# 我们在函数内部定义一个函数
def wrapper():
print('start')
# 这里调用一下我们传进来的函数
func()
print('end')
# 返回一个函数
return wrapper
好了, 这就是一个简单的装饰器了. 我们可以这样去使用这个装饰器.
>>> def hello():
... print('hello')
...
>>> say_hello = decorator(hello)
>>> say_hello
<function decorator.<locals>.wrapper at 0x7f5485f4df80>
>>> say_hello()
start
hello
end
>>>
这里的decorator就是装饰器了, 传入一个hello函数, 返回一个新的函数, 所以归根到底就是个函数. 一切皆对象, 所以返回函数和返回数字, 返回类的实例, 都没什么太大区别.
如果你认真阅读前面三个部分, 这里的装饰器应该没有任何问题. 如果还是感觉到困惑, 再去理解一遍前面三点.
5. 语法糖
如果我们做个这样一个装饰器, 每次调用都要调用一下, 这可不太优雅, 于是python提供一种便捷的语法糖方式. 还是上面的这个装饰器举例子
def decorator(func):
# 我们在函数内部定义一个函数
def wrapper():
print('start')
# 这里调用一下我们传进来的函数
func()
print('end')
# 返回一个函数
return wrapper
@decorator
def hello():
print('hello')
这时候我们再调用hello
>>> hello()
start
hello
end
所以, @decorator 的意思就是说, hello = decorator(hello) , 是不是装饰器也没有那么复杂?
6. 带参数的函数的装饰器
动手试一下, 如何写这样一个函数的装饰器?
def add(a, b):
return a+b
如果你对前面的部分理解的比较清楚透彻, 你大概已经实现出来了.
我们理清一下思路, 装饰器就是一个函数, 传入函数, 返回函数, 那就是这样子了
def decorator(fun):
def wrapper(a, b):
print('start')
t = fun(a,b)
print(t)
print('end')
return wrapper
>>> a = decorator(add)
>>> a
<function decorator.<locals>.wrapper at 0x7f5485f4d680>
>>> a(7, 8)的
start
15
end
7. 可带参数的装饰器
我们这里实现一个装饰器功能:
给定两个参数, msg(string) 和 times(int), 对于被装饰的函数, 先输出msg信息, 然后将fun运行times次,
同理, 如果你对于前面理解的透彻, 这部分应该写出来了.
给点提示, 装饰器是什么?
装饰器是函数, 参数是函数, 返回值也是函数.
那么我们的装饰器能不能用别的函数返回回来呢? 当然可以.
# 外面这个函数其实是用来接受参数的外壳
def deco(msg, times):
# 里面的这个函数其实才是我们最终的装饰器
def wrapper(fun):
print(msg)
for i in range(times):
fun()
# 这里返回我们的装饰器
return wrapper
>>> @deco(time=5)
... def hello():
... print('hello world')
...
hello world
hello world
hello world
hello world
hello world
8. 举个例子
计数装饰器: 计算函数运行次数
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper_count_calls(*args, **kwargs):
wrapper_count_calls.num_calls += 1
print(f"Call {wrapper_count_calls.num_calls} of {func.__name__!r}")
return func(*args, **kwargs)
wrapper_count_calls.num_calls = 0
return wrapper_count_calls
@count_calls
def say_whee():
print("Whee!")
9. 写在后面
装饰器的用途实在很多很广, 如果有兴趣进一步了解, 可以去python的装饰器库去看一下:
https://wiki.python.org/moin/PythonDecoratorLibrary
我一开始用装饰器的时候也是一脸懵逼, 但是也坚持去用, 只要能用到的地方, 都尽可能的去使用, 不会的地方就去查别人的代码, 然后模仿着去写, 慢慢会用了之后再回头咀嚼原理直到吃透. 所以, 最重要的是多动手, 写就完了.
写了蛮久的, 如果对你有帮助, 顺手点个赞(≧▽≦)/. 大佬们赞赏就更欢迎啦. 还在学习, 如果有哪里不对的请多指教.