https://www.programiz.com/python-programming/decorator
Python 中所有的东西都是对象(objects),函数也是 objects (带有属性的 object),如:
def first(msg):
print(msg)
first("Hello")
second = first
second("Hello")
他们得到的输出是相同的,这里 first
和 second
指的是同一个函数对象。
函数可以作为参数传递给另一个函数。以其他函数作为参数的函数也叫做高阶函数(higher order functions),比如:
def inc(x):
return x + 1
def dec(x):
return x - 1
def operate(func, x):
result = func(x)
return result
函数也可以返回另一个函数:
def is_called():
def is_returned():
print("Hello")
return is_returned
new = is_called()
new() # output: Hello
假如函数和方法可以被调用(call),则称其为可调用的(callable)。实际上对于任意一个对象来讲,假如它实现了 __call__()
方法,那么它就是可以被调用的。所以在最基础的情况下,一个装饰器是一个返回可调用对象的可调用对象(a decorator is a callable that returns a callable)
基础上讲,装饰器接受一个函数作为输入,给这个函数加一些功能,然后再返回它:
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
其中 make_pretty
是一个装饰器,在赋值的过程中:
pretty = make_pretty(ordinary)
这一句中,函数 ordinary
被装饰后返回,赋给了 pretty
。装饰器的作用像一个 wrapper
,一般来讲,装饰后的函数会赋给它自己:
ordinary = make_pretty(ordinary)
这一做法很常见,因此 Python 提供了一个语法来进行简化:
@make_pretty
def ordinary():
print("I am ordinary")
这一写法等价于:
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
如果想在装饰过程中加入参数,该怎么做呢?比如:
def divide(a, b):
return a/b
这个函数有两个参数:a,b
,设计一个装饰器来避免除以零的错误:
def smart_divide(func):
def inner(a,b):
print("I am going to divide", a, "and", b)
if b==0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a,b):
return a/b
此时假如除以零的话会返回 None
:
可以注意到 inner
接受的参数和要装饰的函数是一样的,因此可以设计更加一般的装饰器:
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwarg)
return inner
其中 function(*args, **kwargs)
里的 args
为位置参数(positional arguments),kwargs
表示一个关键词参数的字典(the dictionary of keyword arguments)
可以将多个装饰器级联起来:
def star(func):
def inner(*args, **kwargs):
print("*" * 30)
func(*args, **kwargs)
print("*" * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 30)
func(*args, **kwargs)
print("%" * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer("Hello")
"""output:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
"""
上述操作等价于:
def printer(msg):
print(msg)
printer = star(percent(printer))
装饰的顺序对结果是有影响的,假如这样装饰:
@percent
@star
def printer(msg):
print(msg)
结果为:
"""
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
"""