[EDIT] 没想到会被转发。很高兴也很囧。看来眼球多了的确容易纠错。要小心啊小心。
编程语言就是这样。有时候你已经用它写了数万行代码,自以为很熟了。知道某一天遇到一个意料之外的问题。然后你才发现,原来TMD是这样的。
问题
def change(l1, l2):
l1.append(10)
l2 = [7, 5, 3, 1]
list1 = [1, 3, 5, 7]
list2 = [3, 3, 3, 3]
change(list1, list2)
print(list1)
print(list2)
以上这段代码的输出是什么?如果你的答案是
[1, 3, 5, 7, 10]
[3, 3, 3, 3]
那么大牛请安静的离开。如果不是,可以读一下下面的故事。
赋值
最简单的赋值
list1 = [1, 3, 5, 7]
到底发生了什么呢?一个变量list1
被赋值了?太笼统了吧。
对于Python来说,Everything is an object。上面这行代码其实是这样的:
创建一个list对象
创建一个名字
绑定
再来看个例子:
s1 = "hello"
s2 = "hello"
print(s1 is s2)
这个会输出什么?True
! 因为是这样的:
创建一个string对象
创建名字
s1
并绑定到string对象创建名字
s2
并绑定到string对象
而s1
和s2
绑定的是同一个对象!
涨姿势了。那么问题又来了:
l1 = [1, 3, 5, 7]
l2 = [1, 3, 5, 7]
print(l1 is l2)
结果是?。。。。。。一定是True
了吧?错!
这里又有一个很重要的概念。Mutable 和 immutable。"hello"
和[1, 3, 5, 7]
的区别就在于:前者是Immutable而后者是Mutable。对于可变的对象,Python会自动生成全新的对象,以确保各对象可以独立的变动。而不可变的就不需要 - 节省内存。
传参
回到开始的问题。当list1
被传给change()
的时候,到底是什么被传过去了?让我们来做一个小实验。在传入前和change()
内部各加入一个语句
print(id(list1))
和
print(id(l1))
这里要解释一下,Python的每一个对象都有一个唯一的hash id。id()
这个函数就是用来打印出这个id的。加了上面两行之后,我们会发现。在change()
函数之外与之内,两个值是一样的。也就是说Python是把同一个对象传给了change()
。
这下我们就可以理解为什么list1
在调用了change()
之后被改动了。但是那么list2
也应该被改动了才对啊?非也。这里,list2
所绑定的对象的确被传进来了,的确生成了一个新list对象。但是这个对象被绑定给了l2
。跟list2
没半毛钱关系。我们在做一个实验验证一下。
def change(l1, l2):
l1.append(10)
l2 = [7, 5, 3, 1]
list1 = [1, 3, 5, 7]
list2 = [3, 3, 3, 3]
change(list1, list2)
print(list1)
print(list2)
import dis
dis.dis(change)
对比开始的代码,我们加入了最后两句。这个两句使用了dis
模块把change
反编译了。结果如下:
2 0 LOAD_FAST 0 (l1)
3 LOAD_ATTR 0 (append)
6 LOAD_CONST 1 (10)
9 CALL_FUNCTION 1
12 POP_TOP
3 13 LOAD_CONST 2 (7)
16 LOAD_CONST 3 (5)
19 LOAD_CONST 4 (3)
22 LOAD_CONST 5 (1)
25 BUILD_LIST 4
28 STORE_FAST 1 (l2)
31 LOAD_CONST 0 (None)
34 RETURN_VALUE
对应原来的行号3,我们可以看到,Python用4个常数build了一个list。然后把这个绑定到了名字l2
然后就返回了。
至此真相大白。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。