目录
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() 上调用,定义反向迭代的行为,返回一个新的在容器中以反向顺序迭代所有对象的可迭代对象 |
当我们需要定制容器时,需要重构上述魔法方法,此时有如下协议:
定制一个不可改变容器,并记录每个序列被读取的次数:
- 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 次'
定制一个可变容器,要求记录列表中每个元素被访问的次数,并在-章节2 定制不可变容器 基础上,增加如下功能
- 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}
迭代是重复反馈过程的活动,每一次对过程的重复称为一次“迭代”,而每一次迭代结果作为下一次迭代的初始值;
提供迭代方法的容器称为可迭代对象,如常见的序列、字典都支持迭代操作,属于可迭代对象,即迭代器;
譬如使用 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
iter()、next() 对应的魔法方法即 __iter__、__next__;
返回一个带有 __next__ 方法的可迭代对象,一个容器如果本身即迭代器,常返回容器本身;
通过 for 循环、next() 函数等方法调用迭代器的 __next__ 方法获取迭代器范围内的值,直到抛出 StopIteration 异常。
__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