装饰器是接收另一个函数作为实参的函数,其目的是扩展或替换传入函数的行为。
如果函数A是装饰器,函数B是它的实参,那么函数A会修改、扩展或替换函数B的行为,而不修改_函数B的代码。
我们说装饰器函数A包装_了函数B。
虽然我们说的是“修改”行为,但被包装的函数其实_并未被改变。行为要么是添加在_被包装函数周围(以及它返回的内容),要么是被包装函数的行为被_替换_成另一种行为。
一个高阶函数是接收一个或多个函数作为实参、和/或返回一个或多个函数的函数。 一个函数,如果被用作实参或由另一个函数返回,就是一个一等函数。 Python 函数作为一种可调用对象,就是一等函数,可以存入变量或用作实参,就像其他任何值或对象一样。 高阶函数和一等函数共同作用,才让装饰器成为可能。
@符号加在装饰器函数名字的前面,放在要被装饰的函数的正上方,像这样:
@decorator
def decorated_function():
pass
有些装饰器接受实参:
@decorator_with_arg(name="Bob")
def decorated_function2():
pass
如果装饰器定义了默认实参,就必须在@decorator()调用中使用括号,装饰器才能正常工作,就像调用任何函数时一样:
@decorator_with_default_arg()
def decorated_function3():
pass
如果装饰器接收_位置_实参,不提供这些实参就会导致错误,错误信息大致如下:
TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'
@decorator语法是语法糖,也可以说是调用_装饰函数_、并把_被装饰的函数_作为实参传给它的简写形式。
下面是一些调用装饰器的其他写法示例:
def function():
pass
function = decorator(function)
def function2():
pass
function2 = decorator_with_arg(name="Bob")(function2)
def function3():
pass
function3 = decorator_with_default_arg()(function3)
大多数装饰器旨在_扩展_或_替换_另一个函数的行为,但有些装饰器可能什么都不做,只是返回它们所包装的函数。
装饰器是至少接收一个实参的函数,即它们所包装的那个函数。 它们通常返回被包装的函数,或者返回一个使用了该被包装函数的表达式的结果。
一个简单的装饰器(只是返回它包装的函数)可以这样写:
>>> def do_nothing(func):
... return func
...
... @do_nothing
... def function4():
... return 4
...
>>> print(function4())
4
装饰器也可以只添加副作用,比如用于日志记录的额外信息:
>>> def my_logger(func):
... print(f"Entering {func.__name__}")
... return func
...
... @my_logger
... def my_func():
... print("Hello")
...
>>> my_func()
Entering my_func
Hello
装饰器不返回它自己。 它可能返回它的函数实参、另一个函数,或者一个或多个用来替换传入函数或被装饰函数返回值结果的值。 如果装饰器返回另一个函数,通常返回的是一个内部函数。
函数可以定义在另一个函数里面。 这种嵌套的函数叫做内部函数。 装饰器可以用内部函数来包装它的函数实参。 然后装饰器返回这个内部函数。 内部函数接着可能返回最初传入的那个函数实参。
下面是一个用装饰器做校验的例子:
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
@my_validator
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!
第一行定义的是装饰器的func实参。
下一行定义的是装饰器的_内部函数_,它包装了func实参。
由于_内部函数_包装了装饰器的func实参,传给func的实参会同样传给它。
注意,包装函数不必为实参使用与func中定义的相同的名字。
原函数用的是planet,装饰器用的是world,装饰器仍然能正常工作。
内部函数要么返回func,要么在world == "Pluto"时打印一条信息,说明冥王星不是行星。
也可以把它改写成抛出一个ValueError。
所以,内部函数_包装了func,要么返回func,要么做某件事来替代func原本要做的事。
装饰器返回它的_内部函数。
_内部函数_可能返回也可能不返回最初传入的那个函数。
取决于包装函数或_内部函数_中哪段代码被条件执行,可能返回func,可能抛出错误,也可能返回一个属于func返回类型的值。
利用*args和**kwargs语法,可以为接收任意数量实参的函数编写装饰器。
下面是一个用于接收任意数量实参的函数的装饰器示例:
def double(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@double
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 18
print(add(2, 3, 4, 5, 6))
#-> 40
这能把函数实参的返回值翻倍。 如果我们想把返回值变成三倍、四倍等,可以给装饰器本身加一个形参,下一节会演示。
下面是一个可以配置的装饰器示例,它能把被装饰函数的返回值乘以任意倍数:
def multi(factor=1):
if factor == 0:
raise ValueError("factor must not be 0")
def outer_wrapper(func):
def inner_wrapper(*args, **kwargs):
return func(*args, **kwargs) * factor
return inner_wrapper
return outer_wrapper
@multi(factor=3)
def add(*args):
return sum(args)
print(add(2, 3, 4))
#-> 27
print(add(2, 3, 4, 5, 6))
#-> 60
开头几行校验factor不为0。
接着定义外层包装函数。
它的签名与不带参数的装饰器所期望的签名相同。
外层包装函数里有一个内部函数,其签名与原函数相同。
内部包装函数负责把原函数的返回值乘以传给装饰器的实参。
外层包装函数返回内部包装函数,装饰器则返回外层包装函数。
下面是一个带参数的装饰器示例,它控制是否校验传给原函数的实参:
def check_for_pluto(check=True):
def my_validator(func):
def my_wrapper(world):
print(f"Entering {func.__name__} with {world} argument")
if check and world == "Pluto":
print("Pluto is not a planet!")
else:
return func(world)
return my_wrapper
return my_validator
@check_for_pluto(check=False)
def my_func(planet):
print(f"Hello, {planet}!")
my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!
my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!
这样可以轻松切换是否检查Pluto,而且无需修改my_func。