这篇文章我们会了解到Python的序列协议是什么,以及通过序列协议使类或者实例转变为序列。
Python中的内置序列分类
我们按照两个维度来区分序列
第一个维度是按照序列存贮的数据类型,分为容器序列和扁平序列1
2容器序列
list, tuple, deque1
2扁平序列
str, bytes, bytearray, array.array
容器序列里面可以放任意类型的数据。扁平序列的数据类型一致。
注意:array和list的一个重要区别, array只能存放指定的数据类型
第二个维度是按照序列是否可变,分为可变序列和不可变序列1
2可变序列
list, deque, bytearray, array1
2不可变
str, tuple, bytes
Python中序列类型的abc继承关系
这一小节我们将通过abc继承关系来讲下序列协议
跟容器相关的数据结构的抽象基类都存在_collections_abc.py模块下。1"Sequence"(不可变序列), "MutableSequence"(可变序列),
我们看下不可变序列(Sequence)的源码是由哪些抽象函数协议组成的。1
2
3
4
5
6
7class (Reversible, Collection):
"""All the operations on a read-only sequence.
Concrete subclasses must override __new__ or __init__,
__getitem__, and __len__.
"""
Sequence继承了Reversible(用于翻转)和Collection。
我们再看看Collection的源码。1
2
3
4
5
6
7
8
9class Collection(Sized, Iterable, Container):
__slots__ = ()
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented
Collection 又分别继承Sized, Iterable, Container。我们看下这三个类的源码1
2
3
4
5
6
7
8
9
10
11
12
13class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
Sized实现了__len__使我们的序列具有长度。1
2
3
4
5
6
7
8
9
10
11
12
13
14class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
Iterable实现了__iter__使我们的序列可以迭代(for 操作)1
2
3
4
5
6
7
8
9
10
11
12
13class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented
Container实现了__contains__使我们可以使用 is in 判断是否存在序列中。
通过上述的魔法函数组成了构成不可变序列的协议。
对于可变序列MutableSequence,作为不可变序列Sequence的子类,我们看看它的源码多实现了哪些魔法函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class MutableSequence(Sequence):
__slots__ = ()
"""All the operations on a read-write sequence.
Concrete subclasses must provide __new__ or __init__,
__getitem__, __setitem__, __delitem__, __len__, and insert().
"""
@abstractmethod
def __setitem__(self, index, value):
raise IndexError
@abstractmethod
def __delitem__(self, index):
raise IndexError
我们看到最主要的是新增了__setitem__用于赋值,__delitem__用于删除值。这两个魔法函数。
如果我们想自定义一些序列类,只需要实现上述魔法函数(协议)即可。
序列的+、+=和extend的区别
我们看下下面代码1
2
3
4
5
6a = [1, 2]
c = a + [3, 4]
a += (3, 4)
a += [3, 4]
对于 + 两边的数据类型必须一致,而 += 只需要是序列类型即可。
为什么 +=只要是序列就可以呢?
我们看看+=的实现源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class MutableSequence(Sequence):
__slots__ = ()
"""All the operations on a read-write sequence.
Concrete subclasses must provide __new__ or __init__,
__getitem__, __setitem__, __delitem__, __len__, and insert().
"""
def extend(self, values):
'S.extend(iterable) -- extend sequence by appending elements from the iterable'
for v in values:
self.append(v)
def __iadd__(self, values):
self.extend(values)
return self
在可变序列MutableSequence中的__iadd__就是实现 +=操作的,我们看到中间有调用
extend,我们看看extend函数有要求的是可迭代类型。
对于extend:1
2
3
4
5
6a.extend(range(3))
def extend(self, iterable): # real signature unknown; restored from __doc__
""" L.extend(iterable) -> None -- extend list by appending elements from the iterable """
pass
我们看到extend内置源码实现原理接收一个可迭代对象。
实现可切片的对象
下面是Python序列切片使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# 模式[start:end:step]
"""
其中,第一个数字start表示切片开始位置,默认为0;
第二个数字end表示切片截止(但不包含)位置(默认为列表长度);
第三个数字step表示切片的步长(默认为1)。
当start为0时可以省略,当end为列表长度时可以省略,
当step为1时可以省略,并且省略步长时可以同时省略最后一个冒号。
另外,当step为负整数时,表示反向切片,这时start应该比end的值要大才行。
"""
aList = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
aList[::] # 返回包含原列表中所有元素的新列表
aList[::-1] # 返回包含原列表中所有元素的逆序列表
aList[::2] # 隔一个取一个,获取偶数位置的元素
aList[1::2] # 隔一个取一个,获取奇数位置的元素
aList[3:6] # 指定切片的开始和结束位置
aList[0:100] # 切片结束位置大于列表长度时,从列表尾部截断
aList[100:] # 切片开始位置大于列表长度时,返回空列表
aList[len(aList):] = [9] # 在列表尾部增加元素
aList[:0] = [1, 2] # 在列表头部插入元素
aList[3:3] = [4] # 在列表中间位置插入元素
aList[:3] = [1, 2] # 替换列表元素,等号两边的列表长度相等
aList[3:] = [4, 5, 6] # 等号两边的列表长度也可以不相等
aList[::2] = [0] * 3 # 隔一个修改一个
aList[::2] = ['a', 'b', 'c'] # 隔一个修改一个
aList[::2] = [1, 2] # 左侧切片不连续,等号两边列表长度必须相等
aList[:3] = [] # 删除列表中前3个元素
del aList[:3] # 切片元素连续
del aList[::2] # 切片元素不连续,隔一个删一个
下面是自定义一个不可变得序列类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45import numbers
class Group:
#支持切片操作
# staffs 是一个list以便于实现对数据的管理
def __init__(self, group_name, company_name, staffs):
self.group_name = group_name
self.company_name = company_name
self.staffs = staffs
def __reversed__(self):
# 用于对数据的反转
self.staffs.reverse()
def __getitem__(self, item):
# 切片主要的实现函数
# item有两类
# 当我们使用[:2]这种方式的时候item是切片类 item = {slice} slice(None, 2, None)
# 当使用[2]这种方式的时候item是一个int类型 item = {int} 2
# 下面返回的仍然是一个Group方便切片之后仍然可以切片
cls = type(self)
if isinstance(item, slice):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=self.staffs[item])
elif isinstance(item, numbers.Integral):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=[self.staffs[item]])
def __len__(self):
# 返回长度
return len(self.staffs)
def __iter__(self):
# 可迭代
return iter(self.staffs)
def __contains__(self, item):
# 使用 is in
if item in self.staffs:
return True
else:
return False
staffs = ["staff1", "staff2", "staff3", "staff4"]
group = Group(company_name="alibaba", group_name="user", staffs=staffs)
bisect维护已排序序列
注意:一定要是一个已排序的序列
我们先看下这个模块的源码结构
其中的insort为插入数据是insort_right的缩写,bisect为查询插入位置是bisect_right的缩写。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37In [11]: import bisect
In [12]: inter_list = []
# 我们使用insort插入数据 排序查找规则是二分法
In [13]: bisect.insort(inter_list, 3)
In [14]: bisect.insort(inter_list, 4)
In [15]: bisect.insort(inter_list, 1)
In [16]: bisect.insort(inter_list, 2)
In [17]: bisect.insort(inter_list, 5)
In [18]: inter_list
Out[18]: [1, 2, 3, 4, 5]
# 我们使用bisect查找应该插入的位置
# bisect_right表示在查找到重复值的右侧插入
In [19]: bisect.bisect(inter_list, 3)
Out[19]: 3
# bisect_left表示在查找到重复值的左侧插入
In [20]: bisect.bisect_left(inter_list, 3)
Out[20]: 2
# 对应的插入
In [21]: bisect.insort(inter_list, 3)
In [22]: inter_list
Out[22]: [1, 2, 3, 3, 4, 5]
In [23]: bisect.insort_left(inter_list, 3)
In [24]: inter_list
Out[24]: [1, 2, 3, 3, 3, 4, 5]
什么时候不该使用列表
有时对于使用list对性能有很大影响或者,我们可以考虑使用其他序列如array, deque。1
2
3
4
5
6import array
# array和list的一个重要区别, array只能存放指定的数据类型
# i 表示整型
my_array = array.array("i")
my_array.append(1)
array支持的数据类型查看 array