如何将带有复杂__init__逻辑的常规Python类设置为冻结状态?
实现常规Python类的冻结效果(类似冻结dataclass)
对于带有复杂__init__逻辑的常规类,要实现构造完成后禁止修改的效果,有几种实用且优雅的方案:
方法一:重写__setattr__和__delattr__拦截修改
这是最直接的方式,通过自定义属性赋值与删除的拦截逻辑,在实例初始化完成后阻止任何修改操作。
class FrozenRegularClass: def __init__(self, a, b): # 模拟复杂初始化逻辑:计算衍生属性 self.a = a self.b = b self.sum_ab = a + b self.product_ab = a * b # 标记初始化完成,这行必须放在__init__最后 self._is_initialized = True def __setattr__(self, name, value): # 允许初始化阶段的赋值,以及对标记位本身的设置 if not hasattr(self, '_is_initialized') or name == '_is_initialized': super().__setattr__(name, value) else: raise AttributeError(f"无法修改冻结实例的属性 '{name}'") def __delattr__(self, name): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"无法删除冻结实例的属性 '{name}'") super().__delattr__(name) # 测试 obj = FrozenRegularClass(2, 3) print(obj.sum_ab) # 输出5 # 以下操作会抛出AttributeError # obj.a = 4 # del obj.b
方法二:使用元类批量实现冻结
如果多个类需要冻结效果,用元类可以统一注入逻辑,避免重复编写拦截代码。
class FrozenMeta(type): def __new__(cls, name, bases, attrs): # 保留类原本的__setattr__和__delattr__(如果有的话) original_setattr = attrs.get('__setattr__', super().__setattr__) original_delattr = attrs.get('__delattr__', super().__delattr__) def new_setattr(self, name, value): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"无法修改冻结实例的属性 '{name}'") original_setattr(self, name, value) def new_delattr(self, name): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"无法删除冻结实例的属性 '{name}'") original_delattr(self, name) attrs['__setattr__'] = new_setattr attrs['__delattr__'] = new_delattr return super().__new__(cls, name, bases, attrs) # 基于元类创建冻结类 class ComplexFrozenClass(metaclass=FrozenMeta): def __init__(self, data): # 复杂初始化:处理输入数据 self.processed_data = [x * 2 for x in data] self.data_length = len(data) self._is_initialized = True # 测试 obj = ComplexFrozenClass([1,2,3]) print(obj.processed_data) # 输出[2,4,6] # obj.processed_data = [0,0,0] # 抛出AttributeError
方法三:用装饰器注入冻结逻辑
装饰器是更灵活的方式,不需要修改类的继承关系或元类,直接给需要冻结的类添加标记即可。
def frozen(cls): original_setattr = cls.__setattr__ original_delattr = cls.__delattr__ def new_setattr(self, name, value): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"无法修改冻结实例的属性 '{name}'") original_setattr(self, name, value) def new_delattr(self, name): if hasattr(self, '_is_initialized') and self._is_initialized: raise AttributeError(f"无法删除冻结实例的属性 '{name}'") original_delattr(self, name) cls.__setattr__ = new_setattr cls.__delattr__ = new_delattr return cls # 使用装饰器标记冻结类 @frozen class AnotherFrozenClass: def __init__(self, x, y): # 复杂初始化逻辑 self.x_squared = x ** 2 self.y_cubed = y ** 3 self.ratio = x / y if y != 0 else 0 self._is_initialized = True # 测试 obj = AnotherFrozenClass(4, 2) print(obj.ratio) # 输出2.0 # obj.x_squared = 100 # 抛出AttributeError
注意事项
- 所有方案都依赖
_is_initialized标记,必须将它放在__init__的最后一行,确保所有初始化逻辑完成后才启动冻结。 - 如果需要允许特定属性被修改,可以在
__setattr__中添加白名单判断,比如if name in ['allowed_attr']:就跳过拦截。
内容的提问来源于stack exchange,提问作者Allen Wang
相关产品推荐
相关产品推荐

