流畅的python——2 数据结构
二、数据结构
容器序列和扁平序列;可变序列和不可变序列;
列表推导:通常的原则是,只用列表推导来创建新的列表,并且尽量保持简短。
Python 会忽略代码里 []、{} 和 () 中的换行,因此如果你的代码里有多行的列表、列表推导、生成器表达式、字典这一类的,可以省略不太好看的续行符 \。
python2 与 python3 的区别:
python2 中的列表推导等,能够影响全局的变量,即没有生成自己的局部作用域;python3 中的列表推导等,会生成自己局部作用域,不会影响全局中的变量。
笛卡儿积:两个或以上的列表中的元素对构成元组,这些元组构成的列表就是笛卡儿积。
InIn [60]: colors = ['白','黑色']
In [61]: sizes = ['1','2','3']
In [62]: tshirts = [(color,size) for color in colors for size in sizes]
相当于:
for color in colors:
for size in sizes:
pass
In [63]: tshirts
Out[63]: [('白', '1'), ('白', '2'), ('白', '3'), ('黑色', '1'), ('黑色', '2'), ('黑色', '3')]
In [64]: tshirts2 = [(color,size) for size in sizes for color in colors]
相当于:
for size in sizes:
for color in colors:
pass
In [65]: tshirts2
Out[65]: [('白', '1'), ('黑色', '1'), ('白', '2'), ('黑色', '2'), ('白', '3'), ('黑色', '3')]
# 列表推导是 浅拷贝
In [7]: a = [{'a':1}]
In [9]: b = [x for x in a]
In [10]: id(a)
Out[10]: 2592514208968
In [11]: id(b)
Out[11]: 2592514947656
In [13]: id(a[0])
Out[13]: 2592513860448
In [14]: id(b[0])
Out[14]: 2592513860448
列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场,生成列表以外的序列类型。
列表推导:得到完整的新列表。
生成器表达式:得到一个生成器,迭代取值。
In [67]: ll = [x for x in l]
In [68]: ll
Out[68]: [1, 2, 3]
In [69]: lll = (x for x in l)
In [70]: lll
Out[70]: <generator object <genexpr> at 0x000001E5B01A4830>
如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来。
元组与记录
元组不仅仅是不可变的列表,还是对数据的记录,正是元组中元素的数量和位置赋予了元组这个作用。
元组拆包:*
和 _
In [71]: a = (1,2)
In [72]: print('%s:%s'%a)
1:2
a, b = b, a
In [73]: a,b,*ab = range(10)
In [74]: a,b,*ab
Out[74]: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
In [75]: a,b,ab
Out[75]: (0, 1, [2, 3, 4, 5, 6, 7, 8, 9])
def t(a,b):
pass
args = (1,2)
t(*args)
嵌套元组拆包:
In [4]: 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)),
...: ]
In [5]: for city,country,pop,(x,y) in metro_areas: # 拆包的接收,元组
...: print(city,country,pop)
...: print(x,y)
...:
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
python2 与 python3 的区别
python2 支持元组作为函数的形参;python3 不支持。
元组作为记录,字段是没有名称的,具名元组 namedtuple 帮我们解决了这个问题。
collections.namedtuple
是一个工厂函数,用来构建具名元组。
用 namedtuple 构建的类的实例所消耗的内存跟元组是一样的,因为字段名都被存在对应的类里面。这个实例跟普通的对象实例比起来也要小一些,因为 Python不会用__dict__
来存放这些实例的属性。
关于 __dict__
,实例属性的__dict__是空的,因为season是属于类属性。
In [21]: b.__dict__
Out[21]: {}
In [22]: b.season = 'aaa'
In [23]: b.__dict__
Out[23]: {'season': 'aaa'}
定义一个具名元组:
from collections import namedtuple
City = namedtuple('City','a b c') # 类名 字段名(可以是字符串组成的可迭代对象,空格分隔)
# 可以通过字段名或位置获取字段信息,继承自普通元组
In [40]: A = namedtuple('A',['a','b','c'])
In [41]: a = A(1,2,(3,4))
In [42]: a
Out[42]: A(a=1, b=2, c=(3, 4))
In [43]: B = namedtuple('B','d e')
In [44]: b = B(5,6)
In [45]: b
Out[45]: B(d=5, e=6)
In [46]: a = (1,2,b)
In [47]: a
Out[47]: (1, 2, B(d=5, e=6))
In [48]: c = (1,2,B(7,8))
In [49]: c
Out[49]: (1, 2, B(d=7, e=8))
In [50]: a1 = A._make(c) # 通过元组生成元组,等同于 a1 = A(*c)
In [51]: a1
Out[51]: A(a=1, b=2, c=B(d=7, e=8))
In [54]: A._fields # 查看元组类的字段
Out[54]: ('a', 'b', 'c')
In [68]: a1._asdict() # 将具名元组以 collections.OrderedDict 的形式返回,注意普通元组没有这个方法
Out[68]: OrderedDict([('a', 1), ('b', 2), ('c', B(d=7, e=8))])
元组作为不可变列表
除了跟增减元素相关的方法外,元组支持列表的其他所有方法。
元组没有 __reversed__
方法,但是这个方法只是个优化而已,reversed(my_tuple) 这个用法在没有 __reversed__
的情况下也是合法的。
元组不可变。
切片
为什么切片的区间会忽略最后一个元素?
1 range(3) ,直接看出 3 个元素
2 区间长度:end - start 即可
3 分割序列类型:a[:x] 和 a[x:]
s[a:b:c]
取 a 到 b 区间的元素,步长为:c ;c 为负数,则反向取值
切片对象:slice(a,b,c)
命名切片使用,代码可读性
In [27]: 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
...: """
In [28]: line_invoice = invoice.split('\n')[2:]
In [29]: line_invoice
Out[29]:
['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',
'']
In [41]: PRICE = slice(39,45)
In [42]: DES = slice(6,39)
In [43]: ALL = slice(55,None) # 注意 None
In [44]: for line in line_invoice:
...: print(line[PRICE], line[DES])
...:
$17.50 imoroni PiBrella
$4.95 mm Tactile Switch x20
$28.00 anavise Jr. - PV-201
$34.95 iTFT Mini Kit 320x240
多维切片
numpy.ndarray 就用到了这种特性:a[m:n, k:l]
如果取 a[i,j]
,对象的特殊方法 __getitem__
和 __setitem__
以元组形式接受索引参数,会调用 a.__getitem__((i,j))
python 内的序列类型都是一维的,所以,只支持单一的索引,成对索引是不支持的。
省略:Ellipsis
对象的别名,Ellipsis
对象是 ellipsis
类的单一实例。三个英文句号,是一个符号,而不是半个省略号。
是的,你没看错,ellipsis 是类名,全小写,而它的内置实例写作 Ellipsis。这其实跟 bool 是小写,但是它的两个实例写作 True 和 False 异曲同工。
它可以当作切片规范的一部分,也可以用在函数的参数清单中,比如 f(a, ..., z),或 a[i:...]。在 NumPy中,... 用作多维数组切片的快捷方式。如果 x 是四维数组,那么 x[i, ...] 就是 x[i,:, :, :] 的缩写。((http://wiki.scipy.org/Tentative_NumPy_Tutorial)
切片:提取序列的元素,就地修改可变序列
In [57]: a
Out[57]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [58]: del a[5:6]
In [59]: a
Out[59]: [0, 1, 2, 3, 4, 6, 7, 8, 9]
# 如果赋值对象是一个切片,赋值语句右侧必须是一个可迭代对象。
In [60]: a[5:6] = 777
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-60-32fb461a460b> in <module>
----> 1 a[5:6] = 777
TypeError: can only assign an iterable
In [61]: a[5:6] = [777]
In [62]: a
Out[62]: [0, 1, 2, 3, 4, 777, 7, 8, 9]
# 字符串可迭代,插入元素
In [63]: a[5:6] = 'abc'
In [64]: a
Out[64]: [0, 1, 2, 3, 4, 'a', 'b', 'c', 7, 8, 9]
# 字符串是不可变序列
In [65]: b = 'abc'
In [66]: b[:1] = 'c'
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-66-6150d6fb47c9> in <module>
----> 1 b[:1] = 'c'
TypeError: 'str' object does not support item assignment
序列类型的 +
和 *
,不修改原有的操作对象,而是构建一个全新的序列。
In [67]: a = 'abc'
In [68]: a*3
Out[68]: 'abcabcabc'
In [69]: b = [1,2,3]
In [70]: b*2
Out[70]: [1, 2, 3, 1, 2, 3]
可变序列存储值为容器类型时,存的是地址,如果使用 *
,得到的是三个元素的地址相同,不太行。
In [87]: l = [[]]
In [88]: l1 = l * 3
In [89]: for i in l1:
...: print(id(i))
...:
2801684184264
2801684184264
2801684184264
# 发现地址相同
建立由列表组成的列表,使用列表推导避免重复的地址。
# 这里注意:'_' 是不可变类型,所以 *3 ,也没有什么关系。但是,*3 后得到的列表要用 for,因为 列表是可变类型
In [90]: board = [['_'] * 3 for i in range(3)]
In [91]: board
Out[91]: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
In [92]: board[1][2] = 'X'
In [93]: board
Out[93]: [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
错误案例:发现改了三个
In [97]: no_board
Out[97]: [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
In [98]: no_board[1][2] = 'no'
In [99]: no_board
Out[99]: [['_', '_', 'no'], ['_', '_', 'no'], ['_', '_', 'no']]
序列的增量赋值
例如:+=
就地加法。__iadd__
,如果类没有实现该方法,就会调用 __add__
。
如果实现了 __iadd__
,相当于 a.extend(b)
;如果没有实现 __iadd__
,相当于 a = a + b
,首先计算 a + b
,得到新的对象,赋值给 a。也就是说,变量名会不会关联到新的对象,完全取决于类有没有实现 __iadd__
方法。
注意:不可变序列也支持 +=
*=
等,只不过,前后的地址不同。因为,可变序列 实现了 __iadd__
方法,不可变序列肯定不支持变值,只支持变地址,即肯定不实现 __iadd__
方法。
# 不可变序列:地址发生变化
In [104]: a = '123'
In [105]: id(a)
Out[105]: 2801682448144
In [106]: a *= 3
In [107]: a
Out[107]: '123123123'
In [108]: id(a)
Out[108]: 2801682391280
# 可变序列:执行就地修改,地址不变
In [109]: b = [1,23]
In [110]: id(b)
Out[110]: 2801683440648
In [111]: b *= 2
In [112]: b
Out[112]: [1, 23, 1, 23]
In [113]: id(b)
Out[113]: 2801683440648
关于不可变序列的这类操作的性能
对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
str 是一个例外,因为对字符串做 += 实在是太普遍了,所以 CPython 对它做了优化。为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉及复制原有字符串到新位置这类操作。
示例 2-14 一个谜题
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
到底会发生下面 4 种情况中的哪一种?
a. t 变成 (1, 2, [30, 40, 50, 60])。
b. 因为 tuple 不支持对它的元素赋值,所以会抛出 TypeError 异常。
c. 以上两个都不是。
d. a 和 b 都是对的。
In [114]: t = (1, 2, [30, 40]) # 首先,这样赋值不会报错
In [120]: t
Out[120]: (1, 2, [30, 40])
In [121]: t[2] += [1,2]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-121-7b51edfebbe3> in <module>
----> 1 t[2] += [1,2]
TypeError: 'tuple' object does not support item assignment
In [122]: t
Out[122]: (1, 2, [30, 40, 1, 2])
答案是 d ,t[2] 被改动了,但是也有异常抛出
查看代码的字节码:
In [124]: import dis
In [125]: dis.dis('t[2] += [1,2]')
1 0 LOAD_NAME 0 (t)
2 LOAD_CONST 0 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR # 将 t[2] 存入TOS(Top OfStack,栈的顶端)
8 LOAD_CONST 1 (1)
10 LOAD_CONST 0 (2)
12 BUILD_LIST 2
14 INPLACE_ADD # TOS += [1,2] , 因为 TOS 是一个可变对象,所以可以。
16 ROT_THREE
18 STORE_SUBSCR # t[2] = TOS , 失败,因为 t 是不可变的元组
20 LOAD_CONST 2 (None)
22 RETURN_VALUE
这种情况非常罕见。
结论:
1 不要把可变对象放在元组里面。
2 增量赋值不是原子操作,虽然报错,但是完成了操作。
3 查看python字节码,对了解代码运行机制很有帮助。
序列类型的排序
list.sort
:就地排序。返回值为:None
返回None是一个python的一个惯例:如果一个函数或者方法对对象进行的是就地改动,那它就应该返回 None
,好让调用者知道传入的参数发生了变动,并且并未产生新的对象。
弊端:调用者无法将调用串联起来,反之,形成连贯接口(fluent interface)(https://en.wikipedia.org/wiki/Fluent_interface)
sorted
:新建一个列表作为返回值。可以接受可迭代对象作为参数,甚至包括不可变序列或生成器。而不管 sorted
接收什么参数,都会返回列表。
In [4]: c= [ 1,32,3,4]
In [5]: c.sort()
In [6]: c
Out[6]: [1, 3, 4, 32]
In [7]: c= [ 1,32,3,4]
In [8]: sorted(c)
Out[8]: [1, 3, 4, 32]
In [9]: c
Out[9]: [1, 32, 3, 4]
In [10]: a = '2483'
In [11]: a.sort() # 字符串不可变类型,不能就地修改。
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-11-2ed0d7de6146> in <module>
----> 1 a.sort()
AttributeError: 'str' object has no attribute 'sort'
In [12]: sorted(a) # 返回新的列表。
Out[12]: ['2', '3', '4', '8']
参数:
reverse
:设置为 True
,降序排序。默认为 False
In [12]: sorted(a)
Out[12]: ['2', '3', '4', '8']
In [13]: sorted(a,reverse=True)
Out[13]: ['8', '4', '3', '2']
In [14]: c.sort(reverse=True)
In [15]: c
Out[15]: [32, 4, 3, 1]
key
:一个只有一个参数的函数,会被调用在每个元素上,返回值:排序依赖的对比关键字。默认值为 恒等函数(identity function),默认用自己的值进行排序。
例如:
key = str.lower # 忽略大小写的排序
key = len # 根据长度排序
In [17]: a = ['a','B','c','D']
In [18]: a
Out[18]: ['a', 'B', 'c', 'D']
In [19]: a.sort()
In [20]: a
Out[20]: ['B', 'D', 'a', 'c']
In [21]: a.sort(key=lower) # no no no
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-21-e7cd9815514a> in <module>
----> 1 a.sort(key=lower)
NameError: name 'lower' is not defined
In [22]: a.sort(key=str.lower) # yes,字符串也是个类,调用类的方法。
In [23]: a
Out[23]: ['a', 'B', 'c', 'D']
In [24]: a.sort(key='a'.lower) # no no no ,对象的lower,不需要参数。
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-24-21bd131ee3f5> in <module>
----> 1 a.sort(key='a'.lower)
TypeError: lower() takes no arguments (1 given)
In [25]: 'a'
Out[25]: 'a'
In [26]: 'a'.lower
Out[26]: <function str.lower>
In [27]: str.lower
Out[27]: <method 'lower' of 'str' objects>
Python 的排序算法——Timsort——是稳定的,意思是就算两个元素比不出大小,在每次排序的结果里它们的相对位置是固定的。
In [28]: a
Out[28]: ['a', 'B', 'c', 'D']
In [29]: a.sort(key=len)
In [30]: a
Out[30]: ['a', 'B', 'c', 'D']
In [31]: a.sort(key=len,reverse=True) # 降序排序,因为长度一样,相对位置不变,算法稳定。
In [32]: a
Out[32]: ['a', 'B', 'c', 'D']
bisect
模块,二分查找模块
主要函数:bisect
和 insort
,都利用二分查找算法来在 有序序列 中 查找和插入 元素。lo
和 hi
表示搜索的范围。
bisect
:搜索
在有序序列中,查找插入元素的位置。返回 index。
index = bisect(l,'a') # 查找位置 index
l.insert(index,'a') # 使用 insert 插入元素
insort
:可以一步到位,速度更快
In [1]: import bisect
In [2]: a = [1,2,3,5,6,7]
In [3]: b = 4
In [4]: bisect.bisect(a,b)
Out[4]: 3
In [8]: a.insert(3,b)
In [9]: a
Out[9]: [1, 2, 3, 4, 5, 6, 7]
In [12]: bisect.insort(a,5)
In [13]: a
Out[13]: [1, 2, 3, 4, 5, 5, 6, 7]
分数等级:
In [2]: import bisect
In [6]: def grade(score,breakpoints=[60,70,80,90],grades='FDCBA'):
...: i = bisect.bisect(breakpoints,score)
...: return grades[i]
In [7]: grade(90)
Out[7]: 'A'
(https://docs.python.org/3/library/bisect.html)。文档里列举了一些利用 bisect 的函数,它们可以在很长的有序序列中作为 index 的替代,用来更快地查找一个元素的位置。
选择合适的数据类型,当列表不是首选时
当存储1000万个浮点数,应该选用 array
,标准数组
from array import array
from random import random
floats = array('d',(random() for i in range(10**7))) # d : 数组存储的数据类型,新建一个双精度浮点空数组
print(floats[-1])
fp = open('floats.bin','wb') # 存储到二进制文件中,读写更快,容量更小
floats.tofile(fp)
fp.close()
floats2 = array('d')
with open('floats.bin','rb') as f:
floats2.fromfile(f,10**7) # 读出存储的 1000万 个数
print(floats2[-1])
从上面的代码我们能得出结论,array.tofile 和 array.fromfile 用起来很简单。把这段代码跑一跑,你还会发现它的速度也很快。一个小试验告诉我,用 array.fromfile从一个二进制文件里读出 1000 万个双精度浮点数只需要 0.1 秒,这比从文本文件里读取的速度要快 60 倍,因为后者会使用内置的 float 方法把每一行文字转换成浮点数。另外,使用 array.tofile 写入到二进制文件,比以每行一个浮点数的方式把所有数字写入到文本文件要快 7 倍。另外,1000 万个这样的数在二进制文件里只占用 80 000 000 个字节(每个浮点数占用 8 个字节,不需要任何额外空间),如果是文本文件的话,我们需要 181 515 739 个字节。
另外一个快速序列化数字类型的方法是使用pickle(https://docs.python.org/3/library/pickle.html)模块。pickle.dump 处理浮点数组的速度几乎跟 array.tofile 一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。
array.typecode
In [26]: a
Out[26]: array('d', [1.1, 2.3, 4.5])
In [27]: a.typecode # 获取a的类型,一个字符的字符串,代表C语言中的类型
Out[27]: 'd'
In [28]: a
Out[28]: array('d', [1.1, 2.3, 4.5])
In [29]: a.tobytes()
Out[29]: b'\x9a\x99\x99\x99\x99\x99\xf1?ffffff\x02@\x00\x00\x00\x00\x00\x00\x12@'
memoryview
:内存视图,一个内置方法,可以共享内存,操作内存。
memoryview.cast
的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。这听上去又跟 C 语言中类型转换的概念差不多。memoryview.cast
会把同一块内存里的内容打包成一个全新的 memoryview 对象给你。
In [31]: nums = array('h',[-1,-3,2,3,5]) # 有符号短整型,占 2个字节
In [32]: mnums = memoryview(nums) # 得到一个 memoryview
In [35]: oct_mnums = mnums.cast('B') # 转为字符型表示的 memoryview,占 1个字节 ,所以,下面出现了10个
In [36]: oct_mnums
Out[36]: <memory at 0x00000288A0C06288>
In [38]: oct_mnums.tolist() # 以列表的形式查看内容
Out[38]: [255, 255, 253, 255, 2, 0, 3, 0, 5, 0] # 低位,高位
In [39]: oct_mnums[5] # [255, 255, 253, 255, 2, 1, 3, 0, 5, 0] : 2**8 + 2
Out[39]: 0
In [40]: oct_mnums[5] = 1
In [41]: nums
Out[41]: array('h', [-1, -3, 258, 3, 5]) # 2**8 + 2
In [42]: bin(2)
Out[42]: '0b10'
In [45]: 2**10
Out[45]: 1024
In [46]: 2**8
Out[46]: 256
NumPy
和 SciPy
,NumPy数组
In [48]: a = numpy.arange(12)
In [49]: a
Out[49]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
In [50]: type(a)
Out[50]: numpy.ndarray
In [51]: a.shape
Out[51]: (12,)
In [52]: a.shape = 3,4
In [53]: a
Out[53]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [54]: a[2]
Out[54]: array([ 8, 9, 10, 11])
In [55]: a[:,1]
Out[55]: array([1, 5, 9])
In [57]: a.transpose() # 行列互换
Out[57]:
array([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
>>> import numpy
>>> floats = numpy.loadtxt('floats-10M-lines.txt') # 从文本文件读
>>> floats[-3:]
array([ 3016362.69195522, 535281.10514262, 4566560.44373946])
>>> floats *= .5 # 每个元素都 * 0.5
>>> floats[-3:]
array([ 1508181.34597761, 267640.55257131, 2283280.22186973])
>>> from time import perf_counter as pc # 精度和性能都比较高的计时器, python3.3以及之后
>>> t0 = pc(); floats /= 3; pc() - t0
0.03690556302899495
>>> numpy.save('floats-10M', floats) # 存入 .npy 的二进制文件
>>> floats2 = numpy.load('floats-10M.npy', 'r+') # load 方法利用了一种叫作内存映射的机制,它让我们在内存不足的情况下仍然可以对数组做切片。
>>> floats2 *= 6
>>> floats2[-3:]
memmap([3016362.69195522, 535281.10514262, 4566560.44373946])
collections.deque
:双向队列,是一个线程安全,可以快速从两端添加或删除元素的数据类型。列表也可以当做队列使用,但是,列表在删除第一个元素和插入元素到第一个位置的时候,会移动所有其他元素。
In [67]: from collections import deque
In [68]: dq = deque(range(10),maxlen=10) # maxlen,一旦设定,不可修改
In [69]: dq
Out[69]: deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [70]: dq.rotate(3)
# 队列的旋转操作接受一个参数 n,当 n > 0 时,队列的最右边的 n 个元素会被移动到队列的左边。当 n < 0 时,最左边的 n 个元素会被移动到右边。
In [71]: dq
Out[71]: deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6])
In [72]: dq.rotate(-3)
In [73]: dq
Out[73]: deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [74]: dq.appendleft(-1) # 当队列满了,就删除另外一端的元素
In [75]: dq
Out[75]: deque([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8])
In [76]: dq.append(11)
In [77]: dq
Out[77]: deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 11])
In [78]: dq.extend([111,222,333])
In [79]: dq
Out[79]: deque([3, 4, 5, 6, 7, 8, 11, 111, 222, 333])
In [80]: dq.extendleft([0,0,0])
In [81]: dq
Out[81]: deque([0, 0, 0, 3, 4, 5, 6, 7, 8, 11])
In [82]: dq.extendleft([11,22,33]) # 遍历列表的每个元素,放到左边,所以,插入反序
In [83]: dq
Out[83]: deque([33, 22, 11, 0, 0, 0, 3, 4, 5, 6])
为了实现这些方法,双向队列也付出了一些代价,从队列中间删除元素的操作会慢一些,因为它只对在头尾的操作进行了优化。
append 和 popleft 都是原子操作,也就说是 deque 可以在多线程程序中安全地当作先进先出的栈使用,而使用者不需要担心资源锁的问题。
queue 队列
提供了同步(线程安全)类 Queue、LifoQueue 和 PriorityQueue,不同的线程可以利用这些数据类型来交换信息。这三个类的构造方法都有一个可选参数 maxsize,它接收正整数作为输入值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素来腾出位置。相反,如果队列满了,它就会被锁住,直到另外的线程移除了某个元素而腾出了位置。这一特性让这些类很适合用来控制活跃线程的数量。
multiprocessing
这个包实现了自己的 Queue,它跟 queue.Queue 类似,是设计给进程间通信用的。同时还有一个专门的 multiprocessing.JoinableQueue 类型,可以让任务管理变得更方便。
asyncio
Python 3.4 新提供的包,里面有 Queue、LifoQueue、PriorityQueue 和JoinableQueue,这些类受到queue 和 multiprocessing 模块的影响,但是为异步编程里的任务管理提供了专门的便利
heapq
跟上面三个模块不同的是,heapq 没有队列类,而是提供了 heappush 和 heappop方法,让用户可以把可变序列当作堆队列或者优先队列来使用。
我们之所以用列表来存放东西,是期待在稍后使用它的时候,其中的元素有一些通用的特性(比如,列表里存的是一类可以“呱呱”叫的动物,那么所有的元素都应该会发出这种叫声,即便其中一部分元素类型并不是鸭子)。
在Python 3 中,如果列表里的东西不能比较大小,那么我们就不能对列表进行排序:
>>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19]
>>> sorted(l)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() < int()
元组则恰恰相反,它经常用来存放不同类型的的元素。这也符合它的本质,元组就是用作存放彼此之间没有关系的数据的记录。
key 参数
list.sort、sorted、max 和 min 函数的 key 参数是一个很棒的设计。其他语言里的排序函数需要用户提供一个接收两个参数的比较函数作为参数,像是 Python 2 里的cmp(a, b)。用 key 参数能把事情变得简单且高效。说它更简单,是因为只需要提供一个单参数函数来提取或者计算一个值作为比较大小的标准即可,而 Python 2 的这种设计则需要用户写一个返回值是—1、0 或者 1 的双参数函数。说它更高效,是因为在每个元素上,key 函数只会被调用一次。而双参数比较函数则在每一次两两比较的时候都会被调用。诚然,在排序的时候,Python 总会比较两个键(key),但是那一阶段的计算会发生在 C 语言那一层,这样会比调用用户自定义的 Python 比较函数更快。
另外,key 参数也能让你对一个混有数字字符和数值的列表进行排序。你只需要决定到底是把字符看作数值,还是把数值看作字符:
>>> l = [28, 14, '28', 5, '9', '1', 0, 6, '23', 19]
>>> sorted(l, key=int)
[0, '1', 5, 6, '9', 14, 19, '23', 28, '28']
>>> sorted(l, key=str)
[0, '1', 14, 19, '23', 28, '28', 5, 6, '9']