Skip to content

序列构成的数组

此笔记记录于《流畅的python》,大部分为其中的摘要,少部分为笔者自己的理解;笔记为jupyter转的markdown,原始版jupyter笔记在这个仓库

Python也从ABC那里继承了用统一的风格去处理序列数据这一特点。不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片、排序,还有拼接。

内置序列类型概览

序列的分类

  • 容器序列:list、tuple、collections.deque,能存放不同类型的数据
  • 扁平序列:str、bytes、bytearray、memoryview、array.array,只能容纳一种类型

重点:容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。

序列类型还可以按照能否被修改来分类:

  • 可变序列:list、bytearray、array.array、collections.deque、memoryview
  • 不可变序列:tuple、str、bytes

列表推导和生成器表达式

列表推导是一种构建列表的方法,它异常强大,然而由于相关的句法比较晦涩,人们往往不愿意去用它。掌握列表推导还可以为我们打开生成器表达式(generator expression)的大门,后者具有生成各种类型的元素并用它们来填充序列的功能。

列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。如果你的代码里并不经常使用它们,那么很可能你错过了许多写出可读性更好且更高效的代码的机会。

python
symbols = '$¢£¥€¤'
codes = [ord(symbol) for symbol in symbols]
codes
[36, 162, 163, 165, 8364, 164]

Python会忽略代码里[]、{}和( )中的换行,因此如果你的代码里有多行的列表、列表推导、生成器表达式、字典这一类的,可以省略不太好看的续行符\。

filter和map合起来能做的事情,列表推导也可以做,而且还不需要借助难以理解和阅读的lambda表达式:

python
symbols = '$¢£¥€¤'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
beyond_ascii
[162, 163, 165, 8364, 164]
python
beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
beyond_ascii
[162, 163, 165, 8364, 164]

在Python中,lambda表达式用于创建匿名函数,也就是没有名字的函数。这种函数被称为lambda函数。lambda函数的语法如下:

python
lambda arguments: expression

这里,arguments是传入lambda函数的参数,可以是一个或多个。expression是使用这些参数进行操作的表达式。这个表达式的结果就是此lambda函数的返回值。

例如,下面是一个lambda函数,它接受两个参数并返回它们的和:

python
add = lambda x, y: x + y
print(add(5, 3))  # 输出:8

在这个例子中,xy是参数,x + y是表达式。

python
# 使用列表推导式计算笛卡尔积
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
tshirts
[('black', 'S'),
 ('black', 'M'),
 ('black', 'L'),
 ('white', 'S'),
 ('white', 'M'),
 ('white', 'L')]
python
# 如果想依照先尺码后颜色的顺序来排列,只需要调整从句的顺序
tshirts = [(color, size) for size in sizes for color in colors]
tshirts
[('black', 'S'),
 ('white', 'S'),
 ('black', 'M'),
 ('white', 'M'),
 ('black', 'L'),
 ('white', 'L')]

列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。

虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。前面那种方式显然能够节省内存。

生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。

python
symbols = '$¢£¥€¤'
# 如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来
tuple(ord(symbol) for symbol in symbols)
(36, 162, 163, 165, 8364, 164)
python
import array
# array.array 构造方法需要两个参数,因此必须加上括号
array.array('I', (ord(symbol) for symbol in symbols))
array('I', [36, 162, 163, 165, 8364, 164])

下方示例:则是利用生成器表达式实现了一个笛卡儿积,用以打印出上文中我们提到过的T恤衫的2种颜色和3种尺码的所有组合。与示例2-4不同的是,用到生成器表达式之后,内存里不会留下一个有6个组合的列表,因为生成器表达式会在每次for循环运行时才生成一个组合。如果要计算两个各有1000个元素的列表的笛卡儿积,生成器表达式就可以帮忙省掉运行for循环的开销,即一个含有100万个元素的列表。

生成器表达式逐个产出元素,从来不会一次性产出一个含有6个T恤样式的列表

python
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirts in ('%s %s' % (c, s) for c in colors for s in sizes):
  print(tshirts)
black S
black M
black L
white S
white M
white L

元组不仅仅是不可变的列表

元组的特点:

  1. 元组中的元素不能修改
  2. 可以用于没有字段名的记录

元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。

如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息就变得非常重要了。

python
lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)  # 拆包
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'),  
  ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids):  
  print('%s/%s'%passport)
BRA/CE342567
ESP/XDA205856
USA/31195855

元组拆包

元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是,被可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。除非我们用来表示忽略多余的元素,在“用来处理多余的元素”一节里,我会讲到它的具体用法。

python
# 还可以用`*`运算符把一个可迭代对象拆开作为函数的参数
divmod(20, 8)
(2, 4)
python
t = (20, 8)
divmod(*t)
(2, 4)
python
quotient, remainder = divmod(*t)
quotient, remainder
(2, 4)
python
# 让一个函数可以用元组的形式返回多个值
import os
_, filename = os.path.split('/home/luciano/.ssh/idrsa.pub')
filename
'idrsa.pub'

如果做的是国际化软件,那么_可能就不是一个理想的占位符,因为它也是gettext.gettext函数的常用别名,gettext模块的文档里提到了这一点。在其他情况下,_会是一个很好的占位符

python
# 在元组拆包中使用*也可以帮助我们把注意力集中在元组的部分元素上
a, b, *rest = range(5)
a, b, rest
(0, 1, [2, 3, 4])
python
# 嵌套元组拆包
metro_areas = [
    ('Tokyo','JP',36.933,(35.689722,139.691667)),  # ➊
    ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    ('Mexico City', 'MX', 20.142, (19.433333,-99.133333)),
    ('New York-Newark', 'US', 20.104, (40.808611,-74.020386)),
    ('Sao Paulo', 'BR', 19.649, (-23.547778,-46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}' # :15代表占位符,.4f代表保留4位小数
for name, cc, pop, (latitude, longitude) in metro_areas:  # ➋
    if longitude <= 0:  # ➌
        print(fmt.format(name, latitude, longitude))
                |   lat.    |   long.  
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358

具名元组

元组已经设计得很好用了,但作为记录来用的话,还是少了一个功能:我们时常会需要给记录中的字段命名。namedtuple函数的出现帮我们解决了这个问题。

python
from collections import namedtuple
City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933, (35, 139))
tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35, 139))
python
tokyo.population # 你可以通过字段名或者位置来获取一个字段的信息
36.933
python
tokyo.coordinates
(35, 139)
python
tokyo[1]
'JP'

具名元组的属性和方法

python
# _fields属性是一个包含这个类所有字段名称的元组
City._fields
('name', 'country', 'population', 'coordinates')
python
# 用_make( )通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟City(*delhi_data)是一样的。
City._make(('Tokyo', 'JP', 36.933, (35, 139)))
City(name='Tokyo', country='JP', population=36.933, coordinates=(35, 139))
python
# _asdict( )把具名元组以collections.OrderedDict的形式返回,我们可以利用它来把元组里的信息友好地呈现出来。
City._asdict(tokyo)
{'name': 'Tokyo',
 'country': 'JP',
 'population': 36.933,
 'coordinates': (35, 139)}

除了跟增减元素相关的方法之外,元组支持列表的其他所有方法,并且元组没有__reversed__方法。

切片

为什么切片和区间会忽略最后一个元素

  1. 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:range(3)my_list[:3]都返回3个元素。
  2. 当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop-start)即可。
  3. 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成my_list[:x]my_list[x:]就可以了
python
# 我们还可以用s[a:b:c]的形式对s在a和b之间以c为间隔取值。c的值还可以为负,负值意味着反向取值。
s = 'bicycle'
s[::3]
'bye'
python
s[::-1]
'elcycib'
python
s[::-2]
'eccb'

使用切片对象,给切片命名

python
invoice = """
0.....6................................40........52...55........
1909  Pimoroni PiBrella                    $17.50    3    $52.50
1489  6mm Tactile Switch x20                $4.95    2     $9.90
1510  Panavise Jr.-PV-201                $28.00    1    $28.00
1601  PiTFT Mini Kit 320x240               $34.95    1    $34.95
"""
SKU = slice(0, 6)
DESCRIPTION = slice(6, 40)
UNIT_PRICE = slice(40, 52)
QUANTITY = slice(52, 55)
ITEM_TOTAL = slice(55, None)
line_items = invoice.split('\n')[2:]
for item in line_items:
    print(item[UNIT_PRICE], item[DESCRIPTION])
   $17.50    Pimoroni PiBrella                 
    $4.95    6mm Tactile Switch x20            
 $28.00    1 Panavise Jr.-PV-201               
   $34.95    PiTFT Mini Kit 320x240            

如果你有一个多维数组,你可以使用...来表示省略的维度

python
import numpy as np

# 创建一个三维数组
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])

# 使用...来访问第一维度的所有元素
print(arr[..., 0])
[[ 1  4]
 [ 7 10]]

这段代码会打印出三维数组的第一维所有元素,即[[1, 4], [7, 10]]

给切片赋值

如果把切片放在赋值语句的左边,或把它作为del操作的对象,我们就可以对序列进行嫁接、切除或就地修改操作

python
l = list(range(10))
l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
python
l[2:5] = [20, 30]
l
[0, 1, 20, 30, 6, 7, 8, 9]
python
del l[5:7]
python
l
[0, 1, 20, 30, 6, 9]
python
l[3::2] = [11, 22]
python
l
[0, 1, 20, 11, 6, 22]
python
# 如果赋值的对象是一个切片,那么赋值语句的右侧必须是一个可迭代对象。即便只有单独一个值,也要把它转换成可迭代的序列。
l[2:5] = 100
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[38], line 2
      1 # 如果赋值的对象是一个切片,那么赋值语句的右侧必须是一个可迭代对象。即便只有单独一个值,也要把它转换成可迭代的序列。
----> 2 l[2:5] = 100


TypeError: can only assign an iterable

对序列使用+*

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

注意: +*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列

python
# 建立由列表组成的列表
board = [['_'] * 3 for i in range(3)]
board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
python
board[1][2] = 'X'
board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
python
# 下面这种构建二维列表的方式是错误的,因为它其实是三个指向同一个列表的引用。
weird_board = [['_'] * 3] * 3
weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
python
weird_board[1][2] = 'O'
weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
python
# 第一个例子等同于
row = ['_'] * 3
board = []
for i in range(3):
  row = ['_'] * 3
  board.append(row)
# 第二个例子等同于
board = []
for i in range(3):
  board.append(row)

序列的增量赋值

  • 增量赋值运算符+=*=的表现取决于它们的第一个操作对象
  • +=背后的特殊方法是__iadd__
  • 但是如果一个类没有实现这个方法的话,Python会退一步调用__add__
  • 上面所说的这些关于+=的概念也适用于*=,不同的是,后者相对应的是__imul__

接下来有个小例子,展示的是*=在可变和不可变序列上的作用

python
l = [1, 2, 3]
id(l)
2933536739648
python
l *= 2
l
[1, 2, 3, 1, 2, 3]
python
id(l)
2933536739648
python
t = (1, 2, 3)
id(t)
2933536817280
python
t *= 2
id(t)
2933536707616
  • 对于可变序列来说,执行增量乘法后,列表的ID没变,新元素追加到列表上。
  • 对于不可变序列来说,执行增量乘法后,原来的元组被销毁,创建一个新的元组,然后把新元组的引用赋值给变量t。

因此,对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。

python
# 关于+=的谜题
t = (1, 2, [30, 40])
t[2] += [50, 60]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[51], line 3
      1 # 关于+=的谜题
      2 t = (1, 2, [30, 40])
----> 3 t[2] += [50, 60]


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

没人料到的结果:t[2]被改动了,但是也有异常抛出,过程如下:

  1. 将s[a]的值存入TOS(Top Of Stack,栈的顶端)。
  2. 计算TOS+=b。这一步能够完成,是因为TOS指向的是一个可变对象。
  3. s[a]=TOS赋值。这一步失败,是因为s是不可变的元组。

因此,你需要注意:

  1. 不要把可变对象放在元组里面
  2. 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。

list.sort方法和内置函数sorted

  • list.sort方法会就地排序列表,也就是说不会把原列表复制一份。这也是这个方法的返回值是None的原因。
  • 与list.sort相反的是内置函数sorted,它会新建一个列表作为返回值。这个方法可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。
  • 不管是list.sort方法还是sorted函数,都有两个可选的关键字参数reverse、key
  • key代表一个只有一个参数的函数,这个函数会被用在序列里的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。

用返回None来表示就地改动这个惯例有个弊端,那就是调用者无法将其串联起来。而返回一个新对象的方法(比如说str里的所有方法)则正好相反,它们可以串联起来调用,从而形成连贯接口(fluent interface)

可选参数key还可以在内置函数min( )max( )中起作用。另外,还有些标准库里的函数也接受这个参数,像itertools.groupby( )heapq.nlargest( )

python
fruits = ['grape', 'raspberry', 'apple', 'banana']
sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
python
fruits
['grape', 'raspberry', 'apple', 'banana']
python
sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
python
sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
python
sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple']
python
fruits
['grape', 'raspberry', 'apple', 'banana']
python
fruits.sort()
python
fruits
['apple', 'banana', 'grape', 'raspberry']

用bisect来管理已排序的序列

bisect模块包含两个主要函数,bisect和insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素。

python
import bisect
import sys
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = '{0:2d} @ {1:2d}    {2}{0:<2d}'


def demo(bisect_fn):
    for needle in reversed(NEEDLES):
        position = bisect_fn(HAYSTACK, needle)
        offset = position * '  |'
        print(ROW_FMT.format(needle, position, offset))


if __name__ == '__main__':
    if sys.argv[-1] == 'left':
        bisect_fn = bisect.bisect_left
    else:
        bisect_fn = bisect.bisect
    print('DEMO:', bisect_fn.__name__)
    print('haystack->', ' '.join('%2d' % n for n in HAYSTACK))
    demo(bisect_fn)
DEMO: bisect_right
haystack->  1  4  5  6  8 12 15 20 21 23 23 26 29 30
31 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |31
30 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |30
29 @ 13      |  |  |  |  |  |  |  |  |  |  |  |  |29
23 @ 11      |  |  |  |  |  |  |  |  |  |  |23
22 @  9      |  |  |  |  |  |  |  |  |22
10 @  5      |  |  |  |  |10
 8 @  5      |  |  |  |  |8 
 5 @  3      |  |  |5 
 2 @  1      |2 
 1 @  1      |1 
 0 @  0    0 

bisect函数其实是bisect_right函数的别名,后者还有个姊妹函数叫bisect_left。它们的区别在于,bisect_left返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素会被放置于它相等的元素的前面,而bisect_right返回的则是跟它相等的元素之后的位置

bisect.insort插入新元素

排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。bisect.insort就是为了这个而存在的。

python
import bisect
import random
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
    new_item = random.randrange(SIZE*2)
    bisect.insort(my_list, new_item)
    print('%2d->' % new_item, my_list)
10-> [10]
 0-> [0, 10]
 6-> [0, 6, 10]
 8-> [0, 6, 8, 10]
 7-> [0, 6, 7, 8, 10]
 2-> [0, 2, 6, 7, 8, 10]
10-> [0, 2, 6, 7, 8, 10, 10]

当列表不是首选时

虽然列表既灵活又简单,但面对各类需求时,我们可能会有更好的选择。

  • 比如,要存放1000万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是float对象,而是数字的机器翻译,也就是字节表述。这一点就跟C语言中的数组一样。
  • 再比如说,如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。

数组

如果我们需要一个只包含数字的列表,那么array.arraylist更高效。数组支持所有跟可变序列有关的操作,包括.pop、.insert和.extend。另外,数组还提供从文件读取和存入文件的更快的方法,如.frombytes和.tofile

Python数组跟C语言数组一样精简。创建数组需要一个类型码,这个类型码用来表示在底层的C语言应该存放怎样的数据类型。比如b类型码代表的是有符号的字符(signed char),因此array('b')创建出的数组就只能存放一个字节大小的整数,范围从-128到127,这样在序列很大的时候,我们能节省很多空间。而且Python不会允许你在数组里存放除指定类型之外的数据

python
# 一个浮点数数组的创建、存入文件和从文件读取的过程
from array import array
from random import random
floats = array('d', (random() for i in range(10**7)))
floats[-1]
0.5963321947530882
python
fp = open('floats.bin', 'wb')
floats.tofile(fp) # 把数组存入一个二进制文件里
fp.close()
floats2 = array('d')
fp = open('floats.bin', 'rb') 
floats2.fromfile(fp, 10**7) # 从二进制文件里读取1000万个浮点数到数组里
fp.close()
floats2[-1]
0.5963321947530882
python
floats2 == floats # 两个数组相等
True

array.tofilearray.fromfile用起来很简单。把这段代码跑一跑,你还会发现它的速度也很快。

另外一个快速序列化数字类型的方法是使用pickle模块。pickle.dump处理浮点数组的速度几乎跟array.tofile一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。

从Python 3.4开始,数组类型不再支持诸如list.sort( )这种就地排序方法。要给数组排序的话,得用sorted函数新建一个数组

内存视图

memoryview是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片

内存视图其实是泛化和去数学化的NumPy数组。它让你在不需要复制内容的前提下,在数据结构之间共享内存。其中数据结构可以是任何形式,比如PIL图片、SQLite数据库和NumPy的数组,等等。这个功能在处理大型数据集合的时候非常重要。

memoryview.cast的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。这听上去又跟C语言中类型转换的概念差不多。memoryview.cast会把同一块内存里的内容打包成一个全新的memoryview对象给你。

python
# 通过改变数组中的一个字节来更新数组里某个元素的值
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
len(memv)
5
python
memv[0]
-2
python
memv_oct = memv.cast('B') # 把memv里的内容转换成'B'类型,也就是无符号字符
memv_oct.tolist()
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
python
memv_oct[5] = 4 # 把位于位置5的字节赋值成4
numbers
array('h', [-2, -1, 1024, 1, 2])

Numpy和SciPy

  • NumPy实现了多维同质数组(homogeneous array)和矩阵,这些数据结构不但能处理数字,还能存放其他由用户定义的记录。通过NumPy,用户能对这些数据结构里的元素进行高效的操作。
  • SciPy是基于NumPy的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。SciPy的高效和可靠性归功于其背后的C和Fortran代码,而这些跟计算有关的部分都源自于Netlib库。换句话说,SciPy把基于C和Fortran的工业级数学计算功能用交互式且高度抽象的Python包装起来,让科学家如鱼得水。
python
# 对numpy.ndarray的行和列进行基本操作
a = np.arange(12)
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
python
type(a)
numpy.ndarray
python
a.shape
(12,)
python
a.shape = 3, 4
a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
python
a[2]
array([ 8,  9, 10, 11])
python
a[2, 1]
9
python
a[:, 1] # 第二列
array([1, 5, 9])
python
a.transpose() # 转置
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

双向队列和其他形式的队列

利用.append.pop方法,我们可以把列表当作栈或者队列来用(比如,把.append.pop(0)合起来用,就能模拟队列的“先进先出”的特点)。但是删除列表的第一个元素(抑或是在第一个元素之前添加一个元素)之类的操作是很耗时的,因为这些操作会牵扯到移动列表里的所有元素。

collections.deque类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型。

python
from collections import deque
dq = deque(range(10), maxlen=10)
dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
python
dq.rotate(3) # 右移3位
dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
python
dq.rotate(-4) # 左移4位
dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
python
dq.appendleft(-1) # 左边加入-1
dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
python
dq.extend([11, 22, 33]) # 右边加入11, 22, 33
python
dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
python
dq.extendleft([10, 20, 30, 40]) # 左边加入10, 20, 30, 40
dq
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)

其他队列:

  • queue
  • multiprocessing
  • asyncio
  • heapq