【问题标题】:Python: What's a fast way to read and split a file?Python:读取和拆分文件的快速方法是什么?
【发布时间】:2013-05-21 08:54:51
【问题描述】:

我需要读取一个文件并将其分成几行,并用制表符将这些行分成两半,以及去掉所有的语音标记。目前我有一个工作功能。但是,它相当慢:

temp = []
fp = open(fName, "r")
for line in fp:
    temp.append(line.replace("\"","").rstrip("\n").split("\t"))
print temp

这会将文件拆分为列表列表。它实际上可能只是一个列表,因为只要保留顺序,以后就可以很容易地将其重新分成几对。

必须有更快的方法来做到这一点。谁能让我走上正轨?

谢谢!

[编辑] 我正在使用的文件很大,但我会添加类似的内容。 (有没有办法在堆栈溢出时上传文件?)

"CARMILLA"  "35"
"JONATHAN R"    "AA2"
"M" "3"
"EMMA"  "350"
"OLD"   "AA"

应该返回:

["CARMILLA", "35", "JONATHON R", "AA2", "M", "3", "EMMA", "350", "OLD", "AA"]

虽然我的代码将它作为 2 个字符串的列表返回,但这也很好。

抱歉,我可能应该注意到 print 语句代表 return 语句 - 因为我把它从一个函数中取出,所以我把它改成了 print,所以在这里更有意义。

【问题讨论】:

  • 一个示例文件和输出将帮助我们创建答案(用于测试)
  • 当然,我会添加一个。
  • 如果你想要的只是一个打印输出,你可以在你的 for 循环中打印而不是附加到列表中。
  • 您在寻找csv吗?但我不确定性能。
  • 您基于什么假设阅读和拆分“相当慢”?你是怎么测量的?

标签: python list file-io split


【解决方案1】:

我认为列表理解比为每一行调用 .append 更快

from itertools import chain
with open('file.txt') as f:
    lines = chain.from_iterable([l.replace(r'"','').rstrip('\n').split('\t',1) for l in f])

编辑: 所以它会生成一个扁平化列表

>>> 
['CARMILLA', '35', 'JONATHAN R', 'AA2', 'M', '3', 'EMMA', '350', 'OLD', 'AA']

非扁平化版本:

with open('file.txt') as f:
    lines = [l.replace(r'"','').rstrip('\n').split('\t',1) for l in f]

有些时间,原来 OP 是最快的?

import timeit
print("chain, list",timeit.timeit(r"""
with open('file.txt') as f:
    lines = chain.from_iterable([l.replace(r'"','').rstrip('\n').split('\t',1) for l in f])""",setup="from itertools import chain",number=1000))
print("flat       ",timeit.timeit(r"""
with open('file.txt') as f:
    lines = [l.replace(r'"','').rstrip('\n').split('\t',1) for l in f]""",setup="from itertools import chain",number=1000))
print("op's       ",timeit.timeit(r"""temp = []
fp = open('file.txt', "r")
for line in fp:
    temp.append(line.replace("\"","").rstrip("\n").split("\t"))
""",number=1000))
print("jamlyks    ",timeit.timeit(r"""
with open('file.txt', 'rb') as f:
    r = csv.reader(f, delimiter=' ', skipinitialspace=True)
    list(chain.from_iterable(r))""",setup="from itertools import chain; import csv",number=1000))
print("lennart    ",timeit.timeit(r"""
    list(csv.reader(open('file.txt'), delimiter='\t', quotechar='"'))""",setup="from itertools import chain; import csv",number=1000))

产量

C:\Users\Henry\Desktop>k.py
('chain, list', 0.04725674146159321)
('my flat    ', 0.04629905135295972)
("op's       ", 0.04391255644624917)
('jamlyks    ', 0.048360870934994915)
('lennart    ', 0.04569112379085424)

【讨论】:

  • chain.from_iterable 和生成器表达式可以为您节省一些标点符号 - lines = chain.from_iterable(l.replace('"', '')... for l in f)。此外,无需使用原始字符串 - 它与没有 ``. 的字符串没有任何区别
  • chain 返回一个迭代器。 list() 周围会列出一个列表。
【解决方案2】:

通过将temp.append 替换为temp.extend,您将获得单层列表而不是列表列表。

【讨论】:

  • 我只想发表评论,但没有所需的权限。
  • 我会给你这个特权
  • 谢谢,我试试看。
【解决方案3】:

如果您知道每一行只有一个\t,则可以使用split("\t",1)rsplit("\t",1) 来避免扫描整行以查找制表符。

split 之后的strip('"')replace("\"","") 之前split 的一种可能的替代方案。如果它更快,请尝试。

但是您是否计算过仅使用file.read() 读取文件需要多长时间?与此相比,分裂所花费的时间真的很重要吗?

【讨论】:

  • 谢谢!应该阅读文档;我什至不知道你能做到这一点。
  • 这点很好,谢谢。也许分裂不是我的问题。
【解决方案4】:

您应该首先弄清楚您真正的瓶颈是什么。只需读取文件而不构建结果列表。只需在拆分时打印每一行,而不是打印到控制台(速度很慢),而是打印到一个新文件中。我敢打赌它已经快得多了。所以在我看来(没有真正的一天就无法测试)你的问题不在于阅读和拆分部分。这就是你之后要做的事情。试试看。如何进一步优化取决于您的具体用例。

更新:

鉴于您的示例数据,您可以试试这个:

import itertools
print list(itertools.chain(
    *( line.strip().split('\t') for line in file('sample.txt') )
))

它正在为您的数据生成一个生成器。 print list(...) 仅用于打印并与您的示例保持一致。在现实世界的应用程序中,您可能不会创建列表。而是将数据写入它应该去的地方或进一步处理它。

更新2:

如果你想去掉引号并且你确定每个部分都有引号,你可以使用x[1:-1]。或者你可以使用x.strip('"'),如果你想确定的话。但不需要使用正则表达式。

【讨论】:

    【解决方案5】:
    Benchmarks on a 2mb file:
    
    __author__ = 'robert'
    
    from timeit import timeit
    
    os_cached = open("data.csv").read()
    
    
    def test_one():
        result = [line.split("\t") for line in open("data.csv").read().splitlines()]
    
    def test_two():
        for line in open("data.csv"):
            line.split("\t")
            yield line
    
    def test_three():
        for line in open("data.csv").read().splitlines():
            line.split("\t")
            yield line
    
      def test_four():
        from itertools import chain
        with open('data.csv') as f:
            lines = chain.from_iterable([l.replace(r'"','').rstrip('\n').split('\t',1) for l in f])
            return lines
    
    print timeit("test_one()", setup="from __main__ import test_one", number=195)
    print timeit("for line in test_two(): pass", setup="from __main__ import test_two", number=195)
    print timeit("for line in test_three(): pass", setup="from __main__ import test_three", number=195)
    print timeit("for line in test_four(): pass", setup="from __main__ import test_four", number=195)
    
    
    
    7.34187420441
    6.22663840184
    6.60748983698
    10.6207058679
    

    【讨论】:

    • splitlines() 将首先在内存中创建整个列表,而不是内存效率。
    • 您必须在内存中构建一个完整的列表,这需要时间并且...消耗大量内存。为什么应该更快?
    • 目前他正在将其附加到一个列表并打印出该列表。
    • 我会做一些基准测试。上次我检查它是最快的
    • @robertking 重点不是由列表解析创建的列表,而是由file_pointer.read().split_lines() 创建的临时列表(实际上是临时字符串)。
    【解决方案6】:

    像这样,例如:

    >>> import csv
    >>> reader = csv.reader(open('testfile'), delimiter='\t', quotechar='"')
    >>> list(reader)
    [['CARMILLA', '35'], ['JONATHAN R', 'AA2'], ['M', '3'], ['EMMA', '350'], ['OLD', 'AA']]
    

    【讨论】:

    • @HennyH:引用 OP:“虽然我的代码将它作为 2 个字符串的列表返回,但这也很好。”所以不,它不需要被展平。
    【解决方案7】:
    from itertools import chain
    import csv
    
    with open('data.txt', 'rb') as f:
        r = csv.reader(f, delimiter=' ', skipinitialspace=True)
        print list(chain.from_iterable(r))
    

    ['CARMILLA', '35', 'JONATHAN R', 'AA2', 'M', '3', 'EMMA', '350', 'OLD', 'AA']
    

    【讨论】:

      【解决方案8】:

      使用regex 和列表理解:

      import re
      with open("abc") as f:
          lis = [x.group(1) for line in f for x in \
                                   re.finditer(r'"([a-zA-Z0-9\s]+)"', line) ]
          print lis
      

      输出:

      ['CARMILLA', '35', 'JONATHAN R', 'AA2', 'M', '3', 'EMMA', '350', 'OLD', 'AA']
      

      如果制表符分隔值的数量不多,则使用re.findall()

      lis =  [y for line in f for y in re.findall(r'"([a-zA-Z0-9\s]+)"', line)]
      

      或使用itertools.chain:

      lis =  list(chain(*(re.findall(r'"([a-zA-Z0-9\s]+)"', line) for line in f)))
      

      【讨论】:

      • 如果要消耗整个迭代器,列表版re.findall会更快
      • @jamylak 是的,但它会首先在内存中创建整个列表。
      • 是的,但是行很短,这只会引入很多开销
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-13
      • 2021-12-08
      • 2011-12-23
      • 2013-10-24
      • 1970-01-01
      • 2014-10-29
      相关资源
      最近更新 更多