如何循环通过同一打开文件的两个生成器

时间:2018-09-21 07:07:37

标签: python generator itertools

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

第一个问题:将整个文件加载到内存中然后读取行(方法.read())还是一次加载并读取一行(方法{{ 1}})?

由于我不是经验丰富的编码人员,因此我尝试使用.readline()模块中的islice方法的第二种选择。

itertools

尽管循环通过单个生成器(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_atomtype)可打印正确的数据,但同时循环通过两个生成器(如本代码所示)则会打印错误的数据。

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

5 个答案:

答案 0 :(得分: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部分可能会花费相同的时间,但是您可能会受益于快速列表切片(总处理时间),而不是如果您有足够的工作内存来保留文件内容,并且如果可以预先加载整个文件而不是 streaming 是可以接受的。

答案 1 :(得分:0)

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()时,它都会更改其状态,这就是问题的出处。

答案 2 :(得分:0)

第一个问题:我很确定.readline()比.read()更快。另外,基于我的测试的最快方法是像

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

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

您可以参考以下代码段:

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)

答案 3 :(得分:0)

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中的石斑鱼食谱,而忽略每个生成的元组中的第三项:

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

答案 4 :(得分:0)

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

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才能获得所需的行号)模式)。

请记住,您只能在文件仍打开的情况下使用生成器。