使用itertools的迭代器正在跳过一行

时间:2014-04-29 09:14:01

标签: python csv for-loop iterator itertools

我觉得我的问题与Why does takewhile() skip the first line?

有关

我在那里找不到满意的答案。

以下示例使用以下模块

import csv
from itertools import takewhile

这是我的问题。 我有一个csv文件,我想用itertools解析。

例如,我想将标题与内容分开。 第一列中存在关键字就会发现这一点。

以下是file.csv示例

a, content
b, content
KEYWORD, something else
c, let's continue

两个第一行组成文件的标题。 KEYWORD行将其与内容分开:最后一行。

即使如果它不是内容的正确部分,我也想解析分隔行。

with open('file.csv', 'rb') as f:
    reader = csv.reader(f)
    header = takewhile(lambda x: x[0] != 'KEYWORD', reader)
    for row in header:
        print(row)
    print('End of header')
    for row in reader:
        print(row)

我没想到会这样,但会跳过KEYWORD行。 正如您将在以下输出中看到的那样:

['a', ' content']
['b', ' content']
End of header
['c', " let's continue"]

我已经尝试过模拟csv阅读器,看它是否来自那里。 但显然不是。 以下代码产生相同的行为。

l = [['a', 'content'],
    ['b','content'],
    ['KEYWORD', 'something else'],
    ['c', "let's continue"]]

i = iter(l)
header = takewhile(lambda x: x[0] != 'KEYWORD', i)
for row in header:
    print(row)
print('End of header')
for row in i:
    print(row)

如何使用takewhile的功能,同时防止以下内容跳过未解析的行?

据我所知,第一个for在迭代器上调用next来测试其内容。 第二次再次呼叫next,以收集价值。 因此跳过了分隔行。

4 个答案:

答案 0 :(得分:2)

我认为你必须进行重组 - takewhile并不适合你正在做的事情。问题是takewhile必须读取'KEYWORD'开始的行,以确定它已经到达了它不应该采取的行,并且一旦读取了该行文件"读头"是在下一行的开头。同样,对于itertakewhile已经消耗(但丢弃)当您启动'KEYWORD'时开始for row in i的行。

一种替代方案可能是:

header = []
content = []
target = header
for row in reader:
    if line.startswith('KEYWORD'):
        target = content
    target.append(row)

答案 1 :(得分:0)

感谢@jonrsharpe,我开始质疑自己的一些代码技巧。 这是我达到的目的:

class RewindableFile(file):
    def __init__(self, *args, **kwargs):
        nb_backup = kwargs.pop('nb_backup', 1)
        super(RewindableFile, self).__init__(*args, **kwargs)
        self._nb_backup = nb_backup
        self._backups = []
        self._time_anchor = 0

    def next(self):
        if self._time_anchor >= 0:
            item = super(RewindableFile, self).next()
            self._backup(item)
            return item
        else:
            item = self._forward()
            return item

    def rewind(self):
        self._time_anchor = self._time_anchor - 1
        time_bound = min(self._nb_backup, len(self._backups))
        if self._time_anchor < -time_bound:
            raise Exception('You have gone too far in history...')

    def __iter__(self):
        return self

    def _backup(self, row):
        self._backups.append(row)
        extra_items = len(self._backups) - self._nb_backup
        if extra_items > 0:
            del self._backups[0:extra_items]

    def _forward(self):
        item = self._backups[self._time_anchor]
        self._time_anchor = self._time_anchor + 1
        return item

以及我如何使用它:

with RewindableFile('csv.csv', 'rb') as f:
    def test_kwd_and_rewind(x):
        if x[0] != 'KEYWORD':
            return True
        else:
            f.rewind()
            return False

    reader = csv.reader(f)
    header = takewhile(test_kwd_and_rewind, reader)
    for row in header:
        print(row)
    print('End of header')
    for row in reader:
        print(row)

我还可以重载readreadline函数来保存jump。 但我在这里不需要它们。

答案 2 :(得分:0)

你可以像这样自己写。

def takewhile(predicate, iterable):
    for x in iterable:
        yield x
        if not predicate(x):
            break

试验:

>>> list(takewhile(lambda x:x!=3, range(10)))
[0, 1, 2, 3]

答案 3 :(得分:0)

jonrsharpe说得对。这不是一个需要的工作。 itertools还有一个groupby函数,可以更轻松地处理拆分。下面的LastHeader类保留了通过check方法传递的最后一个标题行的记录,并在每次调用check时返回对它的引用。 这使您可以一次浏览文件,而无需回溯任何文件。

class LastHeader():
    """Checks for new header strings. For use with groupby"""
    def __init__(self, sentinel='#'):
        self.sentinel = sentinel
        self.lastheader = ''

    def check(self, line):
        if line.startswith(self.sentinel):
            self.lastheader = line
        return self.lastheader

with open(fname, 'r') as fobj:
    lastheader = LastHeader(sentinel)
    for headerline, readlines in groupby(fobj, lastheader.check):
        foo(headerline)
        for line in readlines:
            bar(line)

其中foobar是您需要对标头和数据进行的任何处理。