【问题标题】:Python writelines() and write() huge time differencePython writelines() 和 write() 巨大的时间差
【发布时间】:2017-06-15 06:56:05
【问题描述】:

我正在编写一个脚本,该脚本读取文件文件夹(每个文件的大小从 20 MB 到 100 MB 不等),修改每行中的一些数据,然后写回文件的副本。

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.writelines('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

在使用 90 MB 文件(约 900,000 行)运行此代码时,它会打印 140 秒作为写入文件的时间。这里我使用了writelines()。所以我搜索了不同的方法来提高文件写入速度,在我阅读的大多数文章中,它说write()writelines() 应该没有任何区别,因为我正在编写单个连接字符串。我还检查了仅以下语句所花费的时间:

new_string = '\n'.join(new_my_list) + '\n'

而且只用了 0.4 秒,所以耗时大不是因为创建列表。 只是为了尝试write() 我尝试了这段代码:

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.write('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

它打印了 2.5 秒。为什么write()writelines() 的文件写入时间差异如此之大,即使它们是相同的数据?这是正常行为还是我的代码有问题?两种情况的输出文件似乎都是一样的,所以我知道数据没有丢失。

【问题讨论】:

  • 赞成找到一种扭曲的方式来使用具有预期结果的 writelines 并发现一个意想不到的警告。
  • 我的 clean_data() 函数也删除了每一行,因此删除了额外的换行符。

标签: python performance file file-writing


【解决方案1】:

file.writelines() 需要一个 iterable 字符串。然后它继续循环并为可迭代中的每个字符串调用file.write()。在 Python 中,该方法执行以下操作:

def writelines(self, lines)
    for line in lines:
        self.write(line)

您传入的是一个大字符串,而字符串也是字符串的可迭代对象。迭代时,您会得到单个字符,长度为 1 的字符串。因此,实际上您是在对 file.write() 进行单独的调用 len(data)。这很慢,因为您一次只构建一个字符的写入缓冲区。

不要将单个字符串传递给file.writelines()。改为传入列表或元组或其他可迭代对象。

您可以在生成器表达式中添加换行符发送单独的行,例如:

 myWrite.writelines(line + '\n' for line in new_my_list)

现在,如果您可以将clean_data() 设为生成器,生成干净的行,您可以将数据从输入文件流式传输,通过数据清理生成器,然后输出到输出文件,而无需使用任何比读取和写入缓冲区所需的内存更多,但清理行需要多少状态:

with open(inputPath, 'r+') as myRead, open(outPath, 'w+') as myWrite:
    myWrite.writelines(line + '\n' for line in clean_data(myRead))

此外,我会考虑更新 clean_data() 以发出包含换行符的行。

【讨论】:

  • myWrite.writelines('\n'.join(my_list) + '\n') 可能只是 myWrite.writelines("{}\n".format(x) for x in my_list) 这样会更快;没有要建立的列表。
  • @Jean-FrançoisFabre:这就是为什么我声明要传入列表或元组或其他可迭代。 :-)
  • @Jean-FrançoisFabre:这可能只是一种节省内存的措施,因为缓冲区仍然连接这些行直到它满了。如果clean_data() 是一个生成器会有所帮助。
  • 感谢@MartijnPieters 我想我现在对 python 认为的可迭代对象有了更好的理解。到目前为止,我的 clean_data 从输入文件中获取所有行的列表,对每一行进行更改,并返回修改后的行列表。清理每一行并立即写入会更有效,还是像我目前在代码中所做的那样将这些行收集到一个列表中并将它们全部一起写入?
  • @ArjunBalgovind:在阅读时清理每一行会更有效率,然后使用yield 将结果传递到下一步。如果文件足够大,内存效率可以转化为整体性能提升(因为内存分配也需要时间,并且您希望避免内存争用),并且 I/O 缓慢可以消除小文件的性能差异。
【解决方案2】:

作为对 Martijn 答案的补充,最好的方法是避免首先使用 join 构建列表

只需将生成器推导传递给writelines,在最后添加换行符:没有不必要的内存分配和循环(除了推导)

myWrite.writelines("{}\n".format(x) for x in my_list)

【讨论】:

    【解决方案3】:

    'write(arg)' 方法需要字符串作为其参数。所以一旦调用,就会直接写入。这就是它更快的原因。 就好像您使用writelines() 方法一样,它需要字符串列表作为迭代器。因此,即使您将数据发送到writelines,它也会假定它有迭代器并尝试对其进行迭代。因此,由于它是一个迭代器,因此需要一些时间来迭代并编写它。

    清楚吗?

    【讨论】:

    • 但它仍然是一个字符串,不是吗?它会迭代1个值吗?这将如何影响写入速度?
    • 是的,你可能想建议类似myWrite.writelines(['\n'.join(my_list) + '\n'])
    • @ArjunBalgovind:单个字符串是单独字符的可迭代。
    • @mgilson myWrite.writelines(['\n'.join(my_list) + '\n']) 和 myWrite.write() 一样好。我现在明白为什么 writelines 这么慢了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 2013-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多