内置序列类型概览
Python 标准基于 C 实现了丰富的序列类型:
- 容器序列:
list
、tuple
、collections.deque
。这些序列可以存放不同类型的数据。 - 扁平序列:
str
、bytes
、bytearray
、memoryview
、array.arry
。这些序列只能存放一种数据类型。
容器序列存放的是它们所包含的任意类型的对象的引用,扁平序列里存放的是值而不是引用。扁平序列其实本质上就是一段连续的内存空间,但是只能存放诸如字符、字节、数值等基础类型。
序列容器按照是否能被修改分类:
- 可变序列:
list
、bytearry
、array.array
、collections.deque
、memoryview
- 不可变序列:
tuple
、str
、bytes
类继承关系:
列表推导和生成器表达式
列表推导是构建 list
的快捷方式,生成器表达式则可以用来高效的创建任何类型的序列。
列表推导
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def str_to_unicode(symbols):
codes = []
for symbol in symbols:
codes.append(ord(symbol))
return codes
# 使用列表推导
def str_to_unicode_ex(symbols):
return [ord(symbol) for symbol in symbols]
if __name__ == "__main__":
symbols = "$&#@*"
print(str_to_unicode(symbols))
print(str_to_unicode_ex(symbols))
列表推导与 filter
和 map
的比较
filter
和 map
合起来能做的事情,列表推导也可以做,而且更加简洁直观:
symbols = '$¢£¥€¤'
beyond_ascii_1 = [ord(s) for s in symbols if ord(s) > 127]
beyond_ascii_2 = list(filter(lambda c : c > 127,map(ord,symbols)))
print(beyond_ascii_1)
print(beyond_ascii_2)
生成器表达式
虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。
生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。
symbols = '$¢£¥€¤'
tp = tuple(ord(symbol) for symbol in symbols)
print(tp)
import array
arr = array.array('I',(ord(symbol) for symbol in symbols))
print(arr)
- 如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来
array
的构造方法需要两个参数,因此括号是必需的。array
构造方法的第一个参数指定了数组中数字的存储方式
元组不仅仅是不可变的列表
元组和记录
元组除了用作不可变的列表,它还可以用于没有字段名的记录。
元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
def record():
lax_coordinates = (33.9425,-118.4008065)
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)
for country,_ in traveler_ids:
print(country)
record()
元组拆包
city,year,pop,chg,area = ('Tokyo', 2003, 32450, 0.66, 8014)
这里将元素分别赋值给变量所使用的技术就是拆包。
元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是:被可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。除非我们用 *
来表示忽略多余的元素,如果拆包时对某个数据不感兴趣,可以使用 _
占位。
拆包的一个优雅的应用:不使用中间变量交换两个变量的值:
a,b = b,a
还可以用 *
将一个可迭代对象拆分作为函数的参数:
print(divmod(20,8))
t = (20,8)
# 参数拆包
print(divmod(*t))
使用 _
展位:
import os
_,filename = os.path.split("/e/")
使用 *
处理剩下的元素:
a,b,*rest = range(5)
print(a,b,rest)
a,*rest,b = range(5)
print(a,rest,b)
嵌套元组拆包
接受表达式的元组可以是嵌套式的,例如: (a, b, (c, d))
。只要这个接受元组的嵌套结构符合表达式本身的嵌套结构,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, _, _, (latitude, longitude) in metro_areas: # 嵌套解包
if longitude <= 0: # 西半球
print(fmt.format(name, latitude, longitude))
具名元组
collections.namedtuple
是一个工厂函数,可以用来构建一个带字段名的元组和一个有名字的类。
from collections import namedtuple
# 使用空格区分各个字段
City = namedtuple('City',' name country population coordinates')
tokyo = City('Tokyo','JP',36.933,(36.689722,139.691667))
print(tokyo)
print(tokyo.population) # 通过字段的形式获取信息
print(tokyo[1]) # 通过索引的形式获取信息
# 属性和方法
print(City._fields) # _fields 包含这个类所有字段名称的元组
LatLong = namedtuple('LatLong', 'lat long')
delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data) # _make 接受可迭代对象创建一个 namedtuple
print(delhi._asdict) # _asdict 把具名元组以 collections.OrderedDict 的形式返回
作为不可变列表的元组
除了跟增减元素相关的方法之外,元组支持列表的其他所有方法。还有一个例外,元组没有 __reversed__
方法.
切片
列表(list
)、元组(tuple
)和字符串(str
)这类序列类型都支持切片操作,
切片和区间忽略最后一个元素
在切片和区间操作里不包含区间范围的最后一个元素是 python 的风格,这个符合下标从 0 开始的传统:
- 当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元素:
range(3)
和my_list[:3]
都返回 3 个元素 - 当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,
(stop - start)
即可 - 以利用任意一个下标来把序列分割成不重叠的两部分:
my_list[:x]
和my_list[x:]
对对象进行切片
可以用 s[a:b:c]
的形式对 s
在 a
和 b
之间以 c
为间隔取值。 c
的值还可以为负,负值意味着反向取值:
s = "bicycle"
print(s[::3])
print(s[::-1])
print(s[::-2])
a:b:c
这种用法只能作为索引或者下标用在 []
中来返回一个切片对象: slice(a, b, c)
。
对 seq[start:stop:step]
进 行 求 值 的 时 候,python 会调用 seq.__getitem__(slice(start, stop, step))
。
多维切片和省略
[]
运算符里还可以使用以逗号分开的多个索引或者是切片,二维的 numpy.ndarray
就可以用 a[i, j]
这种形式来获取,抑或是用 a[m:n, k:l]
的方式来得到二维切片。
省略的正确书写方法是 ...
。比如 f(a, ..., z)
,或 a[i:...]
。在 NumPy 中,...
用作多维数组切片的快捷方式。如果 x
是四维数组,那么 x[i, ...]
就是 x[i, :, :, :]
的缩写。
给切片赋值
如果把切片放在赋值语句的左边,或把它作为 del 操作的对象,我们就可以对序列进行嫁接、切除或就地修改操作
l = list(range(10))
print(l)
l[2:5] = [20,30]
print(l)
del l[5:7]
print(l)
l[3::2] = [11,22]
print(l)
对序列使用 +
和 *
通常 +
号两侧的序列由相同类型的数据所构成,在拼接的过程中两个被操作的序列都不会被修改,python 会新建一个包含同样类型数据的序列来作为拼接的结果。
如果想要把一个序列复制几份然后再拼接起来,更快捷的做法是把这个序列乘以一个整数:
l = [1,2,3]
print(l*5)
print(5 * "abc")
建立由列表组成的列表
board = [['_'] *3 for i in range(3)]
print(board)
board[1][2] = "X"
print(board)
含有指向同一对象的引用的列表是毫无用处的:
wired_board = [['_'] *3] * 3 # 外面的列表包含了 3 个指向同一个引用的列表
print(wired_board)
wired_board[1][2] = "Y"
print(wired_board)
上面两种情况的差异,可以理解为:
# 错误的示范
row=['_'] * 3
board = []
for i in range(3):
board.append(row) # 同一个对象追加三次
board = []
for i in range(3):
row['_'] * 3 # 每次迭代新建一个列表
board.append(row)
序列的增量赋值
增量赋值运算符 +=
和 *=
的表现取决于它们的第一个操作对象。+=
背后的特殊方法是 __iadd__
(用于"就地加法")。但是如果一个类没有实现这个方法的话,python 会退一步调用 __add__
,此时 a += b
这个表达式的效果就变得跟 a = a + b
一样。
l = [1,2,3]
print(id(l))
l *= 2
print(id(l)) # id 没有改变,新的元素被追加到列表中
t = (1,2,3)
print(id(t))
t *= 2 # 创建新的元组
print(id(t))
对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后再追加新的元素。
考虑下面的例子:
t = (1,2,[30,40])
t[2] += [50,60]
借助 Python Tutor 查看执行过程:
s[a] += b
字节码:
dis.dis('s[a]+=b')
1 0 LOAD_NAME 0 (s)
2 LOAD_NAME 1 (a)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_NAME 2 (b)
10 INPLACE_ADD
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
- 不要把可变对象放在元组里面
- 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作
list.sort
方法和内置函数 sorted
list.sort
方法会就地排序列表,也就是说不会把原列表复制一份。这也是这个方法的返回值是 None
的原因。
与 list.sort
相反的是内置函数 sorted
,它会新建一个列表作为返回值。这个方法可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。不管 sorted
接受的是怎样的参数,它最后都会返回一个列表。
fruits = ['grape','raspberry','apple','banana']
sorted_fruits = sorted(fruits)
print(sorted_fruits)
reverse_sorted_fruits = sorted(fruits,reverse=True)
print(reverse_sorted_fruits)
len_sorted_fruits = sorted(fruits,key=len)
print(len_sorted_fruits)
用 bisect
来管理已排序的序列
bisect
模块包含两个主要函数,bisect
和 insort
,两个函数都利用二分查找算法来在有序序列中查找或插入元素。
用 bisect
来搜索
bisect(haystack, needle)
在 haystack
中搜索 needle
的位置,该位置满足的条件是,把 needle
插入这个位置之后, haystack
还能保持升序。也就是在说这个函数返回的位置前面的值,都小于或等于 needle
的值。
import bisect
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
res = [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
print(res)
用 bisect.insort
插入新元素
排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。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)
当列表不是首选时
数组
如果我们需要一个只包含数字的列表,那么 array.array
比 list
更高效:
- 数组支持所有跟可变序列有关的操作,包括
.pop
、.insert
和.extend
- 数组还提供从文件读取和存入文件的更快的方法,如
.frombytes
和.tofile
创建数组需要一个类型码,这个类型码用来表示在底层的 C 语言应该存放怎样的数据类型。比如 b
类型码代表的是有符号的字符)(signed char)。
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])
内存视图
memoryview
是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。
memoryview.cast
的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。
from array import array
numbers = array('h',[-2,-1,0,1,2]) # 5 个短整型有符号数组
mem_view = memoryview(numbers)
print(len(mem_view))
print(mem_view[-2])
mem_view_oct = mem_view.cast('B') # 转换成无符号字符
print(mem_view_oct.tolist())
mem_view_oct[5] = 4
print(numbers)
NumPy 和 SciPy
NumPy 实现了多维同质数组和矩阵,这些数据结构不但能处理数字,还能存放其他由用户定义的记录。
SciPy 是基于 NumPy 的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。
numpy.ndarray
的基本操作:
import numpy as np
a = np.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])
a = a.transpose() # 转置
print(a)
NumPy 文件读写操作:
import numpy as np
floats = np.random.rand(100)
print(floats)
np.save("100floats.npy",floats)
floats2 = np.load("100floats.npy")
print(floats == floats2)
双向队列和其他形式的队列
collections.deque
类(双向队列)是一个线程安全、可以快速从两端添加或者删除元素的数据类型。
from collections import deque
# maxlen 是可选参数,代表队列可以容纳的元素数量,一旦设定,不可修改
dq = deque(range(10),maxlen=10)
print(dq)
dq.rotate(3)
print(dq)
dq.appendleft(100)
print(dq)
dq.extend([11,22,33])
print(dq)