装饰器的目的是为了扩展函数的功能,而不是修改函数本身。 它是一个非常强大的工具,它允许为现有的函数添加新的功能。
本文主要分享有两种装饰器:
- 函数装饰器
- 类装饰器
一个函数可以被修饰为一个装饰器,使用 @
号。
示列代码:
@my_decorator
def my_function():
pass
看完上面的示例代码,我们再来看看这个函数是如何被修饰的。
函数装饰器
为了理解装饰器模式,我们需要先强调下Python中一切都皆为对象。
函数也是一种对象,它可以被赋值给变量,还可以在另一个函数内定义,作为另一个函数的参数,或者从另一个函数返回。
装饰器是一个接受另一个函数作为参数的函数,它包装了函数的行为,并返回包装后的函数。所以装饰器的目的是为了扩展函数的功能,而不是修改函数本身。
练习下面代码:
def start_end_decorator(func):
def wrapper():
print("Start")
func()
print("End")
return wrapper
def print_name():
print("Alex")
print_name()
print()
# 使用装饰器得到一个新的函数
print_name_new = start_end_decorator(print_name)
print_name_new()
结果:
Alex
Start
Alex
End
解释:
上面的代码中定义了两个函数,其中print_name()
函数是一个普通函数,它的功能是打印Alex。
我们重点来看start_end_decorator()
函数:
- 它的参数
func
是一个函数。 - 它在内部定义了一个函数
wrapper(
)作为返回值。当然wrapper
就是一个名字而已,可以随意😊。 wrapper()
首先打印出"Start",然后调用func()
,最后打印"End"。
总起来看,start_end_decorator()
函数的是扩展了print_name()
函数的功能:
- 在其运行之前打印Start,
- 运行结束之后打印End。
所以我们可以用start_end_decorator()
函数来修饰print_name()
函数,得到一个新的函数print_name_new
。
思考题,请写一个装饰器函数,来打印函数的执行时长。
装饰器的语法
上面的函数我们必须重新定义一个函数,这比较麻烦,所以Python提供了一种语法来定义装饰器。 它可以将修饰后函数赋值给自己,我们可以通过@
修饰我们的函数来实现相同的功能。
请练习下面代码:
def start_end_decorator(func):
def wrapper():
print("Start")
func()
print("End")
return wrapper
@start_end_decorator
def print_name():
print('Alex')
print_name()
结果:
Start
Alex
End
解释:
首先我们定义了一个装饰器start_end_decorator()
。 然后在定义新的函数print_name()
时,我们使用了@
装饰器。 从而print_name()
函数具备了新的功能!
是不是
@
非常简单,这也是真实工作中最常用的!!!
修饰带参数的函数
如果我们的函数有输入参数,上面的装饰器就无法实现,因为Python不允许装饰器接受参数。
那我们该怎么办呢?
我们可以在内部函数中使用 *args
和 **kwargs
来实现。
请尝试下面代码:
def start_end_decorator_2(func):
def wrapper(*args, **kwargs):
print('Start')
func(*args, **kwargs)
print('End')
return wrapper
@start_end_decorator_2
def add_5(x):
return x + 5
result = add_5(10)
print(result)
查看结果:
Start
End
None
解释:
跟上面装饰器不同的是,内部定义函数时使用了*args
和**kwargs
。 从而这个装饰器可以修饰需要输入参数的函数。 但是我们会发现输出结果并不是我们想要的,因为我们没有指定输出结果。
下面我们继续深入,来解决这个问题。
装饰器返回值
请练习下面代码:
def start_end_decorator_3(func):
def wrapper(*args, **kwargs):
print('Start')
result = func(*args, **kwargs)
print('End')
return result
return wrapper
@start_end_decorator_3
def add_5(x):
return x + 5
result = add_5(10)
print(result)
结果:
Start
End
15
函数的身份问题
如果我们查看我们修饰后函数的名字,或者使用内置的help
函数,我们会发现,它的名字是wrapper
。
Python认为现在的函数是装饰器函数的内部函数。
代码:
def start_end_decorator_3(func):
def wrapper(*args, **kwargs):
print('Start')
result = func(*args, **kwargs)
print('End')
return result
return wrapper
@start_end_decorator_3
def add_5(x):
return x + 5
print(add_5.__name__)
help(add_5)
结果:
wrapper
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
为了解决这个问题,我们可以使用functools.wraps
装饰器,它将保留原函数的信息。 这可以帮助我们在运行时获取对象的信息,比如函数的名字,参数等。
修正后的代码:
import functools
def start_end_decorator_4(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('Start')
result = func(*args, **kwargs)
print('End')
return result
return wrapper
@start_end_decorator_4
def add_5(x):
return x + 5
result = add_5(10)
print(result)
print(add_5.__name__)
help(add_5)
结果:
Start
End
15
add_5
Help on function add_5 in module __main__:
add_5(x)
最终的自定义装饰器模板
现在我们已经拥有了所有的部件,我们的自定义装饰器模板如下(🍓):
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 定义额外功能
result = func(*args, **kwargs)
# 定义额外功能。
return result
return wrapper
装饰器参数
为了更好地理解装饰器参数的必要性, 我们看看另一个例子:一个repeat
装饰器,它接受一个数字作为输入。
这个装饰器的功能是:重复执行输入函数给定的次数。
你理解了么?装饰器的装饰器...
练习下面的代码:
import functools
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("Alex")
结果:
Hello Alex
Hello Alex
Hello Alex
嵌套装饰器
我们可以通过堆叠的方式将多个装饰器应用到一个函数上。 这些装饰器按照列出的顺序中执行。
代码:
import functools
# 一个装饰器,在执行前后打印日志
def start_end_decorator_4(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('Start')
result = func(*args, **kwargs)
print('End')
return result
return wrapper
# 一个装饰器函数,打印函数的调用信息
def debug(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
print(f"Calling {func.__name__}({signature})")
result = func(*args, **kwargs)
print(f"{func.__name__!r} returned {result!r}")
return result
return wrapper
@debug
@start_end_decorator_4
def say_hello(name):
greeting = f'Hello {name}'
print(greeting)
return greeting
say_hello(name='Alex')
结果:
Calling say_hello(name='Alex')
Start
Hello Alex
End
'say_hello' returned 'Hello Alex'
你可以尝试改变装饰器的顺序,看看结果会发生什么。
类装饰器
我们也可以使用类作为装饰器。 因此,我们必须实现__call__()
方法,使我们的对象可调用。
类装饰器通常用于维护状态。例如,我们可以记录函数被调用的次数。
__call__
本质上和wrapper()
方法是一样的。 它添加了一些功能,执行函数,并返回其结果。
注意,这里我们使用
functools.update_wrapper()
而不是functools.wraps
来保留我们的函数的信息。
代码:
import functools
class CountCalls:
# 这里我们使用了`functools.update_wrapper()`来保留我们的函数的信息。
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
# 额外功能,执行函数,并返回其结果
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"{self.func.__name__!r}被执行了{self.num_calls}次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(num):
print("Hello!")
say_hello(5)
say_hello(5)
结果:
'say_hello'被执行了1次
Hello!
'say_hello'被执行了2次
Hello!
一些常见使用场景
- 用于计算函数的执行时间
- 用于打印函数的调用信息和参数
- 用于检查参数是否符合某些要求,并且根据结果进行不同的行为
- 注册函数(插件)
- 使用
time.sleep()
函数来检查网络行为 - 用于缓存返回值的装饰器
- 添加或更新状态
小节
装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)
大多数初学者不知道在哪儿使用它们,希望上面的分享可以帮助到你!
感谢你的阅读。欢迎大家点赞、收藏、支持!
pythontip 出品,Happy Coding!
公众号: 夸克编程
我们的小目标: 让天下木有难学的Python