跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Python 如何实现不可变对象

__slots__不能使对象真正不可变,因其仅限制动态添加属性,不阻止已有属性修改或可变容器内部变更;需结合__setattr__冻结、不可变类型封装或dataclasses.frozen=True等手段。 为什么
__slots__
不能让对象真正不可变 很多人以为给类加了
__slots__
就能锁死属性,其实它只限制实例属性的动态添加,不阻止已有属性被修改。比如
obj.x = 10
仍可执行,只要
x
在
__slots__
里声明过。更关键的是,
__slots__
对可变容器(如
list
、
dict
)内部的修改完全无约束——
obj.items.append(1)
照常运行。 用
__setattr__
和
__delattr__
拦截赋值 这是最直接的控制点:在实例初始化完成后禁用所有属性写入和删除操作。但要注意时机——必须等
__init__
执行完再启用保护,否则连初始化都会失败。 常见做法是设一个内部标志位:
class ImmutablePoint: def __init__(self, x, y): object.__setattr__(self, '_frozen', False) # 先允许设置 object.__setattr__(self, 'x', x) object.__setattr__(self, 'y', y) object.__setattr__(self, '_frozen', True) # 再冻结
def __setattr__(self, name, value):
    if getattr(self, '_frozen', False):
        raise TypeError(f"Cannot modify attribute '{name}'")
    object.__setattr__(self, name, value)

def __delattr__(self, name):
    if getattr(self, '_frozen', False):
        raise TypeError(f"Cannot delete attribute '{name}'")
    object.__delattr__(self, name)
立即学习 “ Python免费学习笔记(深入) ”; 必须用
object.__setattr__
绕过自定义逻辑,否则初始化会无限递归 检查
_frozen
要用
getattr
,避免在
_frozen
还未设时触发
__getattr__
或 AttributeError 这个方案对嵌套可变对象无效,比如
self.data = [1, 2]
后仍可执行
obj.data.append(3)
用
types.MappingProxyType
或
tuple
封装内部状态 如果对象本质是数据容器,把可变部分替换成真正不可变类型更可靠。例如用
tuple
替代
list
,用
MappingProxyType(dict)
替代裸
dict
。 Python 3.14.3 微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。 下载 示例:
from types import MappingProxyType

class Config: def init(self, data):

强制转为只读映射

    object.__setattr__(self, '_data', MappingProxyType(dict(data)))

def __getattr__(self, name):
    return self._data[name]

def __setattr__(self, name, value):
    raise TypeError("Config is immutable")
立即学习 “ Python免费学习笔记(深入) ”;
MappingProxyType
是真正的只读视图,任何修改尝试(包括
proxy['k'] = v
)都会抛
TypeError
注意它不递归冻结嵌套字典——如果原始
data
里有
{'inner': {}}
,那个
{}
仍是可变的 对简单结构,直接用
namedtuple
或
dataclasses.frozen=True
更省事,但它们不支持自定义方法或属性计算 使用
dataclasses.frozen=True
的实际限制 这是 Python 3.7+ 最省力的方案,但有几个容易忽略的坑: 字段默认值若为可变对象(如
list
、
dict
),会引发
ValueError
;必须用
default_factory
包一层 冻结后连
__dict__
都被禁用,无法动态挂方法或调试属性(除非显式继承并重写
__getattribute__
) 继承链中任一父类用了
frozen=True
,子类也自动冻结,且无法取消——哪怕子类没写
frozen=True
性能上比手写
__setattr__
稍好,因为冻结逻辑在 C 层实现,但调试时错误信息不如自定义的清晰 真正难的不是“怎么冻住”,而是判断哪些字段该冻、哪些不该冻,以及是否接受冻住后失去的所有灵活性——比如你没法再给实例临时加个缓存属性,也没法在测试时 patch 某个字段。

相关文章