【问题标题】:How to loop through two generators of the same opened file如何遍历同一个打开文件的两个生成器
【发布时间】:2019-02-25 13:07:03
【问题描述】:

我有一个中等大小的文件(25MB,1000000 行),我想读取除每第三行之外的每一行。

第一个问题:将整个文件加载到内存中然后读取行(方法.read())是否更快,或者一次加载并读取一行(方法.readline()) ?

由于我不是经验丰富的编码员,我尝试了第二个选项,使用 itertools 模块中的 islice 方法。

import intertools

with open(input_file) as inp:
    inp_atomtype = itertools.islice(inp, 0, 40, 3)
    inp_atomdata = itertools.islice(inp, 1, 40, 3)
    for atomtype, atomdata in itertools.zip_longest(inp_atomtype, inp_atomdata):
        print(atomtype + atomdata)

虽然循环通过单个生成器(inp_atomtypeinp_atomdata)打印正确的数据,但同时循环通过它们(如在此代码中)打印错误的数据。

第二个问题:如何使用生成器到达所需的行?

【问题讨论】:

  • 听起来像是 x y 问题,你想解决什么问题?
  • 根据atomtype的值我要正确对待atomdata变量。

标签: python generator itertools


【解决方案1】:

你不需要对迭代器进行切片,一个简单的行计数器就足够了:

with open(input_file) as f:
    current_line = 0
    for line in f:
        current_line += 1
        if current_line % 3:  # ignore every third line
            print(line)  # NOTE: print() will add an additional new line by default

至于把它变成一个生成器,只需yield 行而不是打印。

在速度方面,鉴于您无论如何都会读取行数,I/O 部分可能会花费相同的时间,但您可能会从快速列表切片而不是计算行数中受益一点(总处理时间),如果您有足够的工作内存来保存文件内容,并且如果预先加载整个文件而不是 流式传输 是可以接受的。

【讨论】:

    【解决方案2】:

    yield 非常适合。

    这个函数从一个可迭代对象中产生对,并跳过每三个项目:

    def two_thirds(seq):
        _iter = iter(seq)
        while True:
            yield (next(_iter), next(_iter))
            next(_iter)
    

    你将失去半对,这意味着two_thirds(range(2)) 将立即停止迭代。

    https://repl.it/repls/DullNecessaryCron

    您也可以使用itertools doc 中的 grouper 配方并忽略生成的每个元组中的第三项:

    for atomtype, atomdata, _ in grouper(lines, 3):
        pass
    

    【讨论】:

    • 这是一个简单的解决方案,我可以同时达到两个变量。
    【解决方案3】:

    第一个问题:我很确定 .readline() 比 .read() 快。另外,根据我的测试,最快的方法是像这样做 lopping:

    with open(file, 'r') as f:
        for line in f:
            ...
    

    第二个问题:我不太确定这个问题。您可以考虑使用产量。

    有一个代码sn-p你可以参考:

    def myreadlines(f, newline):
        buf = ""
        while True:
            while newline in buf:
                pos = buf.index(newline)
                yield buf[:pos]
                buf = buf[pos + len(newline):]
            chunk = f.read(4096)
    
            if not chunk:
            # the end of file
                yield buf
                break
            buf += chunk
    
    with open("input.txt") as f:
        for line in myreadlines(f, "{|}"):
            print (line)
    

    【讨论】:

      【解决方案4】:

      q2:这是我的生成器:

      def yield_from_file(input_file):
          with open(input_file) as file:
              yield from file
      
      def read_two_skip_one(gen):
          while True:
              try:
                  val1 = next(gen)
                  val2 = next(gen)
                  yield val1, val2
                  _ = next(gen)
              except StopIteration:
                  break
      
      if __name__ == '__main__':
          for atomtype, atomdata in read_two_skip_one(yield_from_file('sample.txt')):
              print(atomtype + atomdata)
      

      sample.txt 是使用 bash shell 生成的(它只是数到 100 的行)

      for i in {001..100}; do echo $i; done > sample.txt
      

      关于 q1:如果您要多次读取文件,最好将其保存在内存中。否则你可以逐行阅读。

      关于您遇到的错误结果问题:

      两个itertools.islice(inp, 0, 40, 3) 语句都将使用inp 作为生成器。两者都会调用next(inp),为您提供价值。 每次您在迭代器上调用 next() 时,它都会更改其状态,这就是您的问题所在。

      【讨论】:

      • 您可以调用next(gen)而不将其分配给_
      • 我知道,这是一种习惯 :D 一些 linter 抱怨不使用值。
      【解决方案5】:

      您可以使用生成器表达式:

      with open(input_file, 'r') as f:
          generator = (line for e, line in enumerate(f, start=1) if e % 3)
      

      enumerate 将行号添加到每一行,if 子句忽略可被 3 整除的行号(默认编号从 0 开始,因此您必须指定 start=1 才能获得所需的模式)。

      请记住,您只能在文件仍处于打开状态时使用生成器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多