类装饰器需配合__getattribute__或__getattr__才能拦截属性访问,核心是动态注入访问控制逻辑,通过线程局部存储或上下文变量识别调用者身份,而非仅依赖字段名,并需兼容继承、__slots__及间接访问路径。

类装饰器怎么拦截属性访问
类装饰器本身不直接拦截属性读写,必须配合 __getattribute__ 或 __getattr__ 才能实现权限控制。常见误区是只在装饰器里加个 @classmethod 或简单包装 __init__,这完全无法阻止后续对实例属性的任意访问。
真正起作用的是在被装饰类中动态注入访问控制逻辑。典型做法是:装饰器返回一个新类(或就地修改原类),重载 __getattribute__,并在其中检查当前调用栈的调用者、线程身份或上下文标记。
- 用
inspect.stack()获取调用方函数名,判断是否来自白名单方法(如get_data) - 避免在
__getattribute__里直接访问self._role,否则会触发无限递归——要用super().__getattribute__('role') - 如果只控制部分字段(比如
_secret),优先用__getattr__(仅对未定义属性触发),性能更好且更安全
如何区分“谁在访问”而不是“访问了什么”
权限控制的核心难点不是字段黑名单,而是识别调用上下文。Python 没有内置的“调用者身份”,得靠约定或运行时推断。比如:你不能只看 attr == 'password' 就拒绝,而要确认这次访问是不是来自 AdminSession.validate()。
推荐一种轻量方案:用 threading.local() 绑定当前执行上下文的身份标识,在关键入口(如视图函数、服务方法)手动设置,再在 __getattribute__ 中读取。
import threading
_local = threading.local()
def set_role(role):
_local.role = role
class AccessControlled:
def __getattribute__(self, name):
if name.startswith('_') and name != '_role':
role = getattr(_local, 'role', None)
if role != 'admin':
raise PermissionError(f"Access denied to {name}")
return super().__getattribute__(name)
- 装饰器只需把原类的
__getattribute__替换为上述逻辑,无需改写所有方法 - 注意
_local.role是线程隔离的,但协程(asyncio)中不生效,需换用contextvars.ContextVar - 不要依赖
frame.f_code.co_name判断调用者——优化后可能被内联,不可靠
为什么不用 property 而要上装饰器
单个字段用 @property 完全够用,但当你要统一控制几十个字段、或动态加载的字段(如从 JSON 构建的配置对象)、或不同角色对同一字段有不同读写粒度时,property 会爆炸式膨胀。
类装饰器的价值在于“一次注入,全局生效”。它把权限逻辑从字段声明层抽离到类行为层,让数据结构本身保持干净。
- 装饰器可以接收参数,比如
@access_control(roles=['admin', 'auditor']),比每个@property里重复写条件清晰得多 - 若字段名来自外部配置(如数据库 schema),根本没法提前写
@property,只能靠运行时拦截 - property 的 setter/getter 无法拦截
del obj.attr,而__getattribute__可以配合__delattr__一并控制
容易忽略的继承与元类冲突
如果你的被装饰类本身继承自某个已有基类(比如 dataclass 或 ORM 模型),直接重写 __getattribute__ 可能破坏父类的关键逻辑(如 SQLAlchemy 的属性延迟加载)。这时候不能粗暴覆盖,得做兼容性适配。
最稳妥的做法是:装饰器检测父类是否已定义 __getattribute__,如果是,用 functools.wraps 包装原方法,在前后插入权限检查,而不是直接替换。
- 检查
hasattr(cls, '__getattribute__') and cls.__getattribute__ is not object.__getattribute__ - 避免用
type('NewClass', (cls,), {...})动态创建类——会丢失原始类的__annotations__和__dataclass_fields__ - 如果目标类用了
__slots__,确保新注入的方法不会意外触发AttributeError
权限控制不是加一层装饰器就完事,关键在访问路径是否真实可控。很多看似生效的装饰器,其实只拦住了直接点号访问,却漏掉了 getattr(obj, field)、字典转换、序列化等间接路径——这些都得单独加固。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











