迭代器与生成器 (01)

感觉是这一整周都没有学习和复习了, 一直在忙最近的一个报表开发, 数据处理真的是不容易, 涉及10多张线上, 线下的表, 数据量还挺大, 从 mysql 迁到了 IQ... 主要是数据处理都是要依靠 sql 来进行, 比如去括号呀, 匹配呀这类的... 贼难受, 最有意思的一点是关于匹配, 要进行 4轮匹配, 再全部 union 起来... 虽然性能不太好, 要反复去查表, 但终究功能是可以的, 也看到 sql 的查询强大之处. 相比 Python, 我觉得对于线上大数据量的数据集, 处理就不好弄了, 用 Pandas 读几百万表到 内存 ?? 这就有点难搞, 我也是渐渐觉得, 数据分析, BI, 越发感到 sql 比 Python 更加重要一点, 我当然是两者配合用的呀, 都熟练的.

然后, 还是来继续抄抄书, 学无止境嘛...

不用 for 遍历迭代器

迭代器在 Python 中, 我觉得特别重要, 因为但凡你开始写代码, 都要用到它哇.

我之前有专门做了笔记关于迭代器: https://www.cnblogs.com/chenjieyouge/p/12274459.html

  • 迭代, 是一个动词, 用谓语. 对一个 集合 对象进行 遍历 的过程就是迭代 (for 循环)
  • 可迭代对象, 能够被 for 循环遍历的对象, 即其构造过程 实现了 __ next __ 方法 和 __ iter __ 方法

这里就不再解释什么指针, 万物皆对象这些原理了, 总之, __ next __ 方法使当前指针, 指向下一个元素的地址, 而 __ iter __ 方法, 返回 当前元素值, 这就是 for 遍历的原理.

需求

遍历一个可迭代对象的所有元素, 但就是不让用 for 循环

方案

用 for 循环的原理, 即 __ next __ 方法 或者 内置 next( ) 函数, 二者其实一样的, 我现更偏 对象.__ next __ ( ) 一些

lst = [1,2,'youge']

it  = iter(lst)


# iter() 变为可迭代对象, __next__() 我觉得会更好理解一些
print(it.__next__())

print(next(it)) 

print(it.__next__())
print(it.__next__())
1
2
youge

# 遍历完了再就会报错哦
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-3-4e885b436bf8> in <module>
      6 print(next(it))
      7 print(it.__next__())
----> 8 print(it.__next__())

StopIteration: 

再以一个文件对象举例, 遍历每行, 用 __ next __ ( ) 遍历, 并捕获 StepIteration 异常.

def manual_iter():
    with open("./test.txt") as f:
        try:
            while True:
                line = f.__next__()
                print(line)
        except StopIteration:
            pass
            print("over")

# test
manual_iter()
hello, 

  nice to see you 

do you want to drink with me ? 

over

用这种 try..except 来捕捉 StopIteration 的方式, 我工作中是从来没这用过的, 更多是通过 if 的方式来 break .

# 用 next() 是可以传参的

with open('test.txt') as f:
    while True:
        line = next(f, None)
        if line is None:
            break
        print(line, end = '')
hello, 
  nice to see you 
do you want to drink with me ? 

自定义可迭代的容器

简单理解容器对象, 就 list, tuple, dict, str, zip, enumerate 这些.... 可用来装数据的 "容器"

需求

构建一个自定义容器, 里面包含有列表, 元组等可迭代对象, 并实现在这容器上进行迭代操作

方案

通过在创建类的时候, 实现 __ iter __ 和 __ next __ 方法即可. 当然, 这里的底层结构用 list .

class Node:
    def __init__(self, value):
        self._value = value, 
        self._items = []
        self._index = 0
        
    def __repr__(self):
        '''打印对象时自动触发, 将任意一个对象,包裹为字符串, 跟 eval() 相逆'''
        return f'Node({self._value})'
    
    def app_item(self, node):
        '''添加元素节点'''
        self._items.append(node)
        
    def __iter__(self):
        """返回自己"""
        return iter(self._items)
    
    def __next__(self):
        """返回当前节点值, 并指向下一个元素节点"""
        if self.index < len(self.items):
            cur_value = self.items[self.index]
            
            # 获取当前值后, 指针再往后移动一次
            self.index += 1
        else:
            raise StopIteration
        return cur_value
        
    
# test 
root = Node(0)
node_01 = Node(1)
node_02 = Node(2)
node_03 = Node(3)

root.app_item(node_01)
root.app_item(node_02)
root.app_item(node_03)

# 能够 for 的前提是实现了 __iter__ 和 __next__ 方法哦
for node in root:
    print(node)
    
    
Node((1,))
Node((2,))
Node((3,))

用生成器创建自定义迭代模式

需求

实现一个自定义迭代模式, 跟普通的内置函数如 range(), reversed() 不一样的方式

方案

用生成器 (元组推导式 或 yield ) 来实现, 这里先来写一个生成摸个范围内浮点数的生成器.

def my_range(start, stop, increment):
    '''生成某个范围内的浮点数'''
    while start < stop:
        yield start 
        start += increment
        
# test
for i in my_range(0, 1, 0.25):
    print(i)
    
print(list(my_range(0, 1, 0.15)))
0
0.25
0.5
0.75

[0, 0.15, 0.3, 0.44999999999999996, 0.6, 0.75, 0.9]

反正我个人是越来越喜欢用生成器了, 尤其在写函数的时候, 优先考虑用 yield 而不是 return , 其好处在于更加优雅, 同时节省内存, 且后面还可以运行代码, 简直大爱. 一个函数中, 如果出现 yield 语句, 则将其转为了生成器. 跟普通函数不同在于, 生成器只能用于迭代操作, 不会终止函数, 而 return 则彻底结束函数运行.

def count_down(n):
    print('Start to count from', n)
    while n > 0:
        yield n 
        n -= 1
    print('Done!')
    
# test 
c = count_down(3)

print(c)

print(next(c))
print(next(c))
print(c.__next__())
print(next(c))
<generator object count_down at 0x000002070EE99DB0>
Start to count from 3
3
2
1
Done!
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-28-865f6082c7dc> in <module>
     14 print(next(c))
     15 print(next(c))
---> 16 print(next(c))

StopIteration: 

一个生成器函数的主要特征是它会回应在迭代中使用到的 next 或者, 一旦生成器返回退出, 则迭代终止. 通常的做法是用 用 for 去进行迭代, 不会出现报错的细节呢.

因为, for 的原理, 是一个迭代器的工作过程, 判断是否可迭代, 然后调用 __ next __ 不断取数, 直到异常 StopIteration.

小结

  • 复习了一把迭代, 可迭代对象, 迭代器, 集合对象, 序列对象, 容器等基础概念
  • 迭代的原理, 即是实现了 __ iter __ 和 __ next __ () 方法, 即将返回自身可迭代对象和, 返回当前值并指向下next 节点
  • 函数中用 yield 是真的香, 结合 for 遍历的原理, 不断调用 __ next__ 和 捕捉 StopIteration 异常
posted @ 2020-06-13 18:19  致于数据科学家的小陈  阅读(184)  评论(0编辑  收藏  举报