入门教程、案例源码、学习资料、读者群
请访问: python666.cn
大家好,欢迎来到 Crossin的编程教室 !
今天这篇我要继续来打脸互联网上各种以讹传讹的水货教程。
前阵子我们聊了下Python中有关函数参数传递以及变量赋值的一些内容:Python到底是引用传递还是值传递?网上大多数教程都讲错了
先简单回顾下前文要点:
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
这句话是没错。但这个说法其实极具误导性!难道我给一个可变对象进行赋值,就不是生成一个新对象了吗?
a = [0] print('a', id(a)) a = [1] print('a', id(a))
输出:
a 140286304509768 a 140286304509832
这不是也变了嘛!只要是给变量赋一个新值,就会生成一个新对象。关于这点,我们稍后还会再说。
因为对象不可变,所以为了提高效率,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
这里插一句,判断一个列表是否包含某一元素的 in 操作符,你觉得它是判断 == 还是 is?猜猜这个输出是什么:
print(1.0 in [1, 2, 3])
与不可变对象的区别在于,可变对象除了被赋值外,还可以对自身内容进行修改,如:
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 等方法,这根本是两码事。就像我前面写的,如果大家都是赋值,那么无论是否可变,效果都是一样的,都会生成一个新对象,而不会对原变量的值产生影响。
所以理解了 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
是不可变对象,所以它的方法如 replace
、strip
、upper
都不可能修改原对象,只会返回一个新对象,必须重新赋值才可以。而 list
是可变对象,它的方法如 reverse
、sort
、append
,都是在原有对象上直接修改,无返回值。
不过,有个特殊情况需要注意:
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 里执行下看看输出,是不是和预期一样,想想为什么?这个牵涉到浅拷贝、深拷贝的概念,我们下次再聊。
感谢转发和点赞的各位~
_往期文章推荐_