最新消息:XAMPP默认安装之后是很不安全的,我们只需要点击左方菜单的 "安全"选项,按照向导操作即可完成安全设置。

Python元组的赋值谜题

XAMPP案例 admin 391浏览 0评论

00Python

相信很多人对tuple和list的区别的理解是tuple是一个不可变的序列, 不能对它的元素赋值。我之前也是这么理解的,举个例子:

  1. In : a = (1, 2, 3)
  2. In : a[3] = 4
  3. ---------------------------------------------------------------------------
  4. TypeError                                 Traceback (most recent call last)
  5. <ipython-input-5-d840230b1ac3> in <module>()
  6. ----> 1 a[3] = 4
  7. TypeError: 'tuple' object does not support item assignment
  8. In : a
  9. Out: (1, 2, 3)

也就是一个元组生成,它的元素就不再能改变了。

但是相信很多人见过下面这样的玩法(有人把它当做Python的一个笑话):

  1. In : a = (1, 2, [3, 4])
  2. In : a[2] += [5, 6]
  3. ---------------------------------------------------------------------------
  4. TypeError                                 Traceback (most recent call last)
  5. <ipython-input-2-84fb4a701b92> in <module>()
  6. ----> 1 a[2] += [5, 6]
  7. TypeError: 'tuple' object does not support item assignment
  8. In : a
  9. Out: (1, 2, [3, 4, 5, 6])

明确的报错了,可是为了a的值还是改了呢?

我曾经思考过这个问题,直接上感觉是「对列表[3, 4]的赋值成功,但是后来发生的元组赋值失败造成的」,但是一直苦于没有证据。直到昨晚看《Fluent Python》的时候,才从作者哪里获得了肯定的答案。今天我们用dis模块来分析+=所产生的bytecode(把python代码反汇编为字节码指令):

  1. In : import dis
  2. In : a = (1, 2, [3, 4])
  3. In : dis.dis('a[2] += [5, 6]')
  4.  1           0 LOAD_NAME                0 (a)
  5.              2 LOAD_CONST               0 (2)
  6.              4 DUP_TOP_TWO
  7.              6 BINARY_SUBSCR
  8.              8 LOAD_CONST               1 (5)
  9.             10 LOAD_CONST               2 (6)
  10.             12 BUILD_LIST               2
  11.             14 INPLACE_ADD
  12.             16 ROT_THREE
  13.             18 STORE_SUBSCR
  14.             20 LOAD_CONST               3 (None)
  15.             22 RETURN_VALUE

看起来出现了一坨指令,我挨个逐步的解释下:

  1. LOAD_NAME。把本地变量中相关的值(也就是a)放入堆栈。
  2. LOAD_CONST。把字节码中用到的对应常量(也就是2)放入堆栈。
  3. DUP_TOP_TWO。复制栈顶中前2个引用(也就是a和2),并保留顺序。
  4. BINARY_SUBSCR。把a[2]放到栈顶。
  5. LOAD_CONST。再分别把5和6放入堆栈。
  6. BUILD_LIST。 根据目前堆栈包含的数量创建一个列表,并放入堆栈。
  7. INPLACE_ADD。 a += b其实就是 a = a + b,也就是对栈顶做in-place add的操作。
  8. ROT_THREE。把堆栈中的第二和第三升高,把栈顶(也就是[3, 4, 5, 6])降到栈中的第三位。
  9. STORE_SUBSCR。就是执行 a[2] = [3, 4, 5, 6]。但是由于tuple不可变,这步失败了。

可以看到执行的过程,是先对列表进行了iadd操作并且成功,而之后的tuple赋值失败报错。

也就是:

  1. x = a[2]
  2. x = x.__iadd__([5, 6])
  3. a[2] = x

这样。验证下:

  1. In : a = (1, 2, [3, 4])
  2. In : a[2] = [3, 4, 5, 6]
  3. ---------------------------------------------------------------------------
  4. TypeError                                 Traceback (most recent call last)
  5. <ipython-input-31-d5ba6baf4cf6> in <module>()
  6. ----> 1 a[2] = [3, 4, 5, 6]
  7. TypeError: 'tuple' object does not support item assignment
  8. In : a
  9. Out: (1, 2, [3, 4])

可以看到直接赋值的没有成功。

在Python中,变量赋值采用对象引用的方式,传递的是一个对象的内存地址(像一个指针)。在这里a各项指向了内存中储存了不同数据的实体,对list实体的修改会成功:

  1. In : b = [3, 4]
  2. In : a = (1, 2, b)
  3. In : id(b)
  4. Out: 4571378504
  5. In : a[2] += [5, 6]
  6. ---------------------------------------------------------------------------
  7. TypeError                                 Traceback (most recent call last)
  8. <ipython-input-16-84fb4a701b92> in <module>()
  9. ----> 1 a[2] += [5, 6]
  10. TypeError: 'tuple' object does not support item assignment
  11. In : id(b)
  12. Out: 4571378504
  13. In : a
  14. Out: (1, 2, [3, 4, 5, 6])

可以看到b在值被改变之后,还是原来的那个对象。但是对于其他项的修改就不成功:

  1. In : a[1] += 1
  2. ---------------------------------------------------------------------------
  3. TypeError                                 Traceback (most recent call last)
  4. <ipython-input-25-9fac1c91b625> in <module>()
  5. ----> 1 a[1] += 1
  6. TypeError: 'tuple' object does not support item assignment
  7. In : a
  8. Out: (1, 2, [3, 4, 5, 6])

这是因为数值型(number)、字符串(string)均为不可变的对象。而字典也可以修改成功:

  1. In : a = (1, 2, {'b': 1})
  2. In : a[2]['b'] += 3
  3. In : a
  4. Out: (1, 2, {'b': 4})

竟然没有报错就成功了。我们再直接赋值看看:

  1. In : a[2] = {'b': 5}
  2. ---------------------------------------------------------------------------
  3. TypeError                                 Traceback (most recent call last)
  4. <ipython-input-36-a2916525c596> in <module>()
  5. ----> 1 a[2] = {'b': 5}
  6. TypeError: 'tuple' object does not support item assignment
  7. In : a
  8. Out: (1, 2, {'b': 4})

所以 a[2]['b'] += 3并不是对元组的赋值,而是直接操作了元组中的字典项了。感受下:

  1. In : a = (1, 2, {'b': 1})
  2. In : dis.dis("a[2]['b'] += 5")
  3.          0 STORE_GLOBAL    12891 (12891)
  4.          3 FOR_ITER        10075 (to 10081)
  5.          6 DELETE_GLOBAL   23847 (23847)
  6.          9 SLICE+2
  7.         10 STORE_SLICE+3
  8.         11 DELETE_SUBSCR
  9.         12 SLICE+2
  10.         13 DELETE_SLICE+3
  11. In : c = a[2]
  12. In : c
  13. Out: {'b': 1}
  14. In : dis.dis("c['b'] += 5")
  15.          0 DUP_TOPX        10075
  16.          3 DELETE_GLOBAL   23847 (23847)
  17.          6 SLICE+2
  18.          7 STORE_SLICE+3
  19.          8 DELETE_SUBSCR
  20.          9 SLICE+2
  21.         10 DELETE_SLICE+3

看到了吧,c是一个dict,对 c['b'] += 5"操作的字节码指令和 a[2]['b'] += 5的下面绝大部分的指令一样。

转载请注明:XAMPP中文组官网 » Python元组的赋值谜题

您必须 登录 才能发表评论!