通过yield来自定义迭代器协议, 如遍历顺序, 深度优先, 反向迭代 reversed 等.

这个周末就有点颓... 一方面是手里的工作问题 , 接了个大项目, 急着要上线, 搞得有点崩溃. 其实是想了关于自身职业, 工作技能, 已经再思考回老家的事情, 总是漂泊似乎有些难受的. 北京一年, 深圳两年, 倒是对漂泊有了一定的理解, 同时对自身工作技能, 技术, 行业, 兴趣上也都有一定认知. 都是快速学习的三年哇... 工作上其实做了一件事就是转行, 从文科转到理科, 并将其作为自己的工作, 这着实, 是一条艰辛的路, 而且还要一直走下去, 并一直践行着 "学无止境'', 也深信 "气有浩然"...

不扯了, 继续抄抄书, 很多技能, 知识, 其实都是重复, 反复加深认知的过程.

自定义迭代器协议

需求

构建一个支持迭代操作的对象, 并能自定义实现迭代协议, 即遍历元素的时候有指定的顺序来弄

方案

通过 生成器 函数来写逻辑, 数据结构用 list, 遍历协议(顺序) 以 深度优先 的方式遍历树形节点.

class Node:
def __init__(self, value):
self.value = value,
self.items = []

def __repr__(self):
return f'Node({self.value})'

def add_node(self, node):
self.items.append(node)

def depth_first(self):
"""深度优先-遍历"""
yield self.items
for node in self.items:
yield from node.depth_first()

# test
root = Node(0)
node_01 = Node(1)
node_02 = Node(2)

root.add_node(node_01)
root.add_node(node_02)

# 子节点, 嵌套, 树形结构
node_01.add_node(Node(3))
node_01.add_node(Node(4))
node_02.add_node(Node(5))

for node in root.depth_first():
# 每个节点是一个对象, 但只是想打印其值, 因此 __ repr__ 很关键
print(node)
[Node((1,)), Node((2,))]
[Node((3,)), Node((4,))]
[]
[]
[Node((5,))]
[]

我总是感觉, 这个深度优先写得有些问题, yield self 和 yield from ..我从来没有这样写过, 也没怎么见过, 果然还是我的道行太浅哇. 算了, 先不纠结了, 就当做伪代码来看了.

之前有谈过, 要实现迭代协议, 金属必须要实现 __ iter __ 方法来返回自身的迭代器对象 和 实现 __ next __ 方法 并通过 StopIteration 来捕捉异常,表示迭代的结束. 这里呢就来对上面代码进行改造, 即用一个关联迭代器类, 重新实现 depth_frist() 深度优先的 协议.

class Node2:
def __init__(self, value):
self.value = value
self.children = []

def __repr__(self):
return f'Node({self.value})'

def add_child(self, node):
self.children.append(node)

def __iter__(self):
return iter(self.children)

# 类之间的调用
def depth_first(self):
return DepthFirstIterator(self)

class DepthFirstIterator:
"""深度优先-遍历"""
def __init__(self, start_node):
self.node = start_node
self.children_iter = None
self.child_iter = None

def __iter__(self):
return self

def __next__(self):
if self.children_iter is None:
self.child_iter = iter(self.node)
return self.node

elif self.child_iter:
try:
next_child = next(self.child_iter)
return next_child
except StopIteration:
self.child_iter = None
return next(self)

else:
self.child_iter = next(self.children_iter).depth_first()
return next(self)

我平时也是很少这样用类之间的通信, 主要平时写的脚本太多了.... 面向对象都没怎么用过了. DepthFirstIterator 类和上面使用生成器的基本原理是差不多的, 但明显下面这个, 写起来就优先不那么直观了. 我自己都不太看得懂...所以可见, 熟练使用迭代器是多么的 简洁和优雅呢.

反向迭代序列

需求

反方向迭代一个序列, 类似字符串反转这种.

方案

通过用内置的 reversed( ) 函数, 或者切片 [:: -1] , 或者自己写一个都行的.

很奇怪的一点, 很多面试, 笔试就老喜欢考这种字符串反转, 或者字典按值排序, 我也真是服了...他们不知道 Python在这方面的轮子已经造好了嘛, 优雅而又简洁.

# 好多笔试都挺喜欢这样玩

text = "hello, world!"

# 字符串反转
print(text[::-1])

# 通常来个词频统计
words = {'abc':25, 'cdg': 14, 'mv': 1234, 'youge':666}

# 字典按值降序, 不用内置方法, 就自己写个排序, 粗暴写冒泡, 讲究写快排
sorted(words.items(), key=lambda arr: arr[1], reverse=True)
!dlrow ,olleh

[('mv', 1234), ('youge', 666), ('abc', 25), ('cdg', 14)]

用其他语言, 如我之前用 javascript 来做的, 确实有些麻烦的, 但是用 Python, 这种问题都是秒杀的. 当然这里还是为了突出 reversed ( ) 函数

lst = [1, 3, 4, 5]

for i in reversed(lst):
print(i)
5
4
3
1

其实, reversed( ) 也只是适用于实现了 __ reversed __ 的特殊方法才能生效的呢. 如果不是这样, 那就必须转换为一个序列呀, 比如, 转为一个列表就可以了.

with open('test.txt') as f: 
for line in reversed(list(f)):
print(line)
do you want to drink with me ? 
nice to see you

hello,

如果可迭代对象元素很多的话, 将其转为一个列表需要消耗大量的内存. 比如我最近做的一个列转行, 最后来了一个大list, 几 GB 呢, 我内存立马就飚上去了... 其实..还可以自定义 重写 __ reversed __ 方法来实现反向迭代.

class CountDown:
def __init__(self, start):
self.start = start

# 前向迭代. 前向, 后向, 似乎在写神经网络的 BP...
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1

# 反向迭代
def __reversed__(self):
n = 1
while n <= self.start:
yield n
n += 1

# test
print()
for i in CountDown(10):
print(i, end=' ')

print()
for j in reversed(CountDown(10)):
print(j, end=' ')
10 9 8 7 6 5 4 3 2 1 
1 2 3 4 5 6 7 8 9 10

小结

  • 对迭代的 __ iter __ 和 __ next ___ 结合 StopIteration 异常进行了回顾
  • 用迭代器 yield 写出来的代码会更加简洁和优雅, 可读性也很高, 首选哦
  • 反向迭代 revered() 函数, 的应用和重写, 用得多, 了解的少, 今天才真正去尝试重写 __ reversed __ 方法

耐心和恒心, 总会获得回报的.