如何用Python元类实现:所有属性访问一轮前禁止重复访问
实现属性轮询访问的元类
需求说明
给定属性列表(如["foo", "bar", "baz"]),要求类的实例满足:
- 所有属性可按任意顺序各访问一次
- 一轮访问周期内(所有属性未全部访问完之前),不能重复访问任何属性
- 当一轮所有属性都访问完成后,重置访问记录,允许重新开始下一轮访问
示例用法
class TrackedAttrsMeta(type): @classmethod def with_attributes(cls, *args): # 绑定需要追踪的属性集合到元类 cls._expected_attrs = set(args) return cls class MyClass(metaclass=TrackedAttrsMeta.with_attributes("foo", "bar", "baz")): foo = "quux" bar = {"blah": -1} baz = 1 ob = MyClass() for _ in range(10): print(ob.foo) # 允许:第一轮首次访问 print(ob.bar) # 允许:第一轮未访问过该属性 print(ob.foo) # 触发错误:baz还未访问,不能重复访问foo print(ob.baz) # 若没有上述错误则允许访问:完成第一轮访问 print(ob.foo) # 若已完成第一轮则允许:新一轮首次访问
完整实现代码
class TrackedAttrsMeta(type): @classmethod def with_attributes(cls, *args): # 保存需要追踪的属性集合 cls._expected_attrs = set(args) return cls def __call__(cls, *args, **kwargs): # 创建实例时初始化已访问属性的集合 instance = super().__call__(*args, **kwargs) instance._accessed_attrs = set() return instance def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) original_getattr = cls.__getattribute__ def _tracking_getattr(self, name): # 跳过内部属性的追踪,避免逻辑冲突 if name == "_accessed_attrs": return original_getattr(self, name) expected_attrs = self.__class__._expected_attrs # 先获取属性值,确保异常不影响属性本身的获取逻辑 value = original_getattr(self, name) # 仅对指定属性进行访问校验 if name in expected_attrs: accessed = self._accessed_attrs remaining = expected_attrs - accessed if name in accessed: raise ValueError(f"已访问过'{name}',必须先访问完剩余属性{list(remaining)}才能再次访问") accessed.add(name) # 完成一轮访问后重置记录 if accessed == expected_attrs: self._accessed_attrs = set() return value cls.__getattribute__ = _tracking_getattr
关键实现说明
- 元类的
with_attributes方法:接收需要追踪的属性参数,将其保存为元类的_expected_attrs属性,返回元类本身用于创建目标类。 - 实例初始化:通过元类的
__call__方法,在创建实例时自动初始化_accessed_attrs集合,用于记录当前轮次已访问的属性。 - 自定义
__getattribute__:- 跳过对内部属性
_accessed_attrs的追踪,避免逻辑死循环 - 先获取属性值,确保即使后续抛出异常,属性本身的正常获取不受影响
- 仅对指定属性进行访问校验:若当前属性已在本轮访问过,抛出错误;否则将其加入已访问集合
- 当本轮所有属性都访问完成后,清空
_accessed_attrs,允许开始下一轮访问
- 跳过对内部属性
测试验证
运行以下代码可验证逻辑正确性:
class MyClass(metaclass=TrackedAttrsMeta.with_attributes("foo", "bar", "baz")): foo = "quux" bar = {"blah": -1} baz = 1 ob = MyClass() # 第一轮正常访问 print(ob.foo) # 输出: quux print(ob.bar) # 输出: {'blah': -1} print(ob.baz) # 输出: 1 # 完成一轮后重置,可再次访问foo print(ob.foo) # 输出: quux # 测试重复访问错误 try: print(ob.bar) print(ob.bar) # 此处触发异常 except ValueError as e: print(e) # 输出: 已访问过'bar',必须先访问完剩余属性['baz']才能再次访问
内容的提问来源于stack exchange,提问作者ijustlovemath
相关产品推荐
相关产品推荐

