Python 装饰器
装饰器是 Python 中的高级特性,可以在不修改原函数的情况下增强其功能。
函数是对象
在 Python 中,函数是一等公民,可以作为参数传递和返回值:
def say_hello():
return "Hello"
def say_goodbye():
return "Goodbye"
# 函数可以赋值给变量
func = say_hello
print(func()) # Hello
# 函数可以作为参数
def greet(func):
print(func())
greet(say_hello) # Hello
greet(say_goodbye) # Goodbye
# 函数可以作为返回值
def get_greeting(type):
if type == "morning":
return say_hello
else:
return say_goodbye
func = get_greeting("morning")
print(func()) # Hello
基本装饰器
定义装饰器
装饰器是一个接收函数作为参数的函数:
def my_decorator(func):
def wrapper():
print("调用函数之前")
func()
print("调用函数之后")
return wrapper
def say_hello():
print("Hello!")
# 使用装饰器
say_hello = my_decorator(say_hello)
say_hello()
输出:
调用函数之前
Hello!
调用函数之后
使用 @ 语法
def my_decorator(func):
def wrapper():
print("调用函数之前")
func()
print("调用函数之后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
带参数的装饰器
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
输出:
Hello!
Hello!
Hello!
@functools.wraps
使用 functools.wraps 保留原函数的元信息:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("调用前")
result = func(*args, **kwargs)
print("调用后")
return result
return wrapper
@my_decorator
def say_hello():
"""这是 say_hello 函数"""
print("Hello!")
print(say_hello.__name__) # say_hello(而不是 wrapper)
print(say_hello.__doc__) # 这是 say_hello 函数
常用装饰器示例
1. 计时装饰器
import functools
import time
def timer(func):
@functools.wraps(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(1)
print("完成")
slow_function()
2. 日志装饰器
import functools
import logging
logging.basicConfig(level=logging.INFO)
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"调用 {func.__name__},参数:{args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 返回:{result}")
return result
return wrapper
@log
def add(a, b):
return a + b
print(add(3, 5))
3. 缓存装饰器
import functools
def cache(func):
@functools.wraps(func)
def wrapper(*args):
if args not in wrapper.cache:
wrapper.cache[args] = func(*args)
return wrapper.cache[args]
wrapper.cache = {}
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 很快,因为有缓存
4. 权限检查装饰器
import functools
def require_admin(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
user_role = kwargs.get("role", "guest")
if user_role != "admin":
raise PermissionError("需要管理员权限")
return func(*args, **kwargs)
return wrapper
@require_admin
def delete_user(user_id, role="guest"):
return f"用户 {user_id} 已删除"
# 管理员可以删除
print(delete_user(1, role="admin")) # 用户 1 已删除
# 普通用户不能删除
# print(delete_user(1, role="user")) # PermissionError
5. 重试装饰器
import functools
import time
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts >= max_attempts:
raise e
print(f"尝试 {attempts} 失败,{delay}秒后重试...")
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unstable_function():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return "成功"
print(unstable_function())
类装饰器
装饰器不仅可用于函数,也可用于类:
def singleton(cls):
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("创建数据库连接")
def query(self, sql):
return f"执行: {sql}"
db1 = Database()
db2 = Database()
print(db1 is db2) # True(单例)
装饰器堆叠
多个装饰器可以叠加使用:
def decorator1(func):
def wrapper(*args, **kwargs):
print("装饰器1 - 前")
result = func(*args, **kwargs)
print("装饰器1 - 后")
return result
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("装饰器2 - 前")
result = func(*args, **kwargs)
print("装饰器2 - 后")
return result
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 装饰器1 - 前
# 装饰器2 - 前
# Hello!
# 装饰器2 - 后
# 装饰器1 - 后
类方法装饰器
@property
将方法转换为属性:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 5
print(circle.area) # 78.53975
circle.radius = 10
print(circle.area) # 314.159
@classmethod
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, date_str):
year, month, day = map(int, date_str.split("-"))
return cls(year, month, day)
@classmethod
def today(cls):
import datetime
now = datetime.datetime.now()
return cls(now.year, now.month, now.day)
date = Date.from_string("2024-01-01")
print(date.year, date.month, date.day)
today = Date.today()
print(today.year, today.month, today.day)
@staticmethod
class Math:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(n):
return n % 2 == 0
print(Math.add(3, 5)) # 8
print(Math.is_even(10)) # True
小结
本章我们学习了:
- 函数作为对象(可以赋值、作为参数、作为返回值)
- 基本装饰器的定义和使用
- 带参数的装饰器
- 使用
@functools.wraps保留元信息 - 常用装饰器(计时、日志、缓存、权限检查、重试)
- 类装饰器
- 装饰器堆叠
- 类方法装饰器(@property、@classmethod、@staticmethod)
练习
- 创建一个防抖装饰器
- 创建一个验证函数参数类型的装饰器
- 创建一个自动重试的装饰器,支持指数退避
- 创建一个性能分析装饰器,记录函数调用次数和总耗时