Pythoncsv.DictReader如何反向输出?

2024-06-14 00:49:24 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试反转文件的读取方式。我用听写器是因为我想要字典里的内容。我想读取文件的第一行并将其用作键,然后以类似linux“tac”命令的反向(从下到上)解析文件。有没有一个简单的方法可以做到这一点?下面是我将文件读入字典并写入文件的代码。。。在

reader = csv.DictReader(open(file_to_parse, 'r'), delimiter=',', quotechar='"')
for line in reader:
    # ...

但是,此代码可以正常处理文件。。我需要它从头读文件。在

换言之,我希望它能阅读文件:

^{pr2}$

并且能够让它返回:

{' cars': ' chrysler', ' vegetables': ' corn', 'fruit': 'grape'}
{' cars': ' chevy', ' vegetables': ' celery', 'fruit': 'apple'}
{' cars': ' ford', ' vegetables': ' carrot', 'fruit': 'orange'}

而不是:

{' cars': ' ford', ' vegetables': ' carrot', 'fruit': 'orange'}
{' cars': ' chevy', ' vegetables': ' celery', 'fruit': 'apple'}
{' cars': ' chrysler', ' vegetables': ' corn', 'fruit': 'grape'}

Tags: 文件代码apple字典carsreaderceleryfruit
3条回答

实际上,您不需要将整个文件读入内存。在

csv.DictReader实际上并不需要文件,只需要一个字符串的iterable。*

你可以在平均线性时间内以相反的顺序读取一个文本文件,而不需要太多的开销。这不是小事,但也不是那么难:

def reverse_lines(*args, **kwargs):
    with open(*args, **kwargs) as f:
        buf = ''
        f.seek(0, io.SEEK_END)
        while f.tell():
            try:
                f.seek(-1024, io.SEEK_CUR)
            except OSError:
                bufsize = f.tell()
                f.seek(0, io.SEEK_SET)
                newbuf = f.read(bufsize)
                f.seek(0, io.SEEK_SET)
            else:
                newbuf = f.read(1024)
                f.seek(-1024, io.SEEK_CUR)
            buf = newbuf + buf
            lines = buf.split('\n')
            buf = lines.pop(0)
            yield from reversed(lines)
        yield buf

这不是经过严格测试的,它去掉了新行(这对csv.DictReader很好,但一般来说不好),而且它没有针对异常但可能的边缘情况进行优化(例如,对于非常长的行,它将是二次的),并且它需要Python3.3,并且在关闭/释放迭代器之前文件不会消失(可能应该是这样)一个上下文管理器,这样你就可以处理这个问题了),但是如果你真的想要这个,我敢打赌你可以在ActiveState或PyPI上找到一个没有这些问题的方法。在

不管怎样,对于一个中等大小的文件,我怀疑在几乎任何实际的文件系统上,以正向顺序将整个文件读入内存,然后反向迭代列表,这实际上会更快。但是对于一个非常大的文件(尤其是一个你甚至无法放入内存的文件),这个解决方案显然要好得多。

在我的电脑上,通过一个快速测试(代码见http://pastebin.com/Nst6WFwV),基本的故障是:

  • 对于文件<;<;1000行,速度要慢得多。在
  • 从1K-1M线路下降约10%。在
  • 30米左右的交叉线。在
  • 500米线路速度提高50%。在
  • 1.5G线路速度快1300%。在
  • 实际上,在2.5G行上速度无限快(列表反转版本将我的机器扔进交换地狱,我必须通过ssh来终止进程,并等待几分钟才能恢复……)。在

当然,细节将取决于你的电脑的许多事实。这可能不是巧合,500M72字符行的ASCII占用了我机器上接近一半的物理RAM。但是使用硬盘驱动器而不是SSD,您可能会看到更多的惩罚reverse_lines(因为随机查找比连续读取慢得多,而且磁盘通常更重要)。平台的malloc和VM行为,甚至是位置问题(几乎在读取一行代码后立即对其进行解析,而不是在它被换出和返回之后……)可能会产生影响。等等。在

总之,教训是,如果您不希望至少有100万行代码(或者在资源受限的机器上可能会少一点),那么就不要考虑这个问题;只要保持简单就行了。在


*正如Martijn Pieters在评论中指出的,如果您不使用显式的fieldnamesDictReader需要一个字符串iterable,其中第一行是头。但是,您可以通过使用csv.reader分别读取第一行并将其作为fieldnames传递,或者甚至可以通过itertools.chain-在除向后读取的最后一行之外的所有前向读取中的所有第一行来修复该问题。

您必须将整个CSV文件读入内存;您可以通过在reader对象上调用list()来完成此操作:

with open(file_to_parse, 'rb') as inf:
    reader = csv.DictReader(inf, skipinitialspace=True)
    rows = list(reader)

for row in reversed(rows):

请注意,我在这里将该文件用作上下文管理器,以确保该文件已关闭。您还希望以二进制模式打开文件(将换行处理留给csv模块)。传递给DictReader()的其余配置是默认配置,所以我省略了它们。在

我将skipinitialspace设置为True,根据示例输入和输出判断,定界符后面确实有空格;该选项将删除这些空格。在

csv.DictReader()对象负责将第一行作为键读取。在

演示:

^{pr2}$

读一个列表,然后倒转:

lines = [x for x in reader]
for line in lines[::-1]:
    print line

{' cars': ' chrysler', ' vegetables': ' corn', 'fruit': 'grape'}
{' cars': ' chevy', ' vegetables': ' celery', 'fruit': 'apple'}
{' cars': ' ford', ' vegetables': ' carrot', 'fruit': 'orange'}

或者像马蒂恩·皮特斯建议的那样:

^{pr2}$

相关问题 更多 >