• 类和对象12:容器方法


    目录

    1. 容器协议

    2. 定制不可变容器

    3. 定制可变容器

    4. 迭代器

    5. 定制迭代器

    __iter__(self) 

    __next__(self) 

    实操示例


    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. 定制不可变容器

    定制一个不可改变容器,并记录每个序列被读取的次数:

    1. class Mylist:
    2. def __init__(self, *var):
    3. self.mylist = list(var)
    4. self.counts = {}.fromkeys(range(len(self.mylist)), 0)
    5. def __len__(self):
    6. mylistlen = len(self.mylist)
    7. return mylistlen
    8. def __getitem__(self, item):
    9. self.counts[item] += 1
    10. itemvalue = self.mylist[item]
    11. self.lastinfo = f'容器元素 {itemvalue} 已被读取 {self.counts[item]} 次'
    12. return itemvalue
    13. listx = Mylist('abc',1,2,[3,'$'])
    14. listx[1]
    15. 1
    16. listx[0]
    17. 'abc'
    18. len(listx)
    19. 4
    20. listx.counts
    21. {0: 1, 1: 1, 2: 0, 3: 0}
    22. listx.lastinfo
    23. '容器元素 abc 已被读取 1 次'

    3. 定制可变容器

    定制一个可变容器,要求记录列表中每个元素被访问的次数,并在-章节2 定制不可变容器 基础上,增加如下功能

    • 要求1:实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
    • 要求2:增加 counter(index) 方法,返回 index 参数所指定的元素记录的访问次数
    • 要求3:实现 append()、pop()、remove()、insert()、clear() 和 reverse() 方法
    1. class Mylist:
    2. #初始化,创建列表,创建列表元素访问计数字典
    3. def __init__(self, *var):
    4. self.value = list(var)
    5. self.counts = {}.fromkeys(range(len(self.value)), 0)
    6. #获取容器长度方法
    7. def __len__(self):
    8. return len(self.value)
    9. #获取容器元素方法,每次获取元素,该元素的访问计数+1
    10. def __getitem__(self, item):
    11. itemvalue = self.value[item]
    12. self.counts[item] += 1
    13. return itemvalue
    14. #设置容器元素数值方法
    15. def __setitem__(self, key, value):
    16. self.value[key] = value
    17. #删除容器元素方法,同时删除该元素的访问计数
    18. def __delitem__(self, key):
    19. del self.value[key]
    20. del self.counts[key]
    21. #返回元素的访问计数
    22. def counter(self, index):
    23. return self.counts[index]
    24. #容器添加元素,新增元素配套增加访问计数器,初始值为0
    25. def append(self, value):
    26. self.value.append(value)
    27. self.counts[len(self.value) - 1] = 0
    28. #容器按索引删除元素,默认删除最后一位元素
    29. #支持索引为负,从后往前删除元素
    30. #删除元素后,被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
    31. def pop(self, key=-1):
    32. delvalue = self.value[key]
    33. if - len(self.value) <= key <= len(self.value):
    34. if key < 0:
    35. key += len(self.value)
    36. for i in range(key, len(self.value)-1):
    37. self.counts[i] = self.counts[i + 1]
    38. del self.counts[len(self.value)-1]
    39. else:
    40. raise IndexError('pop index out of range')
    41. del self.value[key]
    42. return delvalue
    43. #容器删除第一个值为 value 的元素
    44. #值在容器中,查询值的第一个索引并删除对应元素;值不在容器中,返回指定错误
    45. #被删除元素之后的所有元素,访问计数器跟随元素补位顺序迁移,随后删除多余的元素访问计数
    46. def remove(self, value):
    47. try:
    48. removeindex = self.value.index(value)
    49. for i in range(removeindex, len(self.value)-1):
    50. self.counts[i] = self.counts[i + 1]
    51. del self.counts[len(self.value)-1]
    52. del self.value[removeindex]
    53. except:
    54. raise ValueError('Mylist.remove(x): x not in Mylist')
    55. #容器在索引 index 位置插入值 value,后续元素自动后移一位
    56. #后移的元素的访问计数,跟随元素一并移动
    57. #支持索引为负,从后往前插入;支持索引超过容器长度,直接插入 value 到容器的最前或最后
    58. def insert(self, index, value):
    59. if - len(self.value) < index < len(self.value):
    60. if index < 0:
    61. index += len(self.value)
    62. self.value.insert(index, value)
    63. lastcount = self.counts[index]
    64. self.counts[index] = 0
    65. for i in range(index+1, len(self.value)-1):
    66. nextcount = self.counts[i]
    67. self.counts[i] = lastcount
    68. lastcount = nextcount
    69. self.counts[len(self.value)-1] = lastcount
    70. else:
    71. if index < 0:
    72. self.value = [value] + self.value
    73. lastcount = self.counts[0]
    74. self.counts[0] = 0
    75. for i in range(1, len(self.value) - 1):
    76. nextcount = self.counts[i]
    77. self.counts[i] = lastcount
    78. lastcount = nextcount
    79. self.counts[len(self.value)-1] = lastcount
    80. else:
    81. self.value.append(value)
    82. self.counts[len(self.value)-1] = 0
    83. #容器元素清空,同时清空容器元素访问计数
    84. def clear(self):
    85. self.value.clear()
    86. self.counts.clear()
    87. #容器元素倒序,容器元素访问计数也跟随倒序移动
    88. def reverse(self):
    89. self.value.reverse()
    90. self.counts = dict(zip(list(self.counts.keys()),list(reversed(self.counts.values()))))
    91. #--------------------------实操验证-----------------------------
    92. #创建实例 list1
    93. list1 = Mylist(3,2,1,0,1,2,3)
    94. #检查实例的值和计数器初始化
    95. list1.value
    96. [3, 2, 1, 0, 1, 2, 3]
    97. list1.counts
    98. {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0}
    99. #读取实例 list1 各元素,检查计数器
    100. list1[1]
    101. 2
    102. list1[2]
    103. 1
    104. list1[2]
    105. 1
    106. list1[2]
    107. 1
    108. list1[4]
    109. 1
    110. list1[4]
    111. 1
    112. list1[5]
    113. 2
    114. list1[6]
    115. 3
    116. list1[6]
    117. 3
    118. list1.counts
    119. {0: 0, 1: 1, 2: 3, 3: 0, 4: 2, 5: 1, 6: 2}
    120. #按索引删除,检查值和计数器
    121. list1.pop(2)
    122. 1
    123. list1.value
    124. [3, 2, 0, 1, 2, 3]
    125. list1.counts
    126. {0: 0, 1: 1, 2: 0, 3: 2, 4: 1, 5: 2}
    127. #按值删除,检查值和计数器
    128. list1.remove(2)
    129. list1.value
    130. [3, 0, 1, 2, 3]
    131. list1.counts
    132. {0: 0, 1: 0, 2: 2, 3: 1, 4: 2}
    133. #清空容器,检查值和计数器
    134. list1.clear()
    135. list1.value
    136. []
    137. list1.counts
    138. {}
    139. #添加元素,并访问元素,检查值和计数器
    140. list1.append('a')
    141. list1.append(1)
    142. list1.append(2)
    143. list1.append('b')
    144. list1[2]
    145. 2
    146. list1[2]
    147. 2
    148. list1[1]
    149. 1
    150. list1.value
    151. ['a', 1, 2, 'b']
    152. list1.counts
    153. {0: 0, 1: 1, 2: 2, 3: 0}
    154. #倒序、正序插入元素,检查值和计数器
    155. list1.insert(-2,'xyz')
    156. list1.value
    157. ['a', 1, 'xyz', 2, 'b']
    158. list1.counter(3)
    159. 2
    160. list1.counts
    161. {0: 0, 1: 1, 2: 0, 3: 2, 4: 0}

    4. 迭代器

    迭代是重复反馈过程的活动,每一次对过程的重复称为一次“迭代”,而每一次迭代结果作为下一次迭代的初始值;

    提供迭代方法的容器称为可迭代对象,如常见的序列、字典都支持迭代操作,属于可迭代对象,即迭代器;

    譬如使用 for 循环对列表进行迭代操作如下:

    1. test = [1,2,3,4,5]
    2. for each in test:
    3. print(each)
    4. 1
    5. 2
    6. 3
    7. 4
    8. 5

    Python 提供两个BIF:iter()、next() 处理迭代操作,调用 iter() 得到它的迭代器,调用 next() 返回迭代器的下一个值,当迭代器没有值可以返回,抛出异常 StopIteration。

    1. #通过 iter() 获取列表 test 的迭代器 testiter
    2. testiter = iter(test)
    3. #testiter 为列表的迭代器对象
    4. testiter
    5. <list_iterator object at 0x00000160CE20F250>
    6. #通过 next() 获取迭代器的下一个值,直到抛出异常 StopIteration
    7. next(testiter)
    8. 1
    9. next(testiter)
    10. 2
    11. next(testiter)
    12. 3
    13. next(testiter)
    14. 4
    15. next(testiter)
    16. 5
    17. next(testiter)
    18. Traceback (most recent call last):
    19. File "<input>", line 1, in <module>
    20. StopIteration

    5. 定制迭代器

    iter()、next() 对应的魔法方法即 __iter__、__next__;

    __iter__(self) 

    返回一个带有 __next__ 方法的可迭代对象,一个容器如果本身即迭代器,常返回容器本身;

    通过 for 循环、next() 函数等方法调用迭代器的 __next__ 方法获取迭代器范围内的值,直到抛出 StopIteration 异常。

    __next__(self) 

    __next__ 方法决定了迭代操作的规则,是定制迭代器的关键;

    实操示例

    定义一个斐波那契数列类型:

    1. class Fibonacci:
    2. def __init__(self, f1=1, f2=1):
    3. self.f1 = f1
    4. self.f2 = f2
    5. def __iter__(self):
    6. return self
    7. def __next__(self):
    8. temp = self.f1
    9. self.f1 = self.f2
    10. self.f2 += temp
    11. if self.f1 > 100:
    12. raise StopIteration
    13. return self.f1
    14. ftest = Fibonacci()
    15. for each in ftest:
    16. print(each)
    17. 1
    18. 2
    19. 3
    20. 5
    21. 8
    22. 13
    23. 21
    24. 34
    25. 55
    26. 89
  • 相关阅读:
    做网赚的核心是:流量思维+变现思维
    《三叶虫与其他故事》我的恐惧如涟漪扩散,荡漾过百万年的时光
    Java Web 安全实战:从登录到退出
    【Linux】使用pip3安装pexpect,解决报错:the ssl module in Python is not available
    kerberos 客户端windows系统版本
    [2022-11-02] MacBook M1安装部署PyMuPDF教程
    数据结构-难点突破(线索化二叉树与遍历 C++中序线索化二叉树,前序线索二叉树,后序线索二叉树)
    机器人、控制领域顶级期刊
    Brew包的基本安装(手把手教学)
    一道题让你秒懂Java中静态代码块、构造代码块、构造方法、普通代码块、main函数的执行顺序
  • 原文地址:https://blog.csdn.net/davidksatan/article/details/125632149