在 Python 里,可变对象和不可变对象这个话题,说简单也简单,说细了也能聊半天。很多人在面试的时候,一听到这个问题,脑子里第一反应就是“list 可变、tuple 不可变”,然后就没了下文。其实这事儿比这复杂得多,光靠记几个数据类型的标签,面试官一追问,你就容易掉坑。
先从定义上说,可变对象就是创建之后,它的内容(值)可以被直接修改的对象;不可变对象则是一旦创建,就不能直接改变其内容的对象。听上去很直白,但 Python 这个语言的设计让这个“能改”和“不能改”的边界,有时候比你想象的模糊。
先举个简单例子。一个 list:
a 是个可变对象,因为我们调用 append 就能原地把它改了,id(a) 在操作前后是一样的。再看 tuple:
tuple 不允许你通过下标去改值,看似很安全,但很多人不知道,如果 tuple 里面装的是可变对象,事情就不一样了:
这和可变不可变没直接关系,但它能暴露一个事实:对象的 id 在 Python 里是可以被复用的,所以判断两个变量是否是同一个对象,不要随便拿 is 当值比较用。说到可变对象,list、dict、set 这三兄弟基本是面试必提。它们都有“原地改”的方法,比如 list 的 append、extend,dict 的 update、set 的 add。这些操作会直接改变对象本身,不会创建新对象。那为什么 Python 要区分可变和不可变呢?有两个大原因。第一是性能,特别是在函数参数传递的时候。Python 里参数传递其实是“对象引用的传递”,可变对象传进去,在函数里改了,外面就跟着变,这对需要共享状态的场景很有用。但这也埋下了坑,比如下面这样:
你可能本来只是想要个新列表,结果老的也被改了。很多新手就是在这里踩坑的,解决办法很简单,进函数前先 copy 一份。第二个原因是安全性。不可变对象在多线程、多进程环境下更安全,因为它们天生没有“被别人改掉”的风险,这对于做字典的 key 或 set 的元素来说尤其重要。Python 要求字典的 key 必须是可哈希(hashable)的,而可变对象是不可哈希的,因为它的 hash 值会随着内容变化,这会直接破坏哈希表结构。这时候你就能理解,为什么 tuple 可以当字典 key,而 list 不行。甚至 tuple 也不是绝对安全的,如果 tuple 里放了个 list,那它也不是真正意义上的可哈希。再说一个面试官爱问的细节:字符串是不可变的,但为什么还能用 replace、upper 之类的方法“改”它?答案是,这些方法其实是返回了一个新的字符串对象,原字符串压根没动。
id(s) 和 id(t) 肯定不一样,这就是不可变对象的特征:所有看似修改的操作,其实都是创建了新对象。还有一种常见的考点是函数默认参数。可变对象当默认参数是大坑:
你可能以为每次调用都会给你一个新的列表,其实不然,默认参数在函数定义时就绑定了那个 list 对象,每次调用都在改同一个列表。这时候要么用不可变类型(比如 None)做默认值,要么每次手动生成新对象。总结下来,可变对象和不可变对象在 Python 里是个底层设计选择,它直接影响到函数调用、内存管理、线程安全、数据结构设计等方方面面。别看它像是个初级问题,里面细节多得很,面试官随便拎一个场景追问,你要是没踩过坑,很容易答空。说实话,我觉得这玩意儿平时写代码的时候更应该注意,而不是等到面试才去背。理解了它的原理,你就会知道什么时候该用 list,什么时候该用 tuple,什么时候要 copy,什么时候可以直接传引用。这样写出来的代码,既不容易出莫名其妙的 bug,也更高效。毕竟,坑是早晚要踩的,但要踩在自己能爬出来的地方才行。
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://www.phpxs.com/post/13380/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料