迭代器与生成器 (05)


这周不断优化和调试 sql, 经过精简和改逻辑, sql 也写了一千多行了, 这是数据处理部分, 然后这部分需要做调度 ETL, 生成宽表. 前台部分的 sql 也有几百行, 终于初步上线了, 剩下一些前台的样式慢慢调整, 感觉是接了一个大项目哇, 全部用sql来完成的, 感觉自己 sql 一下就突飞猛进了, 随心所欲地写, 过程中,不断要处理一些数据, 也是灵活用Python 安排上. Python + SQL 简直无敌, 在数据分析这块, 目前我是这样觉得.

SQL 方面, 主要是在服务器上跑那种比较大量的数据, 几百, 几千万这种, 同时在熟悉sql后, 发现果然万物也皆sql. 对于一些查询集处理, 或者线下数据处理, Python 就无敌强大, 随心所欲. 然后再 BI 安排上, 全套服务.

补一个关于数据分析的话题, 最近也面试了好几个求职的:

  • 都是停留在 "器物" 的层面, 什么会 sql, python ...结果简单的 group by 都写不出来, 太菜
  • 觉得数据分析简单, 也很无语. 要分析, 首先要能处理, 要处理, 首先要会 sql 和编程, 然后还要了解业务, 还要能做BI.. 真的是有点难的

然后, 还是抽空把剩下的一点点迭代器的内容, 赶紧来安排一下.

然后, 还是抽空把剩下的一点点迭代器的内容, 赶紧来安排一下.

管道 (Pipeline) 方式处理数据

需求

想以类似 Linux 的方式, 迭代处理数据. 如处理一个很大的文件, 要分批, 不能够一次性读入内存中哦

方案

生成器函数 yield 安排上.

通常场景是多个路径下, 多个压缩文件夹, 下有多个文件, 各种乱七八糟格式的. 为了处理这些文件呢, 可以定义一个有多个执行任务的简单生成器函数的容器.

def gen_open(file_names): 
    """open a sequence of filenames one at a time producing a file object
    the file is closed immediately when proceeding to the next iteration"""
    
    for file_name in file_names:
        if file_name.endswith('.gz'):
            f = gzip.open(file_name, 'rt')
        
        elif file_name.endswith('.bz2'):
            f = bz2.open(file_name, 'rt')
    
        else:
            f = open(file_name, 'rt')
        
        yield f 
        f.close()

def gen_concatenate(iterators): 
    """chain a sequence of iterators together into a sigle sequence"""
    for it in iterators:
        yield from it 
    
def gen_grep(pattern, lines):
    """look of a regex pattern in a sequence of lines"""
    pat  = re.compile(pattern)
    for line in lines:
        if pat.sreach(line):
            yield line 
    

虽然我平时不咋用这个, 但我总感觉迭代器这些东西蛮高级的, 比如 yield, 我现在写函数, 就优先会想, 能不能用 yield 来代替 return 等...

平铺 (Flatten) 嵌套序列

需求

要将一个多层嵌套的序列, 展开为一个单层列表. 这个就很常用了, 比如咱熟悉的 神经网络, 输入层就是要先将多维矩阵平铺为一个 1 维向量输入呀.

方案

用 yield from 语句, 写一个递归生成器来轻松实现.

#  yield from 

from collections import Iterable 

def flatten(items, ignore_types=(str, bytes)):
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):
            yield from flatten(x)
        else:
            yield x 
            
# test
items = [1, [2,2], [3,[4,[5,6,[5]]]]]
for i in flatten(items):
    print(i, end=',')
1,2,2,3,4,5,6,5,

这种骚操作, 我平时就会接触很多了. isinstance(x, Iterable) 用来检查某个元素是否为可迭代的. 如果是 True 的话, yield from 就会返回所有子例程的值. 即一个没有嵌套的简单序列.

参数 ignore_types 和检测语句 isinstance (x, ignore_types) 用来将字符串和字节排除在外, 防止再展开为单个字符.

words = ['youge', ['adore', 'you'], 'at', ['this',['time']]]
for word in flatten(words):
    print(word)
youge
adore
you
at
this
time

语句 yield from 在我们想在生成器中调用其他其他生成器,作为子例程的是否非常有用的. 它可以代替额外的 for 循环.

# if not use yield from 
def flatten(items, ignore_types=(str, bytes)):
    for x in items:
         if isinstance(x, Iterable) and not isinstance(x, ignore_types):
                for i in flatten(x):
                    yield i 
        else:
            yield x 

这样就会多写一个 for, 显然不如上者简洁和优雅. 还有就是, yield from 在涉及到, 基于微线程和生成器的并发编程中更有大作为, 后面抽空给补充上来.