博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
可变对象与不可变对象
阅读量:6029 次
发布时间:2019-06-20

本文共 2439 字,大约阅读时间需要 8 分钟。

前阵子我们聊了下函数的参数传递以及变量赋值的一些内容:

简单回顾下要点:

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

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

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

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

可变对象和不可变对象

在 Python 中,

可变对象 包括 list、dict、set、自定义类型 等;
不可变对象 包括 int、float、bool、str、tuple 等。

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

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

输出:

a 4463151440a 4463151472

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

a = 1print('a', id(a))b = 1print('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 4423761776b 4423761776TrueTruec 4430180912d 4430180912TrueTrue

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

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

输出:

TrueFalse

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

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

输出:

m [1, 2, 3] 4536815752m [1, 4, 3] 4536815752m [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] 4329894024m [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 abcs2 adcm [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] 4494164104m [1, 2, 3, 4] 4494164104m [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] = 4print('m', m)print('n', n)

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

那么如果是这样呢:

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

猜一猜 m 的结果是什么?

  1. [1, 2, [3]]
  2. [1, 4, [3]]
  3. [1, 2, [5]]
  4. [1, 4, [5]]
  5. 其它结果

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

════

其他文章及回答:
| | | | | | | | | | | | | |
欢迎搜索及关注: Crossin的编程教室

转载地址:http://bedhx.baihongyu.com/

你可能感兴趣的文章
C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入
查看>>
AngularJs ng-change事件/指令(转)
查看>>
linux系统下安装两个或多个tomcat
查看>>
ProtoBuffer 简单例子
查看>>
iOS多线程开发系列之(一)NSThread
查看>>
微信小程序初体验(上)- 腾讯ISUX社交用户体验设计成员出品
查看>>
SAP WM Physical Inventory Method ST & PZ
查看>>
一次快速的数据迁移感悟
查看>>
MySQL修改提示符
查看>>
《ELK Stack权威指南(第2版)》一3.6 Java日志
查看>>
C++流的streambuf详解及TCP流的实现
查看>>
《量化金融R语言初级教程》一2.5 协方差矩阵中的噪声
查看>>
mysql到elasticsearch数据迁移踩坑实践-Ali0th
查看>>
Python轻量级数据分析库DaPy
查看>>
beetl 和 shrio 结合
查看>>
相对/绝对路径,cd命令,mkdir/rmdir命令,rm命令
查看>>
tomcat中web.xml各配置项的意义
查看>>
Nodejs学习笔记(二):《node.js开发指南》代码中需要注意的几点
查看>>
Ztree异步加载自动展开节点
查看>>
反射操作公共成员变量
查看>>