设为首页收藏本站

Crossin的编程教室

 找回密码
 立即加入
查看: 59470|回复: 0
打印 上一主题 下一主题

可变对象与不可变对象

[复制链接]

169

主题

1

好友

733

积分

版主

Rank: 7Rank: 7Rank: 7

跳转到指定楼层
楼主
发表于 2018-7-13 22:27:44 |只看该作者 |正序浏览

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

简单回顾下要点:

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

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

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

讲到这里就有个常被提及的概念:可变对象和不可变对象

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

不可变对象不允许对自身内容进行修改。如果我们对一个不可变对象进行赋值,实际上是生成一个新对象,再让变量指向这个对象。哪怕这个对象简单到只是数字 0 和 1:
  1. a = 0
  2. print('a', id(a))
  3. a = 1
  4. print('a', id(a))
复制代码
输出:
  1. a 4463151440
  2. a 4463151472
复制代码
因为对象不可变,所以为了提高效率,Python 会使用一些公用的对象:
  1. a = 1
  2. print('a', id(a))
  3. b = 1
  4. print('b', id(b))
  5. print(a == b)
  6. print(a is b)
  7. c = 'hello world'
  8. print('c', id(c))
  9. d = 'hello world'
  10. print('d', id(d))
  11. print(c == d)
  12. print(c is d)
复制代码
输出:
  1. a 4423761776
  2. b 4423761776
  3. True
  4. True
  5. c 4430180912
  6. d 4430180912
  7. True
  8. True
复制代码
这里顺便提一下 is 这个操作符。它和 == 的区别在于:== 只判断“值”是不是相等,而 is 则判断是否为同一个对象,也就是地址一致。比如:
  1. a = 2
  2. b = 2.0
  3. print(a == b)
  4. print(a is b)
复制代码
输出:
  1. True
  2. False
复制代码
而可变对象则可以对自身内容进行修改,如:
  1. m = [1, 2, 3]
  2. print('m', m, id(m))
  3. m[1] = 4
  4. print('m', m, id(m))
  5. m.append(5)
  6. print('m', m, id(m))
复制代码
输出:
  1. m [1, 2, 3] 4536815752
  2. m [1, 4, 3] 4536815752
  3. m [1, 4, 3, 5] 4536815752
复制代码
可以看到,虽然 m 的值发生了变化,但是地址没变,还是原来那个 m。

上次我也说到,很多的教程都在用可变和不可变来谈论赋值和参数传递,我觉得这很不好。因为他们说到不可变对象时用的是赋值,而说到可变对象又用了 list 的索引、apeend 等方法,这根本是两码事。如果大家都是赋值,那么无论是否可变,效果都是一样的:
  1. m = [1, 2, 3]
  2. print('m', m, id(m))
  3. m = [4, 5, 6]
  4. print('m', m, id(m))
复制代码
输出
  1. m [1, 2, 3] 4329894024
  2. m [4, 5, 6] 4329910856
复制代码
所以理解了 Python 的赋值原理,就明白这与是否可变无关。而可变对象于不可变对象本身的不同仅在于一个可以修改变量的值,而另一个不允许。

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

另外,明白了可变与不可变的区别,一些方法的效果也就自然理解了:
  1. s = 'abc'
  2. s2 = s.replace('b', 'd')
  3. print('s', s)
  4. print('s2', s2)
  5. m = [1, 2, 3]
  6. m2 = m.reverse()
  7. print('m', m)
  8. print('m2', m2)
复制代码
输出:
  1. s abc
  2. s2 adc
  3. m [3, 2, 1]
  4. m2 None
复制代码
因为 str 是不可变对象,所以它的方法如 replace、strip、upper 都不可能修改原对象,只会返回一个新对象,比如重新赋值才可以。而 list 是可变对象,它的方法如 reverse、sort、append,都是在原有对象上直接修改,无返回值。

不过,有个特殊情况需要注意:
  1. m = [1, 2, 3]
  2. print('m', m, id(m))
  3. m += [4]
  4. print('m', m, id(m))
  5. m = m + [5]
  6. print('m', m, id(m))
复制代码
输出
  1. m [1, 2, 3] 4494164104
  2. m [1, 2, 3, 4] 4494164104
  3. m [1, 2, 3, 4, 5] 4494181128
复制代码
m = m + 和 m += 虽然是一样的结果,但 m 指向的对象却发生了变化。原因在于,前者是做了赋值操作,而后者其实是调用的 __iadd__ 方法。

如果我们就是需要产生一个 list 对象的副本,可以通过 [:]:
  1. m = [1, 2, 3]
  2. print('m', m, id(m))
  3. n = m[:]
  4. print('n', n, id(n))
  5. n[1] = 4
  6. print('m', m)
  7. print('n', n)
复制代码
这样对 n 的修改便不再会影响到 m,因为它们已不是同一个对象。

那么如果是这样呢:
  1. m = [1, 2, [3]]
  2. n = m[:]
  3. n[1] = 4
  4. n[2][0] = 5
  5. print(m)
复制代码
猜一猜 m 的结果是什么?
[1, 2, [3]][1, 4, [3]][1, 2, [5]][1, 4, [5]]其它结果
再去 Python 里执行下看看输出,是不是和预期一样,想想为什么?这个牵涉到浅拷贝、深拷贝的概念,我们下次再聊。



════其他文章及回答:如何自学Python | 新手引导 | 精选Python问答 | Python单词表 | 区块链 | 人工智能 | 双11 | 嘻哈 | 爬虫 | 排序算法 | 我用Python | 高考 | 世界杯 | 竞猜 | requests欢迎搜索及关注:Crossin的编程教室





回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即加入

QQ|手机版|Archiver|Crossin的编程教室 ( 苏ICP备15063769号  

GMT+8, 2024-11-23 00:40 , Processed in 0.015678 second(s), 22 queries .

Powered by Discuz! X2.5

© 2001-2012 Comsenz Inc.

回顶部