python函数参数传递是对象引用传递:不可变对象(如int、str)在函数内重新赋值不影响外部,可变对象(如list、dict)就地修改会影响外部;避免副作用应使用copy()、deepcopy()或不可变类。

因为不可变类能从根本上规避状态意外变更、线程竞争和哈希失效三类高发问题,不是“可选优化”,而是关键场景下的必要设计。
不可变类如何防止函数内意外修改入参
Python参数传递本质是对象引用传递。对可变对象(如 list、dict)做原地修改,调用方看到的值就变了——这常导致隐蔽的副作用。
比如你写一个配置解析函数:
def parse_config(cfg_dict):
cfg_dict['version'] = '1.0' # 原地改
return cfg_dict.get('host')
调用方传入的原始 cfg_dict 已被污染。换成不可变类(如 types.SimpleNamespace 的只读封装,或自定义 frozen=True 的 dataclass),任何赋值都会抛出 AttributeError,错误在第一时间暴露。
- 推荐用
@dataclass(frozen=True)定义配置类,字段不可赋值,且自动支持__hash__ - 避免手动实现
__setattr__,容易漏掉__dict__或描述符绕过 - 若需部分可变,应显式提供
with_*方法返回新实例(如config.with_timeout(30))
为什么多线程环境下不可变类天然安全
线程安全的核心是“无共享可变状态”。只要对象创建后所有字段都不可变,多个线程同时读取它就无需加锁、不会发生竞态条件。
对比:list 在多线程中 append 可能丢数据;tuple 即使被十万线程并发读取,也绝对一致。
- 注意陷阱:不可变类中若包含可变对象(如
tuple里存了list),整体仍不安全——必须递归冻结或使用深拷贝防御 -
NamedTuple和frozen dataclass默认只做浅冻结,嵌套dict仍可被改 - 生产环境建议配合
typing.Final标注字段,辅助静态检查工具发现非法赋值
不可变类作为字典键或集合元素的前提
只有实现了 __hash__ 且哈希值稳定的对象才能当 dict 键或 set 元素。而 Python 要求:一旦对象可哈希,其生命周期内内容不能变——否则哈希表索引会错乱。
例如:
cache = {}
key = ('user_id', 123)
cache[key] = 'cached_data'
# 若 key 是可变列表 [‘user_id’, 123],后续修改它会导致 cache[key] 查不到
- 自定义不可变类必须显式实现
__hash__(通常直接返回hash((self.a, self.b))) - 切勿在
__hash__中引用可变属性或外部状态(如时间戳、随机数) - 如果类有
__eq__,__hash__必须与之逻辑一致,否则set去重失效
真正难的不是“让类不可变”,而是确保不可变性穿透到所有嵌套层级,并在接口设计上杜绝绕过路径。很多看似成功的不可变类,上线后才发现某个 getter 返回了内部可变对象的引用,成了静默漏洞。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











