[EDIT] 没想到会被转发。很高兴也很囧。看来眼球多了的确容易纠错。要当心啊当心。编程
编程语言就是这样。有时候你已经用它写了数万行代码,自觉得很熟了。知道某一天遇到一个意料以外的问题。而后你才发现,原来TMD是这样的。app
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]
那么大牛请安静的离开。若是不是,能够读一下下面的故事。函数
最简单的赋值ui
list1 = [1, 3, 5, 7]
到底发生了什么呢?一个变量list1
被赋值了?太笼统了吧。
对于Python来讲,Everything is an object。上面这行代码实际上是这样的:code
建立一个list对象对象
建立一个名字内存
绑定string
再来看个例子:hash
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
而后就返回了。
至此真相大白。