可变对象与不可变对象

简介: 前阵子我们聊了下函数的参数传递以及变量赋值的一些内容:关于函数参数传递,80%人都错了。

前阵子我们聊了下函数的参数传递以及变量赋值的一些内容:关于函数参数传递,80%人都错了


简单回顾下要点:


1. Python 中的变量不是装有对象的“容器”,而是贴在对象上的“标签”。



2. 参数传递相当于一次赋值:多贴了一个标签。


3. 至于在函数内部对参数的修改是否会影响到外部变量的值,取决于你怎样修改:如果是重新赋值就不会,如果是修改对象自身内容则会。


讲到这里就有个常被提及的概念:


可变对象和不可变对象


在 Python 中,


可变对象包括 list、dict、set、自定义类型等;


不可变对象包括 int、float、bool、str、tuple 等。


不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1:


a = 0
print('a', id(a))
a = 1
print('a', id(a))


输出:


a 4463151440
a 4463151472


因为对象不可变,所以为了提高效率,Python 会使用一些公用的对象:


a = 1
print('a', id(a))
b = 1
print('b', id(b))
print(a == b)
print(a is b)
c = 'hello world'
print('c', id(c))
d = 'hello world'
print('d', id(d))
print(c == d)
print(c is d)


输出:


a 4423761776
b 4423761776
True
True
c 4430180912
d 4430180912
True
True


这里顺便提一下 is 这个操作符。它和 == 的区别在于:== 只判断“值”是不是相等,而 is 则判断是否为同一个对象,也就是地址一致。比如:


a = 2
b = 2.0
print(a == b)
print(a is b)


输出:


True
False


而可变对象则可以对自身内容进行修改,如:


m = [1, 2, 3]
print('m', m, id(m))
m[1] = 4
print('m', m, id(m))
m.append(5)
print('m', m, id(m))


输出:


m [1, 2, 3] 4536815752
m [1, 4, 3] 4536815752
m [1, 4, 3, 5] 4536815752


可以看到,虽然 m 的值发生了变化,但是地址没变,还是原来那个 m。


上次我也说到,很多的教程都在用可变和不可变来谈论赋值和参数传递,我觉得这很不好。因为他们说到不可变对象时用的是赋值,而说到可变对象又用了 list 的索引、apeend 等方法,这根本是两码事。如果大家都是赋值,那么无论是否可变,效果都是一样的:


m = [1, 2, 3]
print('m', m, id(m))
m = [4, 5, 6]
print('m', m, id(m))


输出


m [1, 2, 3] 4329894024
m [4, 5, 6] 4329910856


所以理解了 Python 的赋值原理,就明白这与是否可变无关。而可变对象于不可变对象本身的不同仅在于一个可以修改变量的值,而另一个不允许。


基于这一设定,两者在功能上的最大区别就是:不可变对象可以作为字典 dict 的键 key,而可变对象不行。比如 list 不能作为字典的键,但 tuple 可以。


另外,明白了可变与不可变的区别,一些方法的效果也就自然理解了:


s = 'abc'
s2 = s.replace('b', 'd')
print('s', s)
print('s2', s2)
m = [1, 2, 3]
m2 = m.reverse()
print('m', m)
print('m2', m2)


输出:


s abc
s2 adc
m [3, 2, 1]
m2 None


因为 str 是不可变对象,所以它的方法如 replacestripupper 都不可能修改原对象,只会返回一个新对象,比如重新赋值才可以。而 list 是可变对象,它的方法如 reversesortappend,都是在原有对象上直接修改,无返回值。


不过,有个特殊情况需要注意:


m = [1, 2, 3]
print('m', m, id(m))
m += [4]
print('m', m, id(m))
m = m + [5]
print('m', m, id(m))


输出


m [1, 2, 3] 4494164104
m [1, 2, 3, 4] 4494164104
m [1, 2, 3, 4, 5] 4494181128


m = m +m += 虽然是一样的结果,但 m 指向的对象却发生了变化。原因在于,前者是做了赋值操作,而后者其实是调用的 __iadd__ 方法。


如果我们就是需要产生一个 list 对象的副本,可以通过 [:]


m = [1, 2, 3]
print('m', m, id(m))
n = m[:]
print('n', n, id(n))
n[1] = 4
print('m', m)
print('n', n)


这样对 n 的修改便不再会影响到 m,因为它们已不是同一个对象。


那么如果是这样呢:


m = [1, 2, [3]]
n = m[:]
n[1] = 4
n[2][0] = 5
print(m)


猜一猜 m 的结果是什么?

再去 Python 里执行下看看输出,是不是和预期一样,想想为什么?这个牵涉到浅拷贝、深拷贝的概念,我们下次再聊。


如需了解视频课程及答疑群等更多服务,请号内回复 码上行动

代码相关问题可以在论坛上发帖提问 bbs.crossincode.com

推荐阅读:

开发工具 | 世界杯 | 高考 | 我用Python | 知乎 | 排序 | 朋友圈 | 电影票 | 技术宅 | 火车票 | 单词表 | 押韵工具 | 新手建议 | 就业

相关文章
|
6月前
|
存储 Java 编译器
|
2月前
|
Python
列表的可变性
列表的可变性
创建一个对象用什么运算符?对象实体与对象引用有何不同?
创建一个对象用什么运算符?对象实体与对象引用有何不同?
|
3月前
|
存储 JavaScript 前端开发
对象的属性方法和深浅拷贝
总结,理解对象的属性和方法对于编程是基础而重要的,而掌握深浅拷贝的差异和使用场合则是编程的高级技能,它能帮助你有效地管理数据的完整性和独立性。
19 0
|
11月前
|
Python
Python中的深复制与浅复制:正确处理可变对象
在Python编程中,复制对象是一种常见的操作,而深复制和浅复制是两种不同的复制方式,尤其在处理可变对象时,选择正确的复制方式至关重要。本文将深入讨论深复制和浅复制的概念,并提供一些正确处理可变对象的实用技巧。
|
存储 索引
数组和对象有什么区别?
数组和对象有什么区别?
79 0
|
存储 JavaScript 前端开发
什么是数组,什么是对象,他们的区别是什么
什么是数组,什么是对象,他们的区别是什么
62 0
|
索引 Python 容器
继续打脸水货教程:关于可变对象与不可变对象
前阵子我们聊了下Python中有关函数参数传递以及变量赋值的一些内容:Python到底是引用传递还是值传递?网上大多数教程都讲错了
|
存储 Java Python
Python不可变对象与可变对象
Python语言是一个以**一切皆对象**的面向对象的动态型语言。Python的对象可以根据其是否可以变化划分为可变对象和不可变对象。
|
存储 缓存 安全
可变对象和不可变对象
编程语言的对象可变性是值得研究探讨的内容,本文试分析一下可变对象和不可变对象。
76 0