问题: 装饰器可以装饰函数,也可以装饰类吗?
👇 点击展开/折叠 面试背诵模板(建议先背这个)
可以。装饰器可装饰任何可调用对象,包括函数、方法和类。装饰函数返回增强后的函数,装饰类返回增强后的类。两者本质相同:接收一个可调用对象,返回一个增强版本。区别在于装饰类时通常返回原类(添加属性/方法)或返回其实例(实现单例/注册)。类装饰器常用于添加类属性、注册到全局 registry、实现单例模式等。
---
装饰类的两种典型场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def add_method(cls): def new_method(self): return "动态添加的方法" cls.new_method = new_method cls.author = "admin" return cls
@add_method class User: pass
u = User() print(u.new_method()) print(User.author)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper
@singleton class Database: def __init__(self): self.connected = True
d1 = Database() d2 = Database() print(d1 is d2)
|
装饰类 vs 装饰函数的返回结果对比
| 装饰目标 |
接收参数 |
返回结果 |
典型操作 |
| 函数 |
函数对象 |
函数对象(wrapper 闭包) |
计时、日志、权限校验 |
| 类 |
类对象 |
类对象(原类增强)或实例对象 |
添加方法/属性、注册、单例 |
类实现装饰器 vs 函数实现装饰器(装饰类时)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def add_logging(cls): def log_wrapper(self): print(f"调用 {self.__class__.__name__}") cls.log = log_wrapper return cls
class AddAttribute: def __init__(self, attr_name, attr_value): self.attr_name = attr_name self.attr_value = attr_value def __call__(self, cls): setattr(cls, self.attr_name, self.attr_value) return cls
@AddAttribute("version", "1.0") class Service: pass print(Service.version)
|
装饰类时的注意事项:保留元数据
functools.wraps 对类无效,因为类的 __name__、__doc__ 等属性是描述符,不能直接复制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def rename(cls): cls.__name__ = "RenamedClass" return cls
@rename class Original: pass
print(Original.__name__)
from functools import update_wrapper
def preserve_meta(cls): original_name = cls.__name__ original_doc = cls.__doc__ def decorator(cls): cls.__name__ = original_name cls.__doc__ = original_doc return cls return decorator
|