python 中的序列协议一览

python 中的序列协议一览

在Python中,序列(Sequence)是一种基本的数据结构,用于存储一系列有序的元素。序列是Python中最常见的数据类型之一,它包括字符串(String)、列表(List)、元组(Tuple)等。

序列的特点是可以通过索引访问其中的元素,索引从0开始,并且支持切片操作,可以获取序列的子序列。此外,序列还可以进行迭代、拼接、重复、比较等操作。

在 python 语言中,通过存储数据的类型是否可变可以分为以下 4 种序列:

  • 容器序列
    • list
    • tuple
    • deque
  • 扁平序列
    • str
    • bytes
    • bytearray
    • arrar.array

  • 可变序列
    • list
    • deque
    • bytearray
    • array
  • 不可变序列
    • str
    • tuple
    • bytes

那么关于序列这种对象,其序列协议又是如何的,是如何实现的? 带着这个问题,我们来学习一下 python 序列对象

collections 中 Sequence/MutableSequence

首先,我们来看一下,在 python 中有这样一个库:collections, 其中包含了一些容器类的实现,其中序列包含 Sequence && MutableSequence, 其基类的实现关系又是如何的?

我们可以通过查看源码的方式来看一下不可变序列的协议:

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
class Sequence(Collection[_T_co], Reversible[_T_co], Generic[_T_co]):
@overload
@abstractmethod
def __getitem__(self, index: int) -> _T_co: ...
@overload
@abstractmethod
def __getitem__(self, index: slice) -> Sequence[_T_co]: ...
# Mixin methods
def index(self, value: Any, start: int = ..., stop: int = ...) -> int: ...
def count(self, value: Any) -> int: ...
def __contains__(self, value: object) -> bool: ...
def __iter__(self) -> Iterator[_T_co]: ...
def __reversed__(self) -> Iterator[_T_co]: ...

class MutableSequence(Sequence[_T], Generic[_T]):
@abstractmethod
def insert(self, index: int, value: _T) -> None: ...
@overload
@abstractmethod
def __getitem__(self, index: int) -> _T: ...
@overload
@abstractmethod
def __getitem__(self, index: slice) -> MutableSequence[_T]: ...
@overload
@abstractmethod
def __setitem__(self, index: int, value: _T) -> None: ...
@overload
@abstractmethod
def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ...
@overload
@abstractmethod
def __delitem__(self, index: int) -> None: ...
@overload
@abstractmethod
def __delitem__(self, index: slice) -> None: ...
# Mixin methods
def append(self, value: _T) -> None: ...
def clear(self) -> None: ...
def extend(self, values: Iterable[_T]) -> None: ...
def reverse(self) -> None: ...
def pop(self, index: int = ...) -> _T: ...
def remove(self, value: _T) -> None: ...
def __iadd__(self: TypeshedSelf, values: Iterable[_T]) -> TypeshedSelf: ...
  • 容器对象

对于一个不可变序列来说,首先是属于一个容器对象,对于容器对象而言,in/not in 是经常判断一个数据是否在这个容器中,例如:

1
2
3
4
5
6
contains = [1, 2, 3]
if 1 in contains:
print("yes")
else:
print("no")

实现这个协议的魔法方法是:__contains__

1
def __contains__(self, value: object) -> bool: ...
  • 不可变对象/可变对象

​ sequence 对象是一个不可变对象,那么是如何实现的?

​ 答案是 __getitem__

1
2
3
4
5
6
@overload
@abstractmethod
def __getitem__(self, index: int) -> _T_co: ...
@overload
@abstractmethod
def __getitem__(self, index: slice) -> Sequence[_T_co]: ...

我们可以看到 sequence 对象中只实现了 __getitem__ 方法,只可获取对象,于是内部数据就是不可变了,不可修改;

而实现了__setitem__&&__delitem__ 表示可以对数据进行修改/删除。

  • 可迭代对象

对于一个容器序列,如何获取其中的元素,例如 for … in .. 操作。这是因为实现了 __iter__魔法方法

1
def __iter__(self) -> Iterator[_T_co]: ...
  • 序列的合并

    当对两个序列进行合并时,可以使用 +=,例如:

1
2
a = [1, 2]
b += a

这个是如何实现的,其实是因为魔法方法 __iadd__ 的实现

通过对两个序列类的学习,我们了解了序列类的一些协议,这样我们就可以基于这些知识自己实现一个序列。

可切片的序列类

我们先通过 list 来了解一下 python 中 slice 的概念:

1
2
3
4
5
6
7
8
a = [1, 2, 3, 4, 5]
# a[start:end:step]
b = a[:] # 返回原列表所有数据的引用
b = a[::-1] # 逆序
b = a[::2] # 间隔一个的数据集合,[1, 3, 5]
a[:0] = [1, 2] # 在列表头插入数据
del a[:3]
del a[::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
class Group(object):
"""
可切片的类
"""
def __init__(self, group_name, company_name, staffs):
self.group_name = group_name
self.company_name = company_name
self.staffs = staffs

def __reversed__(self):
cls = type(self)
reversed_staffs = self.staffs.reverse()
return cls(group_name=self.group_name, company_name=self.company_name, staffs=reversed_staffs)

def __getitem__(self, item): # 实现切片的魔法方法
cls = type(self) # 用于在类的实例方法中获取当前实例所属的类
if isinstance(item, int):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=[self.staffs[item]])
elif isinstance(item, slice):
return cls(group_name=self.group_name, company_name=self.company_name, staffs=self.staffs[item])
else:
raise IndexError

def __len__(self):
return len(self.staffs)

def __iter__(self):
return iter(self.staffs)

def __contains__(self, item):
if item in self.staffs:
return True
else:
return False

if __name__ == '__main__':
group = Group(company_name="1", group_name="1.1", staffs=[1, 2])
var = group[1:2]
print(var)
print(group[0].staffs)
print(len(group))

上述代码中,按照序列类的协议实现了一个自定义的序列,借助了内置的序列类 list

bisect 模块介绍

模块介绍:https://docs.python.org/zh-cn/3/library/bisect.html

bisect模块是Python标准库中的一个模块,提供了对已排序列表的二分查找和插入操作的支持。它基于二分查找算法,在有序序列中进行高效的查找和插入。

bisect模块提供了以下常用的函数:

  1. bisect_left(a, x, lo=0, hi=len(a)): 返回在已排序列表 a 中将元素 x 插入的位置(左边界)。 如果 x 已经存在于列表中,那么返回其最左边的位置。可选参数 lohi 可用于指定查找范围。
  2. bisect_right(a, x, lo=0, hi=len(a)): 返回在已排序列表 a 中将元素 x 插入的位置(右边界)。 如果 x 已经存在于列表中,那么返回其最右边的位置。可选参数 lohi 可用于指定查找范围。
  3. bisect(a, x, lo=0, hi=len(a))bisect_right 的别名函数,返回在已排序列表 a 中将元素 x 插入的位置(右边界)。
  4. insort_left(a, x, lo=0, hi=len(a)): 将元素 x 按照有序方式插入到已排序列表 a 中的正确位置(左边界)。 可选参数 lohi 可用于指定插入范围。
  5. insort_right(a, x, lo=0, hi=len(a)): 将元素 x 按照有序方式插入到已排序列表 a 中的正确位置(右边界)。 可选参数 lohi 可用于指定插入范围。

bisect模块的函数可用于在已排序的序列中进行高效的查找和插入操作,例如在有序列表中快速找到插入点,或者将元素按照正确的顺序插入到列表中。这在需要处理大量数据的应用中特别有用,如搜索、排名、数据统计等场景。

1
2
3
4
5
6
7
8
9
import bisect

inter_li = []
bisect.insort(inter_li, 3)
bisect.insort(inter_li, 2)
bisect.insort(inter_li, 5)
bisect.insort(inter_li, 6)
bisect.insort(inter_li, 0)
bisect.insort(inter_li, 2)

列表的使用场景&&其他容器对象介绍

什么时候我们不该使用列表?

  • array
    C 语言中的数组,连续的数组空间,存储的数据类型是一致的
    官方参考文档:https://docs.python.org/zh-cn/3/library/array.html
    常用的类型映射:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Type code   C Type             Minimum size in bytes
    'b' signed integer 1
    'B' unsigned integer 1
    'u' Unicode character 2 (see note)
    'h' signed integer 2
    'H' unsigned integer 2
    'i' signed integer 2
    'I' unsigned integer 2
    'l' signed integer 4
    'L' unsigned integer 4
    'q' signed integer 8 (see note)
    'Q' unsigned integer 8 (see note)
    'f' floating point 4
    'd' floating point 8
1
2
3
4
5
6
7
import array

if __name__ == '__main__':
my_arr = array.array("i")
my_arr.append(1)
# my_arr.append("abc")
print(my_arr)

更为详细的用法可以参考官方文档

  • deque
-------------THANKS FOR READING-------------