Python学习笔记(六)—— 序列运算

代码及内容源自《Fluent Python》——Luciano Ramalho 著

序列支持“+”和“*”运算,通常要求运算符两侧为相同的序列类型,并且运算的结果是生成一个新的序列而不会改变原来的任何一个运算对象。

>>> l=[1,2,3]
>>> l*5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5*'abcd'
'abcdabcdabcdabcdabcd'

有时,我们会需要用一定数量的嵌套列表来初始化一个列表。此时,最好的做法是采用list comprehension,如下

>>> board=[['_']*3 for i in range(3)]
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][1]='X'
>>> board
[['_', '_', '_'], ['_', 'X', '_'], ['_', '_', '_']]

与之相对的一种诱人却错误的方式是

>>> weird_board=[['_']*3]*3
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][1]='Y'
>>> weird_board
[['_', 'Y', '_'], ['_', 'Y', '_'], ['_', 'Y', '_']]

这段异常代码的问题在于,它的实际执行在本质上与下述代码类似:

>>> row=['_']*3
>>> board=[]
>>> for i in range(3):
...    board.append(row)

即,同一个row被引用了三次。不同的是,list comprehension的作用等同于下面的代码:

>>> board=[]
>>> for i in range(3):
...    row=['_']*3
...    board.append(row)
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][1]='Y'
>>> board
[['_', '_', '_'], ['_', 'Y', '_'], ['_', '_', '_']]

增量赋值“+=”,“*=”
增量赋值运算结果依赖于第一个运算对象,运算对象的类型不同,结果也有差别。
“+=”工作的基础是特殊方法__iadd__,如果该方法没有被实现,Python就会调用__add__。对于后者,由于运算中生成了新的对象,所以再赋值后会改变原操作对象的ID。该原理同样适用于“*=”,对应的特殊方法为__imul__。

>>> l=[1,2,3]
>>> id(l)
2297732364616
>>> l*=2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
2297732364616
>>> t=(1,2,3)
>>> id(t)
2297732591552
>>> t*=2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)
2297732664584

下面试着根据代码来判断哪个答案是正确的:

t=(1,2,[30,40])
t[2]+=[50,60]

a. t变为(1, 2, [30, 40, 50, 60])
b. TypeError: ‘tuple’ object does not support item assignment
c. a和b都不对
d. a和b都对

多数人会选择答案b,但实际上正确答案是d。以下是实际的执行结果。

>>> t=(1,2,[30,40])
>>> t[2]+=[50,60]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-19-a113d2524452> in <module>()
      1 t=(1,2,[30,40])
----> 2 t[2]+=[50,60]


TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

查看一下Python生成的字节码,能够更清楚的了解这是如何发生的。

>>> import dis
>>> dis.dis('S[a]+=b')
  1           0 LOAD_NAME                0 (S)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
  • “BINARY_SUBSCR” 将S[a]的值置于栈顶(Top Of Stack,TOS)
  • “INPLACE_ADD” 执行增量运算TOS +=b,如果TOS是一个mutable对象,这一过程被成功执行。(上例中,它是一个列表)
  • “STORE_SUBSCR” 执行赋值,S[a]=TOS, 这一过程失败,因为S是immutable对象。(上例中,t是一个tuple)

虽然这个例子所展示的情况并不常见,但是从该例中还是能学到以下几点:

  • 将mutable项置于tuple中不是一个好主意。
  • 增量赋值并不是一个atomic operation,正如上例中,它会在完成部分工作后,才将错误抛出。
  • 检查Python的字节码并不困难,并且通常都会有很大帮助。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值