核心要点
能列举:不可变 int/float/str/tuple/frozenset/bytes,可变 list/dict/set/bytearray
区分原地修改(lst.append,id 不变)与重新绑定(x += 1 生成新对象,id 变)
说清传参影响:函数内原地改可变对象会波及调用方,重绑定不会
知道只有不可变且可哈希的对象才能作 dict 键 / set 元素
简要回答
| 不可变 | 可变 |
|---|---|
| int, float, str, tuple, frozenset, bytes | list, dict, set, bytearray |
不可变:「修改」实际是创建新对象并重新绑定
标准回答
| 不可变 | 可变 |
|---|---|
| int, float, str, tuple, frozenset, bytes | list, dict, set, bytearray |
不可变:「修改」实际是创建新对象并重新绑定。如 s += "x" 对 str 生成新串。
可变:可原地改内容而不改身份。如 lst.append(1) 同一 id(lst)。
传参影响:
def f(x, lst):
x += 1 # 绑定新 int,不影响外部
lst.append(1) # 原地改,影响外部哈希:不可变且实现 __hash__ 的类型可作 dict/set 的键;可变类型不可哈希。
面试陷阱:tuple 不可变,但若元素含可变 list,list 内容仍可改。
常见误区
⚠️ 常见踩坑
认为 tuple 内任何情况都完全不可变;修改可变默认参数却以为每次调用独立。
追问
追问 1:字符串拼接为什么推荐 join 而非 +=?
str 不可变,循环里 s += x 每次都创建新字符串并复制旧内容,整体是 O(n²)。"".join(parts) 先算总长再一次性分配,是 O(n)。少量几次拼接用 += 无所谓,大循环用 join 或先 append 到 list 再 join。
追问 2:如何「复制」可变对象避免副作用?
浅拷贝用 lst.copy() / list(lst) / dict(d),只复制最外层,嵌套的子对象仍共享。要彻底隔离用 copy.deepcopy(),但开销大。判断需求:只在顶层增删用浅拷贝即可,会改到嵌套结构才用深拷贝。
追问 3:frozen dataclass 解决什么问题?
@dataclass(frozen=True) 让实例的属性不可重新赋值(赋值抛 FrozenInstanceError),并自动生成 __hash__,于是实例可作 dict 键 / set 元素。适合表示不可变的值对象(配置、坐标)。注意它只冻结绑定,若字段本身是 list 等可变对象,其内容仍可被修改。
延伸学习
与本题相关的知识库文章、术语、工具与行业资讯。
📰 AI 资讯