跳到主要内容

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

小结

本章我们学习了:

  1. 函数作为对象(可以赋值、作为参数、作为返回值)
  2. 基本装饰器的定义和使用
  3. 带参数的装饰器
  4. 使用 @functools.wraps 保留元信息
  5. 常用装饰器(计时、日志、缓存、权限检查、重试)
  6. 类装饰器
  7. 装饰器堆叠
  8. 类方法装饰器(@property、@classmethod、@staticmethod)

练习

  1. 创建一个防抖装饰器
  2. 创建一个验证函数参数类型的装饰器
  3. 创建一个自动重试的装饰器,支持指数退避
  4. 创建一个性能分析装饰器,记录函数调用次数和总耗时