卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

    • Python有哪些内置数据类型?
    • Python变量保存的是什么?
    • 可变对象和不可变对象有什么区别?
      • 简要回答
      • 详细回答
      • 知识拓展
    • is和==有什么区别?
    • Python的运算符有哪些?
    • Python条件判断和循环的常见写法与易错点
    • Python字符串为什么不可变?
    • Python列表和元组有什么区别?
    • Python字典和集合有什么区别?
    • Python切片和序列解包怎么用?
    • 列表、字典和集合推导式怎么写?
  • 函数、作用域与模块

  • 面向对象

  • 进阶语法与语言机制

# Python中的可变对象和不可变对象有什么区别?

面试官问:“Python中的可变对象和不可变对象有什么区别?”

很多录友会背出“int、str、tuple不可变,list、dict、set可变”,但被追问“x += 1到底改没改对象”“元组里放个列表还算不可变吗”,就答不上来了。这题的关键是分清两种操作:原地修改对象和让名字重新绑定到新对象。这一篇接着《Python变量保存的是什么?》往下讲。

# 简要回答

可变对象创建后,可以在保持身份不变的前提下原地修改它的值,常见的有list、dict、set和大多数自定义类的实例。不可变对象创建后值不能改变,常见的有int、float、bool、str、tuple、frozenset和bytes。

对不可变对象做“修改”,比如x = x + 1、text.upper(),得到的都是新对象,原对象不变;变量只是被重新绑定了。对可变对象调用append()、update()这类方法,改的是同一个对象,所有指向它的变量都会看到变化。

不可变还要注意边界:元组不可变,指的是它保存的引用不能换,如果元素本身是列表,列表内部仍然可以修改。这也是元组不一定可哈希的原因。

# 详细回答

# 判断标准:身份是否保持不变

判断一个操作改没改原对象,最直接的方法是看操作前后id()有没有变化:

count = 10
before = id(count)
count += 1
print(id(count) == before)  # False,count绑定到了新对象11

scores = [90, 85]
before = id(scores)
scores.append(70)
print(id(scores) == before)  # True,同一个列表被原地修改
1
2
3
4
5
6
7
8
9

int不可变,count += 1只能创建新对象11,再让count指向它。list可变,append()直接改动原列表,身份不变。

原地修改与重新绑定对比

左边是不可变对象:名字换了指向,旧对象原封不动。右边是可变对象:对象本身被改了,所以所有绑定到它的名字都会看到新值。

# 共享可变对象时最容易踩坑

两个名字指向同一个可变对象时,从任意一个名字修改,另一个都会受影响:

config = {"debug": False}
backup = config
backup["debug"] = True
print(config)  # {'debug': True}
1
2
3
4

backup = config没有复制字典,只是多了一个名字。很多人想“先备份再修改”,结果备份和原数据一起被改。真正要备份,需要创建新对象,比如config.copy();嵌套结构还要考虑浅拷贝和深拷贝的差异。

不可变对象就没有这个问题。字符串的所有“修改”方法都返回新字符串,原字符串不受影响,所以多个名字共享同一个字符串是安全的。字符串为什么设计成不可变,可以看《Python字符串为什么不可变?》。

# +=的行为取决于对象类型

+=经常被当作“原地加”,但它的行为取决于对象是否支持原地修改:

items = [1]
alias = items
items += [2]
print(alias)  # [1, 2],列表被原地扩展

point = (1,)
alias = point
point += (2,)
print(alias)  # (1,),元组创建了新对象
1
2
3
4
5
6
7
8
9

列表实现了原地加法,items += [2]相当于在原列表上扩展,alias也能看到。元组不支持原地修改,point += (2,)会创建新元组再重新绑定point,alias仍指向旧元组。

同样的+=,对可变对象是原地修改,对不可变对象是重新绑定。

# 元组的“不可变”有边界

元组不可变,是说它保存的引用不能增删、不能替换。它不保证元素指向的对象也不可变:

record = ("Python", [90, 85])
record[1].append(70)
print(record)  # ('Python', [90, 85, 70])

record[1] = []  # TypeError: 'tuple' object does not support item assignment
1
2
3
4
5

record[1].append(70)没有修改元组,修改的是元组引用的那个列表。record[1] = []想替换元组里的引用,才会被拒绝。

元组嵌套列表引用关系

图中可以看到,元组锁住的是槽位里的引用,绿色路径改的是被引用的列表,红色路径想换掉槽位里的引用,所以会抛出TypeError。

# 可变性和哈希的关系

字典的键和集合的元素要求可哈希:对象在生命周期内哈希值不能变化,并且能用==比较。可变的内置容器如list、dict、set不可哈希,因为内容一变,哈希值就会失效,字典就找不到原来的键。

元组是否可哈希取决于元素:(1, 2)可以作为字典键,([1, 2], 3)调用hash()会抛出TypeError: unhashable type: 'list'。所以“不可变对象一定可哈希”这个说法也不准确。

# 知识拓展

面试官可能追问:

Q1:既然字符串不可变,为什么s += "a"能执行成功?

因为这行代码没有修改原字符串,而是创建了一个新字符串,再让s重新绑定过去。操作前后看id(s),通常会发现身份变了。循环里反复拼接字符串会不断产生新对象,大量拼接更推荐收集到列表后用"".join()。

Q2:函数内修改传入的列表,为什么外面也变了?

调用函数时,形参和实参绑定到同一个对象。函数内append()是原地修改,外面看到的是同一个列表;函数内写items = []则是重新绑定形参,不会影响外面。这正是可变性和名字绑定在函数调用中的体现。

Q3:不可变对象有什么好处?

不可变对象可以被多个名字安全共享,不用担心被意外修改;值稳定也让它适合作为字典键和集合元素。在CPython中,解释器还会对部分不可变对象做复用优化,但这只是实现细节,判断相等时仍应使用==,原因见《is和==有什么区别?》。

Q4:列表和元组除了可变性,还有哪些区别?

还涉及哈希、内存占用和使用语义:元组常用来表示结构固定的一组数据,列表表示同类元素的可变集合。完整对比见《Python列表和元组有什么区别?》。

Last Updated: 10/13/2026, 5:57:00 PM

← Python变量保存的是什么? is和==有什么区别? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪