序列构成的数组
列表推导式
别表推导式是建立列表(list)的快捷方式 ,列表推导可以帮助我们把一个序列或者是其他可迭代类型中的元素过滤掉或是加工,然后再新建一个列表。
输入
tshirts = [(color,size) for size in sizes for color in colors]
tshirts
输出
[('black', 'S'),
('white', 'S'),
('black', 'M'),
('white', 'M'),
('black', 'L'),
('white', 'L')]
生成器表达式
生成器表达式背后遵守了迭代器协议,可以逐个产出元素,而不是先建立有一个完整的列表,然后再把逐个列表传递给某个构造函数。
生成器表达式的语法跟列表推导式差不多,只不过把方括号换成圆括号
输入
#用生成器初始化元组和数组
symbols = 'arihofwo'
tuple(ord(symbol) for symbol in symbols)
输出
(97, 114, 105, 104, 111, 102, 119, 111)
如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来
输入
#使用生成器表达式计算笛卡儿积
colors = ['black','white']
sizes = ['s','m','l']
for tshirt in ('%s%s'%(c,s) for c in colors for s in sizes):
print(tshirt)
输出
blacks
blackm
blackl
whites
whitem
whitel
输入
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
嵌套元组拆包
接受表达式的元组可以是嵌套的,例如(a,b,(c,d))
输入
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))
➊ 每个元组内有4个元素,其中最后一个元素是一对坐标。
➋ 我们把输入元组的最后一个元素拆包到由变量构成的元组里,这样就获取了坐标。
➌ if longitude <= 0:这个条件判断把输出限制在西半球的城市。
输出
| lat. | long.
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
Sao Paulo | -23.5478 | -46.6358
具名元组
collections.nametuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类–这个带名字的类对调试程序有很大帮助
输入
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[1]
输出
'JP'
①创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是有数个字符组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。
②存放在对应字段里的数据要以一串参数的形式传入到构造函数中,(元组的构造函数却只接受的单一的可迭代对象。)
③可通过字段名或者位置来获取一个字段的信息。
具名元组的属性和方法
>>> 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) #➋用_make()通过接受一个可迭代的对象来生成这个类的一个实例,它的作用跟City(*delhi_data)是一样的
>>>delhi._asdict() #➌
OrderedDict([('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)
>>>
➊ _fields属性是一个包含这个类所有字段名称的元组。
➋ 用_make()通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟City(*delhi_data)是一样的。
➌ _asdict()把具名元组以collections.OrderedDict的形式返回,我们可以利用它来把元组里的信息友好地呈现出来。
现在我们知道了,元组是一种很强大的可以当作记录来用的数据类型。它的第二个角色则是充当一个不可变的列表。下面就来看看它的第二重功能。
作为不可变列表的元组
列表或元组的方法和属性(那些由object类支持的方法没有列出来)
切片
我们还可以用s[a : b : c]的形式对s在a和b之间以c为间隔取 值。c的值还可以为负,负值意味着反向取值。
>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'
给切片赋值
>>> 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 #➊
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
➊ 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单独一个值,也要把它转换成可迭代的序列。
对序列使用+和*
Python程序员会默认序列是支持+和*操作的。通常+号两侧的序列由相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被修改,Python会新建一个包含同样类型数据的序列来作为拼接的结果。
>>> l = [1, 2, 3]
>>> l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'
+和*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列。
建立由列表组成的列表
#一个包含3个列表的列表,嵌套的3个列表各自有3个元素来代表井字游戏的一行方块
>>> board = [['_'] * 3 for i in range(3)] #➊
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X' #➋
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
➊ 建立一个包含3个列表的列表,被包含的3个列表各自有3个元素。打印出这个嵌套列表。
➋ 把第1行第2列的元素标记为X,再打印出这个列表。
序列的增量赋值
#展示的是*=在可变和不可变序列上的作用
>>> l = [1, 2, 3]
>>> id(l)
4311953800 #➊
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800 #➋
>>> t = (1, 2, 3)
>>> id(t)
4312681568 #➌
>>> t *= 2
>>> id(t)
4301348296 #➍
➊ 刚开始时列表的ID。
➋ 运用增量乘法后,列表的ID没变,新元素追加到列表上。
➌ 元组最开始的ID。
➍ 运用增量乘法后,新的元组被创建。
#一个谜题
>>> 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都是对的。
我刚看到这个问题的时候,异常确定地选择了b,但其实答案是d,也就是说a和b都是
>>> 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的初始和最终状态如下图
list.sort方法和内置函数sorted
list.sort方法会就地排序列表,也就是说不会把原列表复制一份。这也是这个方法的返回值是None的原因,提醒你本方法不会新建一个列表。
与list.sort相反的是内置函数sorted,它会新建一个列表作为返回值。这个方法可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器,而不管sorted接受的是怎样的参数,它最后都会返回一个列表。
>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry'] #➊
>>> fruits
['grape', 'raspberry', 'apple', 'banana'] #➋
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple'] #➌
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry'] #➍
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple'] #➎
>>> fruits
['grape', 'raspberry', 'apple', 'banana'] #➏
>>> fruits.sort() #➐
>>> fruits
['apple', 'banana', 'grape', 'raspberry'] #➑
➊ 新建了一个按照字母排序的字符串列表。
➋ 原列表并没有变化。
➌ 按照字母降序排序。
➍ 新建一个按照长度排序的字符串列表。因为这个排序算法是稳定的,grape和apple的长度都是5,它们的相对位置跟在原来的列表里是一样的。
➎ 按照长度降序排序的结果。结果并不是上面那个结果的完全翻转,因为用到的排序算法是稳定的,也就是说在长度一样时,grape和apple的相对位置不会改变。
➏ 直到这一步,原列表fruits都没有任何变化。
➐ 对原列表就地排序,返回值None会被控制台忽略。
➑ 此时fruits本身被排序。
已排序的序列可以用来进行快速搜索,而标准库的bisect模块给我们提供了二分查找算法。
用bisect来管理已排序的序列
bisect模块包含两个主要函数,bisect和insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素。
用bisect来搜索
#在有序序列中用bisect查找某个元素的插入位置
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
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函数。
➎ 把选定的函数在抬头打印出来。
上图为代码的输出,每一行以needle @ pasition(元素及其应该插入的位置)开始,然后展示了该元素在原序列中的物理位置。
用bisect.insort插入新元素
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]
insort跟bisect一样,有lo和hi两个可选参数用来控制查找的范围。它也有个变体叫insort_left,这个变体在背后用的是bisect_left。
当列表不是首选时
NumPy和SciPy
#对numpy.ndarray的行和列进行基本操作
>>> import numpy #➊
>>> a = numpy.arange(12) #➋
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.shape #➌
(12,)
>>> a.shape = 3, 4 #➍
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[2] #➎
array([ 8, 9, 10, 11])
>>> a[2, 1] #➏
9
>>> a[:, 1] #➐
array([1, 5, 9])
>>> a.transpose() #➑
array([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
➊ 安装NumPy之后,导入它(NumPy并不是Python标准库的一部分)。
➋ 新建一个0~11的整数的numpy.ndarry,然后把它打印出来。
➌ 看看数组的维度,它是一个一维的、有12个元素的数组。
➍ 把数组变成二维的,然后把它打印出来看看。
➎ 打印出第2行。
➏ 打印第2行第1列的元素。
➐ 把第1列打印出来。
➑ 把行和列交换,就得到了一个新数组。
双向队列和其他形式的队列
利用.append和.pop方法,我们可以把列表当作栈或者队列来用(比如,把.append和.pop(0)合起来用,就能模拟栈的“先进先出”的特点)。
#使用双端队列
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10) #➊
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.rotate(3) #➋
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
>>> dq.rotate(-4)
>>> dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
>>> dq.appendleft(-1) #➌
>>> dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.extend([11, 22, 33]) #➍
>>> dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
>>> dq.extendleft([10, 20, 30, 40]) #➎
>>> dq
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
➊ maxlen是一个可选参数,代表这个队列可以容纳的元素的数量,而且一旦设定,这个属性就不能修改了。
➋ 队列的旋转操作接受一个参数n,当n > 0时,队列的最右边的n个元素会被移动到队列的左边。当n < 0时,最左边的n个元素会被移动到右边。
➌ 当试图对一个已满(len(d) == d.maxlen)的队列做尾部添加操作的时候,它头部的元素会被删除掉。注意在下一行里,元素0被删除了。
➍ 在尾部添加3个元素的操作会挤掉-1、1和2。
➎ extendleft(iter)方法会把迭代器里的元素逐个添加到双向队列的左边,因此迭代器里的元素会逆序出现在队列里。