迭代器与生成器 (02)


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

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

自定义迭代器协议

需求

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

方案

通过 生成器 函数来写逻辑, 数据结构用 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 __ 方法