page contents

Python中的可变对象和不可变对象?

在 Python 里,可变对象和不可变对象这个话题,说简单也简单,说细了也能聊半天。很多人在面试的时候,一听到这个问题,脑子里第一反应就是“list 可变、tuple 不可变”,然后就没了下文。其实这事儿比这复杂得多,光靠记几个数据类型的标签,面试官一追问,你就容易掉坑。

attachments-2025-08-gDf43UOj689fe215568e2.jpg在 Python 里,可变对象和不可变对象这个话题,说简单也简单,说细了也能聊半天。很多人在面试的时候,一听到这个问题,脑子里第一反应就是“list 可变、tuple 不可变”,然后就没了下文。其实这事儿比这复杂得多,光靠记几个数据类型的标签,面试官一追问,你就容易掉坑。

先从定义上说,可变对象就是创建之后,它的内容(值)可以被直接修改的对象;不可变对象则是一旦创建,就不能直接改变其内容的对象。听上去很直白,但 Python 这个语言的设计让这个“能改”和“不能改”的边界,有时候比你想象的模糊。

先举个简单例子。一个 list:

a = [123]
a.append(4)
print(a)  # [1, 2, 3, 4]

a 是个可变对象,因为我们调用 append 就能原地把它改了,id(a) 在操作前后是一样的。再看 tuple

b = (123)
# b[0] = 100  # 直接改会报错

tuple 不允许你通过下标去改值,看似很安全,但很多人不知道,如果 tuple 里面装的是可变对象,事情就不一样了:

c = ([12], 3)
c[0].append(99)
print(c)  # ([1, 2, 99], 3)

这个 tuple 自己没变(它的引用没变),但是里面那个 list 被改了。所以“不可变”并不等于“彻底冻结”,它只是指最外层的容器结构不变,至于里面放了啥,那是另一回事。

再说到不可变对象,比如 int、str、tuple,这些都是 Python 内建的典型不可变类型。一个有意思的点是,Python 会对一些小的 int 对象做缓存,所以你会看到这样的行为:

x = 256
y = 256
print(x is y)  # True

m = 257
n = 257
print(m is n)  # False

这和可变不可变没直接关系,但它能暴露一个事实:对象的 id 在 Python 里是可以被复用的,所以判断两个变量是否是同一个对象,不要随便拿 is 当值比较用。

说到可变对象,list、dict、set 这三兄弟基本是面试必提。它们都有“原地改”的方法,比如 list 的 appendextend,dict 的 update、set 的 add。这些操作会直接改变对象本身,不会创建新对象。

那为什么 Python 要区分可变和不可变呢?有两个大原因。第一是性能,特别是在函数参数传递的时候。Python 里参数传递其实是“对象引用的传递”,可变对象传进去,在函数里改了,外面就跟着变,这对需要共享状态的场景很有用。但这也埋下了坑,比如下面这样:

def add_item(lst, item):
    lst.append(item)
    return lst

my_list = [12]
add_item(my_list, 3)
print(my_list)  # [1, 2, 3]

你可能本来只是想要个新列表,结果老的也被改了。很多新手就是在这里踩坑的,解决办法很简单,进函数前先 copy 一份。

第二个原因是安全性。不可变对象在多线程、多进程环境下更安全,因为它们天生没有“被别人改掉”的风险,这对于做字典的 key 或 set 的元素来说尤其重要。Python 要求字典的 key 必须是可哈希(hashable)的,而可变对象是不可哈希的,因为它的 hash 值会随着内容变化,这会直接破坏哈希表结构。

这时候你就能理解,为什么 tuple 可以当字典 key,而 list 不行。甚至 tuple 也不是绝对安全的,如果 tuple 里放了个 list,那它也不是真正意义上的可哈希。

再说一个面试官爱问的细节:字符串是不可变的,但为什么还能用 replaceupper 之类的方法“改”它?答案是,这些方法其实是返回了一个新的字符串对象,原字符串压根没动。

s = "hello"
t = s.upper()
print(s)  # hello
print(t)  # HELLO

id(s) 和 id(t) 肯定不一样,这就是不可变对象的特征:所有看似修改的操作,其实都是创建了新对象。

还有一种常见的考点是函数默认参数。可变对象当默认参数是大坑:

def f(val, items=[]):
    items.append(val)
    return items

print(f(1))  # [1]
print(f(2))  # [1, 2]

你可能以为每次调用都会给你一个新的列表,其实不然,默认参数在函数定义时就绑定了那个 list 对象,每次调用都在改同一个列表。这时候要么用不可变类型(比如 None)做默认值,要么每次手动生成新对象。

总结下来,可变对象和不可变对象在 Python 里是个底层设计选择,它直接影响到函数调用、内存管理、线程安全、数据结构设计等方方面面。别看它像是个初级问题,里面细节多得很,面试官随便拎一个场景追问,你要是没踩过坑,很容易答空。

说实话,我觉得这玩意儿平时写代码的时候更应该注意,而不是等到面试才去背。理解了它的原理,你就会知道什么时候该用 list,什么时候该用 tuple,什么时候要 copy,什么时候可以直接传引用。这样写出来的代码,既不容易出莫名其妙的 bug,也更高效。毕竟,坑是早晚要踩的,但要踩在自己能爬出来的地方才行。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

  • 发表于 2025-08-16 09:42
  • 阅读 ( 21 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

1335 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2172 文章
  3. Pack 1335 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章