第五章 自定义序列类
5.1 python中的序列分类
序列是python中一个很重要的协议。
第一个维度:
容器序列(list、tuple、deque)
容器序列可以放任意类型的数据,所以理解为容器
my_list = []
my_list.append(1) # int
my_list.append("a") # str
print(my_list)
[1, 'a']
扁平序列(str、bytes、bytearray、array.array)
str是序列类型,有个特性是可以用for循环遍历。
第二个维度:
可变序列(list、deque、bytearray、array)
不可变序列(str、tuple、bytes)
5.2 序列类型的abc继承关系
跟容器相关的一些数据结构的抽象基类都是放在collections.abc里,点进去看
__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable", "Collection",
"Set", "MutableSet",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString",
]
里面都是抽象基类,"Sequence"(不可变序列)、 "MutableSequence"(可变序列),是序列相关的抽象基类。
点击去Sequence看,所有魔法函数构成了序列协议:
class Sequence(Reversible, Collection):
"""All the operations on a read-only sequence.
Concrete subclasses must override __new__ or __init__,
__getitem__, and __len__.
"""
__slots__ = ()
@abstractmethod
def __getitem__(self, index):
raise IndexError
def __iter__(self):
i = 0
try:
while True:
v = self[i]
yield v
i += 1
except IndexError:
return
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
def __reversed__(self):
for i in reversed(range(len(self))):
yield self[i]
Sequence继承了两个类,Reversible是数据的反转
点进去看Collection
class Collection(Sized, Iterable, Container):
__slots__ = ()
@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented
继承了三个抽象基类Sized, Iterable, Container
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
return _check_methods(C, "__len__")
return NotImplemented
Sized实现了len方法,这样可以计算Collection的长度
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented
Iterable实现了迭代器,可以进行for循环
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
return _check_methods(C, "__contains__")
return NotImplemented
Container实现了contains,可以使用if、in判断。当然如果不实现contains,使用getitem魔法方法,也是可以用if、in判断的。
再看MutableSequence(可变数据类型):
class 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魔法函数,分别是添加和删除,还有很多其他的魔法函数,这些魔法函数构成了序列的协议。
5.3 list中+、+=和extend的区别
a = [1,2]
c = a + [3,4]
print(C)
[1, 2, 3, 4]
+可以把两个list做连接,+=也可以把两个list做连接
a = [1,2]
a += [3,4] # 就地加
print(a)
[1, 2, 3, 4]
+=还可以连接列表和元组
a = [1,2]
a += (3,4) # 就地加
print(a)
[1, 2, 3, 4]
还是一样的效果,但如果把+中的列表改为元组
a = [1,2]
c = a + (3,4)
print(C)
c = a + (3,4)
TypeError: can only concatenate list (not "tuple") to list
会抛出异常,所以+和+=有很大的区别。
+=中可以接任意的序列类型,+=其实是通过一个魔法函数实现的。
MutableSequence(可变序列类型)里面有个__iadd__魔法函数:
def __iadd__(self, values):
self.extend(values)
return self
调用的是extend方法:
def extend(self, values):
'S.extend(iterable) -- extend sequence by appending elements from the iterable'
if values is self:
values = list(values)
for v in values:
self.append(v)
extend接受一个values,调用for循环,任何序列类型都可以调用for循环,然后依次append进列表。
那么使用extend也是可以的:
a = [1,2]
a += (3,4)
a.extend(range(3))
print(a)
[1, 2, 3, 4, 0, 1, 2]
再说append方法,和extend是不一样的
a = [1,2]
a.append([1,2])
a.append((3,4))
print(a)
[1, 2, [1, 2], (3, 4)]
append是把列表变成了一个元素加入列表中,而不是迭代里面的元素,依次加入列表中。
append不会去做for循环。
5.4 实现可切片的对象
先看下切片操作
#模式[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]
print (aList[::]) # 返回包含原列表中所有元素的新列表
print (aList[::-1]) # 返回包含原列表中所有元素的逆序列表
print (aList[::2]) # 隔一个取一个,获取偶数位置的元素
print (aList[1::2]) # 隔一个取一个,获取奇数位置的元素
print (aList[3:6]) # 指定切片的开始和结束位置
aList[0:100] # 切片结束位置大于列表长度时,从列表尾部截断
aList[100:] # 切片开始位置大于列表长度时,返回空列表
[3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
[17, 15, 13, 11, 9, 7, 6, 5, 4, 3]
[3, 5, 7, 11, 15]
[4, 6, 9, 13, 17]
[6, 7, 9]
[0, 2, 0, 4, 0, 6]
aList = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
aList[len(aList):] = [9] # 在列表尾部增加元素
print("1-",aList)
aList[:0] = [1, 2] # 在列表头部插入元素
print("2-",aList)
aList[3:3] = [4] # 在列表中间位置插入元素
print("3-",aList)
aList[:3] = [1, 2] # 替换列表元素,等号两边的列表长度相等
print("4-",aList)
aList[3:] = [4, 5, 6] # 等号两边的列表长度也可以不相等
print("5-",aList)
aList[::2] = [0] * 3 # 隔一个修改一个
print ("6-",aList)
aList[::2] = ['a', 'b', 'c'] # 隔一个修改一个
print("7-",aList)
# aList[::2] = [1,2] # 左侧切片不连续,等号两边列表长度必须相等
aList[:3] = [] # 删除列表中前3个元素
print("8-",aList)
del aList[:3] # 切片元素连续
print("9-",aList)
del aList[::2] # 切片元素不连续,隔一个删一个
print("10-",aList)
1- [3, 4, 5, 6, 7, 9, 11, 13, 15, 17, 9]
2- [1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 15, 17, 9]
3- [1, 2, 3, 4, 4, 5, 6, 7, 9, 11, 13, 15, 17, 9]
4- [1, 2, 4, 4, 5, 6, 7, 9, 11, 13, 15, 17, 9]
5- [1, 2, 4, 4, 5, 6]
6- [0, 2, 0, 4, 0, 6]
7- ['a', 2, 'b', 4, 'c', 6]
8- [4, 'c', 6]
9- []
10- []
实现一个支持切片的对象(不可变),根据Sequence抽象基类里的魔法函数。
import numbers
class Group:
# 支持切片操作
def __init__(self, group_name, company_name, staffs): # 组名,公司名,员工
self.group_name = group_name
self.company_name = company_name
self.staffs = staffs # staffs是list
def __reversed__(self):
self.staffs.reverse()
def __getitem__(self, item):
cls = type(self) # 获取当前class
# 返回一个Group类型
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):
if item in self.staffs:
return True
else:
return False
staffs = ["bobby1", "imooc", "bobby2", "bobby3"]
group = Group(company_name="imooc", group_name="user", staffs=staffs)
# 切片
sub_group = group[:2]
# 求长度
print(len(group))
# is、in判断
if "bobby1" in group:
print ("yes")
# for 迭代
for user in group:
print(user)
# 反转
reversed(group)
print(group.staffs)
4
yes
bobby1
imooc
bobby2
bobby3
['bobby3', 'bobby2', 'imooc', 'bobby1']
Group是组的概念,在生活中很常见,比如学校的学习组,公司的部门。对Group实例切片,返回一个Group类型的对象。
getitem是切片的关键,item可以是一个slice对象,也可以是一个int。
sub_group是一个Group类型对象。
5.5 bisect维护已排序序列
用来处理已排序的序列,用来维持已排序的升序序列。
点进bisect里看:
有insort和bisect,insort是用来插入的。
对数据结构了解的话,对已排序的序列,有一种查找效率很高的算法叫二分查找,bisect就是用二分查找来维持已排序序列,包括插入一个数据(insort)、查找数据应该插入什么位置(bisect)。
insort有两个变种的函数insort_right和insort_left,是insort_right的默认
- insort_left:如果插入的数据在序列中已存在,则新数据插入已存在数据之前
- insort_right:如果插入的数据在序列中已存在,则新数据插入已存在数据之后
bisect也有两个变种的函数bisect_left和bisect_right,是bisect_right的默认
- bisect_left:一个数据插入序列的时候,插入位置做左边开始计算
- bisect_right:一个数据插入序列的时候,插入位置做右边开始计算
import bisect
#用来处理已排序的序列,用来维持已排序的序列, 升序
#二分查找
inter_list = []
bisect.insort(inter_list, 3)
bisect.insort(inter_list, 2)
bisect.insort(inter_list, 5)
bisect.insort(inter_list, 1)
bisect.insort(inter_list, 6)
print(inter_list)
[1, 2, 3, 5, 6]
返回一个排序的序列,所以如果要维护一个排序好的序列,建议使用bisect插入数据,而且二分查性能很高。
print(bisect.bisect(inter_list, 3))
print(bisect.bisect_left(inter_list, 3))
3
2
可以知道3在序列中应该插入的位置是什么。
5.6 什么时候不应该用列表
平时用的最多的是list,但有时候会有更好的选择,array和deque。
array是c语言中的数组,性能很高。
先来看一下list,是用c语言写的,里面有很多方法。
再来看一下array,也是用c语言写的。
array和list的一个重要区别, array只能存放指定的数据类型。list是一个容器,可以存放任何类型的数据,但是array在使用的时候一开始就要声明存放的数据类型,并且之后也只能存储对应类型的数据。
array也有很多方法,有很多跟list是一样的。
import array
my_array = array.array("i") # int类型
my_array.append(1)
my_array.append("abc")
array('i', [1])
my_array.append("abc")
TypeError: an integer is required (got type str)
存放非int数据会报错。
5.7 列表生成式、生成器表达式、字典推导式
列表生成式:用一行代码生成列表,性能高于列表操作
比如要提取出1-20之间的奇数,用for循环可以实现
odd_list = []
for i in range(21):
if i %2 == 1 :
odd_list.append(i)
print(odd_list)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
那使用列表生成式呢?
odd_list= [ i for i in range(21) if i % 2 == 1]
print(odd_list)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
比如逻辑复杂的情况,对奇数取平方
def handle_time(item):
return item * item
odd_list = [handle_time(i) for i in range(21) if i % 2 == 1]
print(odd_list)
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
生成器表达式:可迭代
odd_list= ( i for i in range(21) if i % 2 == 1)
print(odd_list)
for item in odd_list:
print(item)
<generator object <genexpr> at 0x0000020492C46C80>
1
3
5
7
9
11
13
15
17
19
字典推导式
比如反转字典key,value的位置
my_dict = {"bobby1":22,"bobby2":23,"imooc":5}
reversed_dict = {value:key for key,value in my_dict.items()}
print(reversed_dict)
{22: 'bobby1', 23: 'bobby2', 5: 'imooc'}
集合推导式
my_dict = {"bobby1":22,"bobby2":23,"imooc":5}
my_set = {key for key,value in my_dict.items()}
print(my_set)
{'bobby2', 'bobby1', 'imooc'}