装饰器
前面学了函数和 lambda,接下来瞅瞅 Python 中一个稍微高级但非常有用的特性——装饰器。它能帮我们在不修改原函数代码的前提下,给函数动态扩展功能。
装饰器不仅可以用于函数,也可以用于类。不过类装饰器、以及
@staticmethod、@classmethod、@property等内置装饰器,都需要先理解「类」的概念。这些内容会在下一篇《类》中再补充,本篇先聚焦函数装饰器。
1. 什么是装饰器
装饰器(decorator)是 Python 中的一种高级功能。
简单来说,装饰器本质上就是一个函数,它接收一个函数作为参数,并返回一个新的函数。返回的新函数通常会对原函数做一些增强。
比如你想给很多函数都加上「调用前打印日志」的功能,如果每个函数都手动改,会很麻烦。用装饰器就可以统一实现,原函数该怎么写还怎么写。
常见应用场景:
- 日志记录:记录函数调用信息、参数和返回值。
- 性能统计:统计函数执行时间。
- 权限控制:限制某些函数只能被特定用户调用。
- 缓存:缓存函数结果,避免重复计算。
2. 基本语法
装饰器的核心思想是:用一个函数「包装」另一个函数。
def decorator_function(original_function):
def wrapper(*args, **kwargs):
# 调用前可以做一些事情
print('执行前')
result = original_function(*args, **kwargs)
# 调用后也可以做一些事情
print('执行后')
return result
return wrapperdecorator_function:装饰器函数,接收原函数作为参数。wrapper:包装函数,真正被执行的其实是它。return wrapper:装饰器返回包装函数。
使用 @ 语法糖,可以这样写:
@decorator_function
def target_function():
print('原函数执行')上面的 @decorator_function 等价于下面这行代码:
target_function = decorator_function(target_function)所以当你调用 target_function() 时,实际执行的是 wrapper()。
3. 第一个装饰器:打印日志
下面是一个最简单的装饰器示例,在函数执行前后打印信息。
def my_decorator(func):
def wrapper():
print('函数执行前')
func()
print('函数执行后')
return wrapper
@my_decorator
def say_hello():
print('Hello!')
# 输出: 函数执行前
# 输出: Hello!
# 输出: 函数执行后
say_hello()解析:
my_decorator接收say_hello这个函数。@my_decorator等价于say_hello = my_decorator(say_hello)。- 调用
say_hello()时,实际执行的是wrapper()。
4. 带参数的装饰器
如果被装饰的函数本身有参数,wrapper 也要能接收参数。通常用 *args, **kwargs 来兼容任意参数。
def my_decorator(func):
def wrapper(*args, **kwargs):
print('执行前')
func(*args, **kwargs)
print('执行后')
return wrapper
@my_decorator
def greet(name):
print(f'Hello, {name}!')
# 输出: 执行前
# 输出: Hello, Alice!
# 输出: 执行后
greet('Alice')
*args接收多余的位置参数,**kwargs接收多余的关键字参数。这样写可以让装饰器通用于任意函数。
5. 装饰器工厂(带参数的装饰器进阶)
有时候我们想给装饰器本身也传参数,比如控制重复执行的次数。这时需要再包一层函数,形成「装饰器工厂」。
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print('Hello!')
# 输出: Hello!
# 输出: Hello!
# 输出: Hello!
say_hello()执行过程:
repeat(3)返回一个装饰器函数decorator。@repeat(3)等价于say_hello = repeat(3)(say_hello)。- 最终
say_hello被替换成wrapper,调用时会循环执行 3 次原函数。
6. 多个装饰器堆叠
一个函数可以同时使用多个装饰器。定义时从下到上依次包裹,调用时从上到下依次执行。
def decorator1(func):
def wrapper():
print('Decorator 1')
func()
return wrapper
def decorator2(func):
def wrapper():
print('Decorator 2')
func()
return wrapper
@decorator1
@decorator2
def say_hello():
print('Hello!')
# 输出: Decorator 1
# 输出: Decorator 2
# 输出: Hello!
say_hello()解析:
- 定义时,
@decorator2先包裹say_hello,然后@decorator1再包裹一次。 - 等价于
say_hello = decorator1(decorator2(say_hello))。 - 调用时,先进入最外层的
decorator1的wrapper,再进入decorator2的wrapper,最后执行原函数。
7. 装饰器练习
最后来几个实际应用示例,巩固一下。
7.1 计算函数执行时间
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__} 执行耗时: {end - start:.4f} 秒')
return result
return wrapper
@timer
def slow_function():
time.sleep(0.5)
print('函数执行完毕')
# 输出: 函数执行完毕
# 输出: slow_function 执行耗时: 0.50xx 秒
slow_function()7.2 登录权限校验
def login_required(func):
def wrapper(user, *args, **kwargs):
if not user.get('is_login'):
print('请先登录')
return
return func(user, *args, **kwargs)
return wrapper
@login_required
def view_profile(user):
print(f'欢迎回来,{user["name"]}')
# 输出: 请先登录
view_profile({'name': 'juzicoding', 'is_login': False})
print('--------------华丽的分割线--------------')
# 输出: 欢迎回来,juzicoding
view_profile({'name': 'juzicoding', 'is_login': True})装饰器 = 函数包装函数 + 不修改原代码扩展功能。掌握
@语法、wrapper思想和*args, **kwargs的写法,就能应付大部分函数装饰场景了。类相关装饰器将在后续《类》中展开。