Introduce
- python中的序列分类
- 序列的abc继承关系
- 序列中+、+=和extend的区别
- 实现可切片的对象
- bisect维护已排序序列
- 什么时候我们不该用列表
- 列表推导式、生成器表达式和字典推导式
一、Python中的序列分类
序列是 python 中重要的协议
所谓序列,即成员有序排列,可通过下标访问.
常见序列类型包括字符串(普通字符串和unicode字符串),列表和元组.
1、序列分类
按照元素类型是否相同:
- 容器序列:list、tuple、deque
- 扁平序列:str、bytes、bytearray、array.array
按照元素是否可变:
- 可变类型:list、deque、bytearray、array.array
- 不可变:str、tuple、bytes
2、容器序列和扁平序列的区别?
-
容器序列
可以存放不同类型的数据。即可以存放任意类型对象的引用。
(容器序列可以放任意类型的数据,所以理解为容器) -
扁平序列
只能容纳一种类型。
也就是说其存放的是值而不是引用。换句话说扁平序列其实是一段连续的内存空间,由此可见扁平序列其实更加紧凑。但是它里面只能存放诸如字符、字节和数值这种基础类型。
示例代码:
# 元素类型任意
my_list = list()
my_list.append(100)
my_list.append(True)
# 指定元素类型
import array
my_array = array.array('i')
my_array.append(100)
# 初始化数组需要整型,附加字符串抛异常
my_array.append('abc')
二、序列的abc继承关系
1、概念
python 中内置的 collections.abc 抽象基类,可以帮助我们理解数据类型实现细节
python 是基于协议的语言,结合鸭子类型和魔法函数,就可以达到实现某种类型
**from collections.abc import ***
- Iterable: iter
- Reversible: reversed
- Sized: len
- Container: contains
- Collection: Sized, Iterable, Container
- Sequence: getitem, Reversible, Collection
- MutableSequence: setitem, delitem, Sequence
不同魔法函数的组合,构建不同的类型
跟容器相关的一些数据结构的抽象基类都是放在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”(可变序列),是序列相关的抽象基类。
2、Sequence(不可变序列)
点击去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
2.1、Reversible
Reversible是数据的反转
2.2、Collection
点进去看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
Sized实现了len方法,这样可以计算Collection的长度
-
Iterable
Iterable实现了迭代器,可以进行for循环
-
Container
Container实现了contains,可以使用if、in判断。当然如果不实现contains,使用getitem魔法方法,也是可以用if、in判断的。
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判断的。
3、MutableSequence(可变数据类型)
再看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魔法函数,分别是添加和删除,还有很多其他的魔法函数,这些魔法函数构成了序列的协议。
三、序列中+、+=和extend的区别
1、综述
综述:
- 加号 + 会新生成对象,并且 + 两边类型需要一致
- 加等 += 就地加,只需要可迭代就行
- append 附加单个元素,extend 扩展多个元素
# + 加新增
l1 = [1, 2]
l2 = l1 + [3, 4]
print("type(l1): %d, and l1: %s" % (id(l1), l1))
print("type(l2): %d, and l2: %s" % (id(l2), l2))
# += 就地加
l1 += ['3', '4']
l1 += ('5', '6')
l1 += range(2)
print("type(l1): %d, and l1: %s" % (id(l1), l1))
# + 两边类型需相同
# += 只需要可迭代的就行,__iadd__ 魔法函数实现
运行结果:
type(l1): 1612537423104, and l1: [1, 2]
type(l2): 1612535559744, and l2: [1, 2, 3, 4]
type(l1): 1612537423104, and l1: [1, 2, '3', '4', '5', '6', 0, 1]
进程已结束,退出代码为 0
2、拓:+=其实是通过__iadd__ 魔法函数实现
+=中可以接任意的序列类型,+=其实是通过一个魔法函数实现的。
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循环。
四、实现可切片的对象
1、列表切片操作
'''
模式 [start:end:step]
第一个数字 start 表示切片开始位置,默认 0
第二个数字 end 表示切片截止(但不包含)位置,默认列表长度
第三个数字 step 表示切片的步骤,默认为 1
当 start 为 0 时可以省略
当 end 为列表长度时可以省略
当 step 为 1 时可以省略,并且省略步长时可以同时省略最后一个冒号
当 step 为负数时,表示反向切片,这时 start 应该比 end 的值要大才行
'''
a_list = [3, 4, 5, 6, 7, 9, 11, 13, 15, 17]
a_list[::] # 返回包含原列表中所有元素的新列表
a_list[::-1] # 返回包含原列表中所有元素的逆向新列表
a_list[::2] # 隔一个元素取一个,获取偶数位置的元素
a_list[1::2] # 隔一个元素取一个,获取奇数位置的元素
a_list[3:6] # 指定切片的开始和结束位置
a_list[0:100] # 切片结束位置大于列表长度是,从列表尾部截断
a_list[100:] # 切片开始位置大于列表长度时,返回空列表
a_list[len(a_list):0] = [9] # 在列表尾部增加元素
a_list[:0] = [1, 2] # 在列表头部增加元素
a_list[3:3] = [100] # 在列表中间位置插入元素
a_list[:2] = [100, 200] # 替换列表元素,等号两边长度相等
a_list[3:] = [4, 5, 6] # 替换列表元素,等号两边长度可以不相等
a_list[:3] = [] # 删除列表中前 3 个元素
2、自定义序列类
实现一个支持切片的对象(不可变),根据collections.abc 序列类 Sequence抽象基类里的魔法函数。
-
Group是组的概念
在生活中很常见,比如学校的学习组,公司的部门;
对Group实例切片,返回一个Group类型的对象。
-
getitem是切片的关键,item可以是一个slice对象,也可以是一个int。
-
sub_group是一个Group类型对象。
示例代码:
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']
五、bisect维护已排序序列
1、bisect
bisect
- bisect 维护一个升序的序列
- 内部二分查找实现,效率高
(用来处理已排序的序列,用来维持已排序的升序序列。)
示例代码:
import bisect
# 处理已排序 序列 升序
# 内部二分查找算法实现
l1 = list()
bisect.insort(l1, 10)
bisect.insort(l1, 3)
bisect.insort(l1, 2)
bisect.insort(l1, 6)
print(l1) # [2, 3, 6, 10]
2、详细介绍
点进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在序列中应该插入的位置是什么。
六、什么时候我们不该用列表
某些应用场景,除了 list 我们还有其他更好的选择
- 比 list 更好的 python 内置数据结构
- array 数组 连续的内存空间,性能高(array是c语言中的数组)
- deque 双向列表
array 数组
array 与 list 一个重要区别,array 只能存储指定的数据类型数据
import array
list
my_array = array.array('i')
my_array.append(100)
my_array.append('abc')
七、列表推导式、生成器表达式和字典推导式
1、列表推导式
1.1、概念
列表推导式,或列表生成式,通过一行代码生成列表
示例代码:
# 提取出 1-20 之间的奇数
odd_list = [i for i in range(21) if i % 2 == 1]
print(odd_list)
# 逻辑复杂的情况
def handle_item(item):
return item * item
odd_list = [handle_item(i) for i in range(21) if i % 2 == 1]
print(odd_list)
运行结果:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
进程已结束,退出代码为 0
1.2、特点
- 列表生成式性能高于列表操作
- 逻辑过于复杂时,列表生成式可读性降低
2、生成器表达式
可迭代
列表推导 [] -> ()
my_gen = (i for i in range(21) if i % 2 == 1)
print(type(my_gen)) # <class 'generator'>
for i in my_gen:
print(i)
3、字典推导式
比如反转字典key,value的位置
d1 = {'key1': 'value1', 'key2': 'value2'}
d2 = {v: k for (k, v) in d1.items()}
print(d2)
4、集合推导式
set1 = {v for v in d1.values()}
print(set1)