UP | HOME

Table of Contents

Chapter 2 序列构成的数组

列表

分类

从存放的数据类型可以分为:

  • 容器序列(container sequence),如: listtuplecollections.deque ,可以存放不同类型数据的*引用*。
  • 扁平序列(flat sequence),如: strbytesbytearraymemoryviewarray.array , 只能容纳字符、字节和数值这种基础类型。扁平序列是一段连续的内存空间,更加紧凑。

从是否可变可以分为:

  • 可变序列,如: listbytearrayarray.arraycollections.dequememoryview
  • 不可变序列,如: tuplestrbytes

列表推导

通常的原则是,列表推导只用来创建新的列表,并保持简短,尽量避免滥用,如通过列表推导重复获取列表的副作用。

*列表推导不会再有变量泄漏的问题*,在 Python 2.x 中,列表推导 for 关键字之后的赋值可能会影响上下文中的同名变量

>>> x = 'my precious'
>>> dummy = [x for x in 'ABC']
>>> x
'C'

但在 Python 3 中列表推导有了自己的局部作用域,不会影响上下文的赋值。

元组(tuple)

元组拆包或可迭代元素拆包(Iterable Unpacking)

  • 交换两个变量的值: a, b = b, a
  • 用 * 运算符把一个可迭代对象拆开作为函数的参数: test(*a_tuple)
  • 用*来处理剩下的元素: a, b, *rest = range(5)

namedtuple

namedtuple 构建的类,内存消耗跟元组一样,比普通的对象实例消耗要小一些,常用方法or属性: _fields_make()_asdict()

作为不可变列表

除增减元素相关方法外,元素支持列表的其他所有方法,另外元组没有 _reversed__ 方法,不过可以通过 reverse(a_tup) 替代。

切片

如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单独 一个值,也要把它转换成可迭代的序列。

对序列使用 + 和 *

对序列使用 + 和 *,都不会改变原有序列,而是重新创建新的序列。但如果序列中的元素是对象的引用,则需要注意下面这种情况:

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

>>> weird_board[1][2] = 'O'
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

weird_board 中的三个元素都是对同一个列表的引用,所以在赋值时,其实是在操作同一个列表,所以要通过每次创建新的列表来避免这种问题, board = [['_'] * 3 for i in range(3)]

序列的增量赋值

增量赋值运算符 += 和 *= 的表现取决于它们的第一个操作对象。

  • += 背后的特殊方法是 __iadd__ (用于“就地加法”),会赋值给原对象,没有 __iadd__ 方法会退一步调用 __add__ 方法,会产生一个新的对象,并把结果赋值给新对象。
  • *= 作用在可变序列上时会在原序列上追加新元素;作用在不可变序列上时,每次都会产生新对象,效率极低。

str 是一个例外,因为对字符串做 += 实在是太普遍了,所以 CPython 对它做了优化。为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉及复制原有字符串到新位置这类操作。

一个关于+=的谜题

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

由于 t[2] 是可变的,所以列表元素正常追加,但将结果赋值给 t[2] 就会报错,因为t是元素,不可变。

所以:

  • 不要把可变对象放在元组里面
  • 增量赋值不是一个原子操作

bisect

根据分数,找到对应传成绩等级

def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    i = bisect.bisect(breakpoints, score)
    return grades[i]

当列表不再是首选

  • 只处理数字列表时, array.arraylist 更高效
  • memoryview 是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片
  • collections.deque 类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型
    • queue
    • multiprocessing
    • asyncio
    • heapq