目录
1. 容器协议
Python 中的协议(protocol)与其他编程语言中的接口很相似,规定哪些方法必须被定义,但 Python 中协议更多的是一种指南,而不是强制要求。
在 Python 中序列(如列表、字符串、元组等)和映射(字典)都属于容器类型,和容器类型有关的魔法方如下表:
魔法方法 | 含义 |
__len__(self) | 在 len(self) 上调用,定义返回容器中元素个数的行为,可能用于真值检测 |
__contains__(self, item) | 在成员测试运算符(in 或 not in)上调用,定义检测容器成员的行为 |
__iter__(self) | 在 iter(self) 上调用,定义容器的迭代器 |
__next__(self) | 在 next(self) 上调用,定义迭代器返回下一个值的行为 |
__getitem__(self, key) | 在 self[key]、self[i:j:k]、x in self 等情况中调用,定义获取容器中指定元素的行为 |
__setitem__(self, key, value) | 在 self[key] = value,self[i:j:k] = value 中调用,定义为容器的键或索引或分片赋值的行为 |
__delitem__(self, key) | 在 del self[key]、del self[i:j:k] 中调用,定义删除容器中指定元素的行为,相当于 del self[key] |
__reversed__(self) | 在 reversed() 上调用,定义反向迭代的行为,返回一个新的在容器中以反向顺序迭代所有对象的可迭代对象 |
当我们需要定制容器时,需要重构上述魔法方法,此时有如下协议:
- 定制不可变容器,需要定义 __len__ 和 __getitem__ 方法;
- 定制可变容器,则除了 __len__ 和 __getitem__ 方法外,还需定义 __setitem__ 和 __delitem__ 方法。
2. 定制不可变容器
定制一个不可改变容器,并记录每个序列被读取的次数:
class Mylist:
def __init__(self, *var):
self.mylist = list(var)
self.counts = {}.fromkeys(range(len(self.mylist)), 0)
def __len__(self):
mylistlen = len(self.mylist)
return mylistlen
def __getitem__(self, item):
self.counts[item] += 1
itemvalue = self.mylist[item]
self.lastinfo = f'容器元素 {itemvalue} 已被读取 {self.counts[item]} 次'
return itemvalue
listx = Mylist('abc',1,2,[3,'$'])
listx[1]
1
listx[0]
'abc'
len(listx)
4
listx.counts
{0: 1, 1: 1, 2: 0, 3: 0}
listx.lastinfo
'容器元素 abc 已被读取 1 次'
3. 定制可变容器
定制一个可变容器,要求记录列表中每个元素被访问的次数,并在-章节2 定制不可变容器 基础上,增加如下功能
- 要求1:实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
- 要求2:增加 counter(index) 方法,返回 index 参数所指定的元素记录的访问次数
- 要求3:实现 append()、pop()、remove()、insert()、clear() 和 reverse() 方法
class Mylist:
#初始化,创建列表,创建列表元素访问计数字典
def __init__(self, *var):
self.value = list(var)
self.counts = {}.fromkeys(range(len(self.value)), 0)
#获取容器长度方法
def __len__(self):
return len(self.value)
#获取容器元素方法,每次获取元素,该元素的访问计数+1
def __getitem__(self, item):
itemvalue = self.value[item]
self.counts[item] += 1
return itemvalue
#设置容器元素数值方法
def __setitem__(self, key, value):
self.value[key] = value
#删除容器元素方法,同时删除该元素的访问计数
def __delitem__(self, key):
del self.value[key]
del self.counts[key]
#返回元素的访问计数
def counter(self, index):
return self.counts[index]
#容器添加元素,新增元素配套增加访问计数器,初始值为0
def append(self, value):
self.value.append(value)
self.counts[len(self.value) - 1] = 0
#容器按索引删除元素,默认删除最后一位元素
#支持索引为负,从后往前删除元素
#删除元素后,被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
def pop(self, key=-1):
delvalue = self.value[key]
if - len(self.value) <= key <= len(self.value):
if key < 0:
key += len(self.value)
for i in range(key, len(self.value)-1):
self.counts[i] = self.counts[i + 1]
del self.counts[len(self.value)-1]
else:
raise IndexError('pop index out of range')
del self.value[key]
return delvalue
#容器删除第一个值为 value 的元素
#值在容器中,查询值的第一个索引并删除对应元素;值不在容器中,返回指定错误
#被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
def remove(self, value):
try:
removeindex = self.value.index(value)
for i in range(removeindex, len(self.value)-1):
self.counts[i] = self.counts[i + 1]
del self.counts[len(self.value)-1]
del self.value[removeindex]
except:
raise ValueError('Mylist.remove(x): x not in Mylist')
#容器在索引 index 位置插入值 value,后续元素自动后移一位
#后移的元素的访问计数,跟随元素一并移动
#支持索引为负,从后往前插入;支持索引超过容器长度,直接插入 value 到容器的最前或最后
def insert(self, index, value):
if - len(self.value) < index < len(self.value):
if index < 0:
index += len(self.value)
self.value.insert(index, value)
lastcount = self.counts[index]
self.counts[index] = 0
for i in range(index+1, len(self.value)-1):
nextcount = self.counts[i]
self.counts[i] = lastcount
lastcount = nextcount
self.counts[len(self.value)-1] = lastcount
else:
if index < 0:
self.value = [value] + self.value
lastcount = self.counts[0]
self.counts[0] = 0
for i in range(1, len(self.value) - 1):
nextcount = self.counts[i]
self.counts[i] = lastcount
lastcount = nextcount
self.counts[len(self.value)-1] = lastcount
else:
self.value.append(value)
self.counts[len(self.value)-1] = 0
#容器元素清空,同时清空容器元素访问计数
def clear(self):
self.value.clear()
self.counts.clear()
#容器元素倒序,容器元素访问计数也跟随倒序移动
def reverse(self):
self.value.reverse()
self.counts = dict(zip(list(self.counts.keys()),list(reversed(self.counts.values()))))
#--------------------------实操验证-----------------------------
#创建实例 list1
list1 = Mylist(3,2,1,0,1,2,3)
#检查实例的值和计数器初始化
list1.value
[3, 2, 1, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0}
#读取实例 list1 各元素,检查计数器
list1[1]
2
list1[2]
1
list1[2]
1
list1[2]
1
list1[4]
1
list1[4]
1
list1[5]
2
list1[6]
3
list1[6]
3
list1.counts
{0: 0, 1: 1, 2: 3, 3: 0, 4: 2, 5: 1, 6: 2}
#按索引删除,检查值和计数器
list1.pop(2)
1
list1.value
[3, 2, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 1, 2: 0, 3: 2, 4: 1, 5: 2}
#按值删除,检查值和计数器
list1.remove(2)
list1.value
[3, 0, 1, 2, 3]
list1.counts
{0: 0, 1: 0, 2: 2, 3: 1, 4: 2}
#清空容器,检查值和计数器
list1.clear()
list1.value
[]
list1.counts
{}
#添加元素,并访问元素,检查值和计数器
list1.append('a')
list1.append(1)
list1.append(2)
list1.append('b')
list1[2]
2
list1[2]
2
list1[1]
1
list1.value
['a', 1, 2, 'b']
list1.counts
{0: 0, 1: 1, 2: 2, 3: 0}
#倒序、正序插入元素,检查值和计数器
list1.insert(-2,'xyz')
list1.value
['a', 1, 'xyz', 2, 'b']
list1.counter(3)
2
list1.counts
{0: 0, 1: 1, 2: 0, 3: 2, 4: 0}
4. 迭代器
迭代是重复反馈过程的活动,每一次对过程的重复称为一次“迭代”,而每一次迭代结果作为下一次迭代的初始值;
提供迭代方法的容器称为可迭代对象,如常见的序列、字典都支持迭代操作,属于可迭代对象,即迭代器;
譬如使用 for 循环对列表进行迭代操作如下:
test = [1,2,3,4,5]
for each in test:
print(each)
1
2
3
4
5
Python 提供两个BIF:iter()、next() 处理迭代操作,调用 iter() 得到它的迭代器,调用 next() 返回迭代器的下一个值,当迭代器没有值可以返回,抛出异常 StopIteration。
#通过 iter() 获取列表 test 的迭代器 testiter
testiter = iter(test)
#testiter 为列表的迭代器对象
testiter
<list_iterator object at 0x00000160CE20F250>
#通过 next() 获取迭代器的下一个值,直到抛出异常 StopIteration
next(testiter)
1
next(testiter)
2
next(testiter)
3
next(testiter)
4
next(testiter)
5
next(testiter)
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
5. 定制迭代器
iter()、next() 对应的魔法方法即 __iter__、__next__;
__iter__(self)
返回一个带有 __next__ 方法的可迭代对象,一个容器如果本身即迭代器,常返回容器本身;
通过 for 循环、next() 函数等方法调用迭代器的 __next__ 方法获取迭代器范围内的值,直到抛出 StopIteration 异常。
__next__(self)
__next__ 方法决定了迭代操作的规则,是定制迭代器的关键;
实操示例
定义一个斐波那契数列类型:
class Fibonacci:
def __init__(self, f1=1, f2=1):
self.f1 = f1
self.f2 = f2
def __iter__(self):
return self
def __next__(self):
temp = self.f1
self.f1 = self.f2
self.f2 += temp
if self.f1 > 100:
raise StopIteration
return self.f1
ftest = Fibonacci()
for each in ftest:
print(each)
1
2
3
5
8
13
21
34
55
89