python进阶学习笔记-序列的拼接、增量赋值

使用 * 拼接、创建列表

如果想要把一个序列复制几份然后再拼接起来,快捷的做法是把这个序列乘以一个整数产生一个新序列。

*会自动将列表复制几份然后拼接在一起放在一个列表中。

eg:

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

使用 * 创建由列表组成的列表

当需要初始化一个嵌套着几个列表的列表时,使用[[ ]] * n结合列表推导实现。

eg:

>>> board = [['_'] * 3 for i in range(3)]   #建立一个包含 3 个列表的列表,被包含的 3 个列表各自有 3 个元素(一个列表内具有的三个元素是由['_'] * 3产生的)
>>> board   #打印出这个嵌套列表
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'    #注意,在列表中的元素下标是从0开始的,所以这个下标实际上指的是第二个列表元素的第三个元素,该元素值修改为 X,再打印出这个列表
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

以上这几行代码等价于:

>>> board = []
>>> for i in range(3):
...		 row=['_'] * 3   #每次迭代中都新建了一个列表,作为新的一行追加到要创建的列表中。也就是说虽然这些列表内的元素是一样的,但是这样创建出来之后是三个不同的列表对象加入到了最终的列表中。
... 	 board.append(row)
...
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[2][0] = 'X'
>>> board   #修改原则相同
[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]

接下来是一种错误的与列表组成列表的方法:

>>> weird_board = [['_'] * 3] * 3 #最外层的这个*3创建的列表包含3个指向同一个列表的引用。
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 'O'  #因为创建列表时引用的列表都指向了同一个列表,所以列表内的所有列表都被修改了
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

以上这几行代码等价于:

row=['_'] * 3
board = []
for i in range(3):
	board.append(row)  #也就是将同一个列表对象加入到了列表中3次

序列的增量赋值

此部分通过增量赋值运算符 += 和 *= 实现的,两者用法和概念相似,所以以 += 为例。

+= 背后的调用的特殊方法是 iadd (用于“就地加法”,就是将值直接加到被赋值的变量上)。但是如果没有能够实现这个方法的条件,Python 会退一步调用 add

如果 a 实现调用了 iadd 这个方法。对可变序列(例如list、bytearray 和 array.array)来说,a 会就地改动,就像调用了 a.extend(b)一样,把可迭代对象b追加给a,不用先计算再赋值。但是如果 a 没有实现 iadd 的话,a += b 这个表达式的效果就变得跟 a = a+b 一样了:首先计算 a + b,得到一个新的对象,然后赋值给 a。
总体来讲,可变序列一般都实现了 iadd 方法,因此 += 是就地加法。而不可变序列根本就不支持这个操作。
上面所说的这些关于 += 的概念也适用于 *=,不同的是,后者相对应的方法是 imul(就地重复拼接)。

关于* =的id代码(展示 *= 在可变和不可变序列上的作用):

>>> l = [1, 2, 3]
>>> id(l)
4311953800  #刚开始时列表的 ID
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800  #运用增量乘法后,列表的 ID 没变,新元素追加到列表上
>>> t = (1, 2, 3)
>>> id(t)
4312681568  #元组最开始的 ID
>>> t *= 2
>>> id(t)
4301348296  #运用增量乘法后,新的元组被创建

所以对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。但str 是一个例外,为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉及复制原有字符串到新位置这类操作。

一个有趣的例子(使用到了dis()模块)

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):   #因为 tuple 不支持对它的元素赋值,抛出 TypeError 异常
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t     #虽然抛出了异常,但是t的值依然被改变了
(1, 2, [30, 40, 50, 60])

用dis()方法查看s[a] = b 背后的字节码,了解代码背后的运行机制:

>>> dis.dis('s[a] += b')
	1 0 LOAD_NAME 0(s)
	3 LOAD_NAME 1(a)
	6 DUP_TOP_TWO
	7 BINARY_SUBSCR   #将 s[a] 的值存入 TOS(Top Of Stack,栈的顶端)。
	8 LOAD_NAME 2(b)
	11 INPLACE_ADD  #计算 TOS += b。这一步能够完成,是因为TOS 指向的是一个可变对象(也就是示例2-15 里的列表)。
	12 ROT_THREE
	13 STORE_SUBSCR #s[a] = TOS 赋值。这一步失败,是因为 s是不可变的元组(示例 2-15 中的元组t)
	14 LOAD_CONST 0(None)
	17 RETURN_VALUE

首先我们来了解一下dis模块:
Python代码是先被编译为Python字节码后,再由Python虚拟机来执行Python字节码(pyc文件主要就是用于存储字节码指令的)。一般来说一个Python语句会对应若干字节码指令,Python的字节码是一种类似汇编指令的中间语言,但是一个字节码指令并不是对应一个机器指令(二进制指令),而是对应一段C代码,而不同的指令的性能不同,所以不能单独通过指令数量来判断代码的性能,而是要通过查看调用比较频繁的指令的代码来 确认一段程序的性能。

一个Python的程序会有若干代码块组成,例如一个Python文件会是一个代码块,一个类,一个函数都是一个代码块,一个代码块会对应一个运行的上下文环境以及一系列的字节码指令。

dis模块主要是用来分析字节码的一个内置模块,经常会用到的方法是dis.dis([bytesource]),参数为一个代码块,可以得到这个代码块对应的字节码指令序列。(当查出指令时,在网上搜一下就知道意思啦)

最后,注意:

  1. 不要把可变对象放在元组里面。
  2. 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。
  3. 可通过查看 Python 的字节码了解代码背后的运行机制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值