【问题标题】:How to parse very big files in python?如何在python中解析非常大的文件?
【发布时间】:2018-11-23 06:31:39
【问题描述】:

我有一个非常大的 tsv 文件:1.5 GB。我想解析这个文件。我使用以下功能:

def readEvalFileAsDictInverse(evalFile):
  eval = open(evalFile, "r")
  evalIDs = {}
  for row in eval:
    ids = row.split("\t")
    if ids[0] not in evalIDs.keys():
      evalIDs[ids[0]] = []
    evalIDs[ids[0]].append(ids[1])
  eval.close()


  return evalIDs 

这需要 10 多个小时,它仍在工作。我不知道如何加快这一步,是否还有其他方法可以解析,例如文件

【问题讨论】:

标签: python bigdata


【解决方案1】:

这里有几个问题:

  • if ids[0] not in evalIDs.keys() 测试键在python 2 中需要永远,因为keys()list.keys() 无论如何都很少有用。更好的方法已经是if ids[0] not in evalIDs,但是,但是...
  • 为什么不改用collections.defaultdict
  • 为什么不使用csv 模块?
  • 覆盖 eval 内置(好吧,看看它有多危险,这并不是一个真正的问题)

我的建议:

import csv, collections

def readEvalFileAsDictInverse(evalFile):
  with open(evalFile, "r") as handle:
     evalIDs = collections.defaultdict(list)
     cr = csv.reader(handle,delimiter='\t')
     for ids in cr:
        evalIDs[ids[0]].append(ids[1]]

如果 list 尚不存在,魔法 evalIDs[ids[0]].append(ids[1]] 会创建一个 list。无论python版本如何,它都是可移植的且非常快,并保存if

我认为使用默认库不会更快,但 pandas 解决方案可能会。

【讨论】:

    【解决方案2】:

    一些建议:

    使用defaultdict(list) 而不是自己创建内部列表使用dict.setdefault()

    dict.setfdefault() 每次都会创建默认值,这是一个时间燃烧器 - defautldict(list) 没有 - 它已被优化:

    from collections import defaultdict    
    def readEvalFileAsDictInverse(evalFile):
      eval = open(evalFile, "r")
      evalIDs = defaultdict(list)
      for row in eval:
        ids = row.split("\t")
        evalIDs[ids[0]].append(ids[1])
      eval.close()
    

    如果您的密钥是有效的文件名,您可能需要调查awk 以获得更高的性能,然后在 python 中执行此操作。

    类似

    awk -F $'\t' '{print > $1}' file1
    

    将更快地创建您的拆分文件,您可以简单地使用以下代码的后半部分从每个文件中读取(假设您的密钥是有效的文件名)来构建您的列表。 (署名:here) - 您需要使用os.walk 或类似方式获取您创建的文件。文件中的每一行仍然是制表符分隔的,并在前面包含 ID


    如果您的密钥本身不是文件名,请考虑将不同的行存储到不同的文件中,并且只保留 key,filename 的字典。

    拆分数据后,再次将文件加载为列表:

    创建测试文件:

    with open ("file.txt","w") as w:
    
        w.write("""
    1\ttata\ti
    2\tyipp\ti
    3\turks\ti
    1\tTTtata\ti
    2\tYYyipp\ti
    3\tUUurks\ti
    1\ttttttttata\ti
    2\tyyyyyyyipp\ti
    3\tuuuuuuurks\ti
    
        """)
    

    代码:

    # f.e. https://stackoverflow.com/questions/295135/turn-a-string-into-a-valid-filename
    def make_filename(k):
        """In case your keys contain non-filename-characters, make it a valid name"""          
        return k # assuming k is a valid file name else modify it
    
    evalFile = "file.txt"
    files = {}
    with open(evalFile, "r") as eval_file:
        for line in eval_file:
            if not line.strip():
                continue
            key,value, *rest = line.split("\t") # omit ,*rest if you only have 2 values
            fn = files.setdefault(key, make_filename(key))
    
            # this wil open and close files _a lot_ you might want to keep file handles
            # instead in your dict - but that depends on the key/data/lines ratio in 
            # your data - if you have few keys, file handles ought to be better, if 
            # have many it does not matter
            with open(fn,"a") as f:
                f.write(value+"\n")
    
    # create your list data from your files:
    data = {}
    for key,fn in files.items():
        with open(fn) as r:
            data[key] = [x.strip() for x in r]
    
    print(data)
    

    输出:

    # for my data: loaded from files called '1', '2' and '3'
    {'1': ['tata', 'TTtata', 'tttttttata'], 
     '2': ['yipp', 'YYyipp', 'yyyyyyyipp'], 
     '3': ['urks', 'UUurks', 'uuuuuuurks']}
    

    【讨论】:

    • 为什么要创建defaultdict 并测试密钥?
    • @Jean-FrançoisFabre 复制和粘贴错误 - 感谢您指出
    【解决方案3】:
    1. evalIDs 更改为collections.defaultdict(list)。您可以避免使用if 来检查是否存在密钥。
    2. 考虑在外部使用split(1) 或什至在python 内部使用读取偏移量来分割文件。然后使用multiprocessing.pool 并行加载。

    【讨论】:

    • 您能否提供更多详细信息。我对 multiprocessing.pool 没有任何想法
    • 如果 I/O 是瓶颈,则并行加载不会有任何好处
    • 如果 I/O 是瓶颈,那么是的,它不会有太大的好处,但除了每个人都建议的 defaultdict 之外,这是我能想到的唯一值得尝试的另一件事。
    • @bib 文件中所有行的长度都一样吗?
    【解决方案4】:

    也许,你可以让它更快一些;改变它:

    if ids[0] not in evalIDs.keys():
          evalIDs[ids[0]] = []
    evalIDs[ids[0]].append(ids[1])
    

    evalIDs.setdefault(ids[0],[]).append(ids[1])
    

    第一种解决方案在“evalID”字典中搜索 3 次。

    【讨论】:

    • setdefault 比默认字典慢。 timeit.timeit(lambda : d.setdefault('x',[]).append(1)) 报告0.4583683079981711timeit.timeit(lambda : c['x'].append(1)) 报告0.28720847200020216 其中d{}ccollections.defaultdict(list)。所有答案都建议如此。你为什么选择这个作为正确的?这里的解决方案不如提到的其他解决方案。
    • 我无法测量显着差异(Python 3.7.1),但 OP 应该测量它。
    • 在我的测量中,defaultdict 的速度大约是 setdefault 的两倍。 (3.5.3),我认为这是合理的,因为 setdefault 每次调用它时都会评估它的参数(每次都会创建一个新的空列表)。
    猜你喜欢
    • 2018-12-01
    • 1970-01-01
    • 2015-09-22
    • 2013-02-28
    • 2013-03-24
    • 2010-09-25
    • 1970-01-01
    • 2015-08-26
    相关资源
    最近更新 更多