1

[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。上面这行代码其实是这样的:

  1. 创建一个list对象

  2. 创建一个名字

  3. 绑定

再来看个例子:

s1 = "hello"
s2 = "hello"
print(s1 is s2)

这个会输出什么?True! 因为是这样的:

  1. 创建一个string对象

  2. 创建名字s1并绑定到string对象

  3. 创建名字s2并绑定到string对象

s1s2绑定的是同一个对象!

涨姿势了。那么问题又来了:

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然后就返回了。

至此真相大白。


仗键去国
1.5k 声望10 粉丝

码农; 顾问; 架构师; kubernetes, Openstack and docker 贡献者