容器序列存放的是它们所包含的任意类型的对象的引用, 包括list, tuple, collections.deque等.
扁平序列存放的是值而不是引用, 是一段连续的内存空间, 包括str, bytes, bytearray, memoryview, array.array等.
可变序列: list, bytearray, array.array, collections.deque, memoryview
不可变序列: tuple, str, bytes
2.2 列表推导和生成器表达式 2.2.1 列表推导和可读性列表推导是构建列表的快捷方式.
symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
codes.append(ord(symbol))
codes
>>> [36, 162, 163, 165, 8364, 164]
# use list comp symbols = '$¢£¥€¤' codes = [ord(symbol) for symbol in symbols] codes >>> [36, 162, 163, 165, 8364, 164]
通常的原则是, 只用列表推导来创建新的列表, 并且尽量保持简短.
2.2.2 列表推导同filter和map比较# compare list comp and filter/map symbols = '$¢£¥€¤' beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] beyond_ascii >>> [162, 163, 165, 8364, 164]
symbols = '$¢£¥€¤' beyond_ascii = list(filter(lambda c: c>127, map(ord, symbols))) beyond_ascii >>> [162, 163, 165, 8364, 164]
map/filter组合不一定比列表推导要快.
2.2.3 笛卡尔积由列表推导可以生成两个或以上的可迭代类型的笛卡尔积.
# Cartesian product
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')]
tshirts = [(color, size) for size in sizes for color in colors]
tshirts
>>> [('black', 'S'),
('white', 'S'),
('black', 'M'),
('white', 'M'),
('black', 'L'),
('white', 'L')]
如果想要生成其它类型的序列, 可以使用生成器表达式.
2.2.4 生成器表达式生成器表达式背后遵守了迭代器协议, 可以逐个地产出元素, 而不是先建立一个完整的列表, 然后再把这个列表传递到某个构造函数里.
生成器表达式的语法和列表推导差不多, 只不过把方括号换成了圆括号.
# generator symbols = '$¢£¥€¤' tuple(ord(symbol) for symbol in symbols) >>> (36, 162, 163, 165, 8364, 164)
这里生成器表达式是函数调用过程中的唯一参数, 因此不需要额外的圆括号.
import array
array.array('I', (ord(symbol) for symbol in symbols))
>>> array('I', [36, 162, 163, 165, 8364, 164])
这个array的构造方法需要两个参数, 第一个参数指定了数组中数字的存储方式, 因此需要添加圆括号.
使用生成器表达式计算笛卡尔积
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in ('%s %s' % (c,s) for c in colors for s in sizes):
print(tshirt)
>>> black S
black M
black L
white S
white M
white L
2.3 元组不仅仅是不可变的列表
2.3.1 元组和记录
元组其实是对数据的记录, 元组的每个元素都存放了记录中一个字段的数据, 外加这个字段的位置.
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
for country, _ in traveler_ids:
print(country)
USA
BRA
ESP
最好辨认的元组拆包形式是平行赋值, 如
latitude, longitude = lax_coordinates latitude
33.9425
longitude
-118.408056
*运算符把一个可迭代对象拆开作为函数的参数
t = (20, 8) divmod(*t)
(2, 4)
quotient, remainder = divmod(*t) quotient, remainder
(2, 4)
可以用_占位符处理不感兴趣的数据
import os
_, filename = os.path.split('home/luciano/.ssh/idrsa.pub')
filename
‘idrsa.pub’
用*来处理剩下的数据, 可以放在任意位置
# use of * a, b, * rest = range(5) a, b, rest
(0, 1, [2, 3, 4])
a, *body, c, d = range(5) a, body, c, d
(0, [1, 2], 3, 4)
*head, b, c, d = range(5) head, b, c, d
([0, 1], 2, 3, 4)
2.3.3 嵌套元素拆包接受表达式的元组可以是嵌套式的, 只要这个接受元组的嵌套结构符合表达式本身的嵌套结构, 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}'
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.63582.3.4 具名元组
collections.namedtuple 可以用来构建一个带字段名的元组和一个有名字的类.
例:定义和使用具名元组
from collections import namedtuple
City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
tokyo
City(name=‘Tokyo’, country=‘JP’, population=36.933, coordinates=(35.689722, 139.691667))
创建一个具名元组需要两个参数, 一个是类名, 另一个是类的各个字段的名字. 后者可以是由数个字符串构成的可迭代对象, 或以空格分割开的字段名组成的字符串.
可以通过字段名或者位置来获取一个字段的信息.
tokyo.population
36.933
tokyo.coordinates
(35.689722, 139.691667)
tokyo[1]
‘JP’
具名元组还有些自己专有的属性, 比如__fields__, _make(iterable),_asdict()
City._fields
(‘name’, ‘country’, ‘population’, ‘coordinates’)
返回这个类所有字段名称的元组
LatLong = namedtuple('LatLong', 'lat long')
delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data)
delhi._asdict()
{‘name’: ‘Delhi NCR’,
‘country’: ‘IN’,
‘population’: 21.935,
‘coordinates’: LatLong(lat=28.613889, long=77.208889)}
for key, value in delhi._asdict().items():
print(key + ':' , value)
name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)
_make()接受一个可迭代对象来生成类的一个实例, _asdict()把具名元组以collections.OrderedDict的形式返回.
2.3.5 作为不可变列表的元组元组和列表的方法和属性中, 除了增减元素相关的方法外, 元组支持列表的其他所有方法.
2.4 切片 2.4.1 为什么切片和区间会忽略最后一个元素- 当只有最后一个位置信息时, 我们也可以快速看出切片和区间里有几个元素
- 当起止位置信息都可见时, 我们可以快速计算出切片和区间的长度
- 可以利用任意一个下标把序列分割成不重叠的两部分, my_list[:x]和my_list[x:]
我们可以用s[a:b:c] 的形式对s在a和b之间以c为间隔取值, c为负意味着反向取值.
a:b:c这种用法只能作为索引或者下标用在[]中来返回一个切片对象: slice(a, b, c). 可以给切片命名, 如
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 imoroni PiBrella
$4.95 mm Tactile Switch x20
$28.00 anavise Jr. - PV-201
$34.95 iTFT Mini Kit 320x240
2.4.3 多维切片和省略
[]运算符还可以使用以逗号分开的多个索引或者是切片, numpy.ndarray就用到了这个特性.
要正确处理这种[]运算符, 对象的__getitem__和__setitem__需要以元组的形式接收a[i, j]的值, python会调用a.__getitem__((i, j)).
省略的正确书写方法是三个英语句号(...). 他可以作为切片规范的一部分, 也可以用在函数的清单参数中. 在numpy中, 如果x是四维数组, 那么x[i, ...]就是x[i, :, :, :]的缩写.
2.4.4 给切片赋值把切片放在赋值语句左边, 或作为del的对象, 可以对序列进行嫁接, 切除或就地修改的操作.
l = list(range(10)) l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[2:5] = [20, 30] l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
del l[5:7] l
[0, 1, 20, 30, 5, 8, 9]
l[3::2] = [11, 22] l
[0, 1, 20, 11, 5, 22, 9]
l[2:5] = 100
TypeError: can only assign an iterable
对切片赋值时, 右侧必须是个可迭代对象
l[2:5] = [100] l
[0, 1, 100, 22, 9]
2.5 对序列使用+和*在拼接的过程中, 两个被操作的序列都不会被修改, Python会新建一个包含同样类型数据的序列来作为拼接的结果.
如果要将一个序列复制几份再拼接起来, 可以使用*.
l = [1, 2, 3] l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
+和*都遵循这个规律, 不修改原有的操作对象.
建立由列表组成的列表.
board = [['_'] * 3 for i in range(3)] board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = 'X' board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
如果列表中包含的是同一对象的引用, 则是错误的创建.
# wrong example weird_board = [['_'] * 3] * 3 weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
weird_board[1][2] = 'X' weird_board
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]2.6 序列的增量赋值
+=和*=的表现取决于他们的第一个操作对象. +=背后的特殊方法是__iadd__, 如果没有实现__iadd__的话, a+=b这个表达式的效果就变得跟a=a+b一样.
l = [1, 2, 3] id(l)
1949033126720
l *= 2 l
[1, 2, 3, 1, 2, 3]
id(l)
1949033126720
可变序列ID不变.
t = (1, 2, 3) print(id(t)) t *= 2 print(id(t))
1949033056256
1949033389120
不可变序列会创建新的对象.
一个关于+=的谜题t = (1, 2, [30, 40]) t[2] += [50, 60] print(t)
运行这段程序会报错
TypeError: ‘tuple’ object does not support item assignment
但是再对其输出
print(t)
(1, 2, [30, 40, 50, 60])
发现t还是被改变了.
- 不要把可变对象放在元组里面
- 增量赋值不是一个原子操作
- 查看Python的字节码对了解代码背后的运行机制很有帮助
list.sort会就地排序列表, 因此其返回值是None.
与list.sort相反的是sorted,它会新建一个列表作为返回值, 可以接受任何形式的可迭代对象作为参数.
list.sort和sorted都有两个参数:
reverse:True则降序输出, 默认为False, 即升序
key: 一个函数, 它会被作用在每个元素上, 返回值用于比较大小.
fruits = ['grape', 'raspberry', 'apple', 'banana'] print(sorted(fruits)) print(fruits) print(sorted(fruits, reverse=True)) print(sorted(fruits, key=len)) print(sorted(fruits, key=len, reverse=True)) print(fruits) fruits.sort() print(fruits)
[‘apple’, ‘banana’, ‘grape’, ‘raspberry’]
[‘grape’, ‘raspberry’, ‘apple’, ‘banana’]
[‘raspberry’, ‘grape’, ‘banana’, ‘apple’]
[‘grape’, ‘apple’, ‘banana’, ‘raspberry’]
[‘raspberry’, ‘banana’, ‘grape’, ‘apple’]
[‘grape’, ‘raspberry’, ‘apple’, ‘banana’]
[‘apple’, ‘banana’, ‘grape’, ‘raspberry’]
上述例子也可看出, list.sort会直接改变原列表, sorted 则不会.
2.8 用bisect来管理已排序的序列bisect模块包含两个主要函数, bisect和insort, 它们利用二分查找来查找或插入.
2.8.1 用bisect来搜索bisect(haystack, needle)在haystack中搜索needle的位置.
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_left haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30 31 @ 14 | | | | | | | | | | | | | |31 30 @ 13 | | | | | | | | | | | | |30 29 @ 12 | | | | | | | | | | | |29 23 @ 9 | | | | | | | | |23 22 @ 9 | | | | | | | | |22 10 @ 5 | | | | |10 8 @ 4 | | | |8 5 @ 2 | |5 2 @ 1 |2 1 @ 0 1 0 @ 0 0
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_left和bisect_right 的查找位置.
例: 根据一个分数, 找到对应的成绩.
import bisect
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
[‘F’, ‘A’, ‘C’, ‘C’, ‘B’, ‘A’, ‘A’]
2.8.2 用bisect.insort插入新元素
insort(seq, item)将变量item插入seq中, 并能保持seq的升序排列.
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]
如果需要一个只包含数字的列表, 那么array.array比list更高效.
from array import array
from random import random
floats = array('d', (random() for i in range(10 ** 7)))
print(floats[-1])
fp = open('floats.bin', 'wb')
floats.tofile(fp)
fp.close()
floats2 = array('d')
fp = open('floats.bin', 'rb')
floats2.fromfile(fp, 10 ** 7)
fp.close()
print(floats2[-1])
print(floats2 == floats)
0.5963321947530882
0.5963321947530882
True
tofile可以将array存入到一个二进制文件, fromfile可以从二进制文件中读取.
array类型不支持如list.sort()这种就地排序方法.
2.9.2 内存视图memoryview能让用户在不复制内容的情况下操作同一个数组的不同切片.
memoryview.cast的概念跟数组模块类似, 能用不同的方式读写同一块内存数据, 而且内容字节不会随意移动. memoryview.cast会把同一块内存里的内容打包成一个全新的memoryview对象.
# memoryview
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
print(len(memv))
print(memv[0])
memv_oct = memv.cast('B')
print(memv_oct.tolist())
memv_oct[5] = 4
print(numbers)
5
-2
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array(‘h’, [-2, -1, 1024, 1, 2])
这里h表示短整型有符号整数, B表示无符号字符.
2.9.3 numpy和scipy# numpy import numpy a = numpy.arange(12) print(a) print(type(a)) print(a.shape) a.shape = 3, 4 print(a) print(a[2]) print(a[2, 1]) print(a[:, 1]) print(a.transpose())
[ 0 1 2 3 4 5 6 7 8 9 10 11]2.9.4 双向队列和其他形式的队列(12,) [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]] [ 8 9 10 11] 9 [1 5 9] [[ 0 4 8] [ 1 5 9] [ 2 6 10] [ 3 7 11]]
collections.deque是一个线程安全, 可以快速从两端添加或者删除元素的数据类型.
# deque from collections import deque dq = deque(range(10), maxlen=10) print(dq) dq.rotate(3) print(dq) dq.rotate(-4) print(dq) dq.appendleft(-1) print(dq) dq.extend([11, 22, 33]) print(dq) dq.extendleft([10, 20, 30, 40]) print(dq)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
maxlen确定了队列的最大长度.
rotate会将队列的所有数字向右移动, 若为负, 则左移.
appendleft会将数字添加到队列头部, 若超过最大长度, 则删除队列尾部的相应数量的数字.
extend会将列表中的三个数字依次添加到尾部, 并删掉头部的多余数字.
extendleft会添加到头部, 也是依次添加, 所以顺序是逆序的.



