如何在python中解析非常大的文件?

Posted

技术标签:

【中文标题】如何在python中解析非常大的文件?【英文标题】:How to parse very big files in 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 多个小时,它仍在工作。我不知道如何加快这一步,是否还有其他方法可以解析,例如文件

【问题讨论】:

可能与***.com/questions/17444679/reading-a-huge-csv-file重复 什么操作系统? 我正在使用 130 GB 作为 ram 的 linux 服务器。 @GuillaumeJacquenot 不,一点也不。 OP 没有先读这些行。 @bib 退一步,你打算用字典eval做什么? 【参考方案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://***.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】:
    evalIDs 更改为collections.defaultdict(list)。您可以避免使用if 来检查是否存在密钥。 考虑在外部使用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 其中dccollections.defaultdict(list)。所有答案都建议如此。你为什么选择这个作为正确的?这里的解决方案不如提到的其他解决方案。 我无法测量显着差异(Python 3.7.1),但 OP 应该测量它。 在我的测量中,defaultdict 的速度大约是 setdefault 的两倍。 (3.5.3),我认为这是合理的,因为 setdefault 每次调用它时都会评估它的参数(每次都会创建一个新的空列表)。

以上是关于如何在python中解析非常大的文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 python 中加载大的 .mat 文件?

如何使用 Python 比较 2 个非常大的矩阵

如何在python的内存中保存一个非常大的字典?

在 php 中解析非常大的 XML 文件

如何在目标c中逐行解析JSON文件

如何使用python有效地填充给定一个非常大的表的矩阵?