python cookbook 学习笔记 第四章 迭代器和生成器(13) 创建数据处理管道

  • 创建数据处理管道
  • 问题:
    • 想以数据管道(类似Unix管道,但是Unix管道又是什么鬼)的方式迭代处理数据。比如,有大量的数据需要处理,但是不 能将他们一次性放入内存中。
  • 解决方案:
    • 生成器函数是一个实现管道机制的好办法。假定要处理一个非常大的日志文件:
"""
foo/
access-log-012007.gz
access-log-022007.gz
access-log-032007.gz
...
access-log-012008
bar/
access-log-092007.bz2
...
access-log-022008
"""
  • 假设每个日志文件包含这样的数据:
"""
124.115.6.12 - - [10/Jul/2012:00:18:50 -0500] "GET /robots.txt ..." 200 71
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /ply/ ..." 200 11875
210.212.209.67 - - [10/Jul/2012:00:18:51 -0500] "GET /favicon.ico ..." 404 369
61.135.216.105 - - [10/Jul/2012:00:20:04 -0500] "GET /blog/atom.xml ..." 304 -
...
"""
  • 为了处理这些文件,可以定义一个由多个执行特定独立任务的简单生成器函数组成这样的容器。比如:
import os
import fnmatch
import gzip
import bz2
import re

def gen_find(filepat, top):

    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist, filepat):
            yield os.path.join(path, name)

def gen_opener(filenames):

    for filename in filenames:
        if filename.endswith(".gz"):
            f = gzip.open(filename, "rt")
        elif filename.endswith(".bz2"):
            f = bz2.open(filename, "rt")
        else:
            f = open(filename, "rt")
        yield f
        f.close()

def gen_concatenate(iterators):

    for it in iterators:
        yield from it

def gen_grep(pattern, lines):
    pat = re.compile(pattern)
    for line in lines:
        if pat.search(line):
            yield line

lognames = gen_find("access-long*", "www")
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep("(?i)python", lines)
for line in pylines:
    print(line)
  • 如果将来想扩展管道,甚至可以再生成器表达式中包装数据。比如,下面这个版本计算出传输的字节数,并计算其总和。
lognames = gen_find("access-log*", "www")
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines= gen_grep("(?i)", lines)
bytecolumn = (line.rsplit(None, 1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != "-")
print("Total", sum(bytes))
  • 讨论: 以管道方式处理数据可以用来解决各类其他问题,包括解析,读取实时数据,定时轮询等。
  • 为了理解上述代码,重点要明白 yield 语句作为数据的生产者,而for循环语句作为数据的消费者。当这些生成器被 连在一起后,每个 yield 会将一个单独的数据元素传递给迭代处理管道的下一阶段。在例子的最后部分,sum()函数 是最终的程序驱动者,每次从生成器管道中提取出一个元素。
  • 这种方式一个非常好的特点是每个生成器函数很小,并且都是独立的。这样的话就容易编写和维护。很多时候,这些函数 如果比较通用的话,可以在其他场景里重复使用,并且最终将这些组件组合起来的代码看上去非常简单,也很容易理解。
  • 使用这种方式的内存效率也很高,事实上,由于使用了迭代方式处理,代码运行过程中只需要很小很小的内存。
  • gen_concatenate()函数的目的是将输入序列拼接成一个很长的行序列。 itertools.chain()函数同样有类似的 功能,但是他需要将所有可迭代对象作为参数传入。在上面这个例子中, 你可能会写类似这样的语句 lines =itertools.chain(*files),使得 gen_opener()生成器被全部消费掉。但 由于 gen_opener()生成器每次生成一个打开过的文件,等下一次迭代步骤时,文件就关闭了。因此,chain()在这里 不能这样用。上面的方案可以避免这种情况发生。
  • 最后,管道方式不是万能的。有时候想立即处理所有数据。然而,即便是这种情况,使用生成器管道也可以将这类问题从 逻辑上变成工作流的处理方式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值