如何在Python中逐行读取大文件

时间:2011-11-04 13:26:29

标签: python file-read

我想迭代整个文件的每一行。一种方法是通过读取整个文件,将其保存到列表中,然后浏览感兴趣的行。这种方法使用了大量内存,所以我正在寻找替代方案。

到目前为止我的代码:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

执行此代码会显示错误消息:device active

有什么建议吗?

目的是计算成对的字符串相似度,意味着对于文件中的每一行,我想要计算每隔一行的Levenshtein距离。

11 个答案:

答案 0 :(得分:1202)

正确的,完全Pythonic的方式来读取文件如下:

with open(...) as f:
    for line in f:
        # Do something with 'line'

with语句处理打开和关闭文件,包括是否在内部块中引发异常。 for line in f将文件对象f视为可迭代,它自动使用缓冲的I / O和内存管理,因此您不必担心大文件。

  

应该有一种 - 最好只有一种 - 显而易见的方法。

答案 1 :(得分:113)

排名顺序中的两种内存有效方式(首先是最好的) -

  1. 使用with - 支持python 2.5及以上版本
  2. 使用yield如果您真的想控制阅读量
  3. 1。使用with

    with是读取大文件的好用且高效的pythonic方式。优点 - 1)退出with执行块后,文件对象自动关闭。 2)with块内的异常处理。 3)memory for循环逐行遍历f文件对象。在内部它确实缓冲IO(在昂贵的IO操作上优化)和内存管理。

    with open("x.txt") as f:
        for line in f:
            do something with data
    

    2。使用yield

    有时人们可能希望对每次迭代中读取的内容进行更精细的控制。在这种情况下,请使用iter& yield。注意这个方法明确需要在最后关闭文件。

    def readInChunks(fileObj, chunkSize=2048):
        """
        Lazy function to read a file piece by piece.
        Default chunk size: 2kB.
        """
        while True:
            data = fileObj.read(chunkSize)
            if not data:
                break
            yield data
    
    f = open('bigFile')
    for chuck in readInChunks(f):
        do_something(chunk)
    f.close()
    

    陷阱和为了完整性 - 以下方法对于阅读大文件不是那么优雅或不优雅,但请阅读以全面理解。

    在Python中,从文件中读取行的最常用方法是执行以下操作:

    for line in open('myfile','r').readlines():
        do_something(line)
    

    然而,当这样做时,readlines()函数(同样适用于read()函数)将整个文件加载到内存中,然后迭代它。对于大文件,稍微更好的方法(首先提到的两种方法是最好的)是使用fileinput模块,如下所示:

    import fileinput
    
    for line in fileinput.input(['myfile']):
        do_something(line)
    

    fileinput.input()调用按顺序读取行,但在读取之后不会将它们保留在内存中,甚至只是这样,因为python中的file是可迭代的。

    参考

    1. Python with statement

答案 2 :(得分:35)

删除换行符:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

使用universal newline support所有文本文件行似乎都会以'\n'终止,无论文件中的终结符是'\r''\n'还是'\r\n'

编辑 - 指定通用换行支持:

  • Unix上的Python 2 - open(file_path, mode='rU') - 必需 [thanks @Dave]
  • Windows上的Python 2 - open(file_path, mode='rU') - 可选
  • Python 3 - open(file_path, newline=None) - 可选

newline参数仅在Python 3中受支持,默认为None。在所有情况下,mode参数默认为'r'。在Python 3中不推荐使用U。在Windows上的Python 2中,似乎有一些其他机制可以将\r\n转换为\n

文档:

保留本机行终止符:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

二进制模式仍然可以将文件解析为in行。每行都有它在文件中的任何终结符。

感谢@katrielalexanswer,Python的open() doc和iPython实验。

答案 3 :(得分:16)

这是一种在python中读取文件的可能方法:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

它没有分配完整列表。它遍布各行。

答案 4 :(得分:9)

关于我来自哪里的一些背景。代码片段结尾。

如果可以的话,我更喜欢使用像H2O这样的开源工具来执行超高性能并行CSV文件读取,但此工具在功能集中受到限制。我最终编写了大量代码来创建数据科学管道,然后再投入H2O集群进行监督学习。

通过在多处理库的池对象和地图功能中添加大量并行性,我一直在阅读来自UCI repo的8GB HIGGS数据集等文件,甚至用于数据科学目的的40GB CSV文件。例如,使用最近邻搜索进行聚类以及DBSCAN和Markov聚类算法需要一些并行编程技巧来绕过一些严重具有挑战性的内存和挂钟时间问题。

我通常喜欢首先使用gnu工具将文件逐行拆分为部分,然后将它们全部文件掩码,以便在python程序中并行查找和读取它们。我常常使用1000多个部分文件。这些技巧有助于极大地提高处理速度和内存限制。

pandas dataframe.read_csv是单线程的,因此您可以通过运行map()进行并行执行来完成这些技巧,以使pandas更快。您可以使用htop查看使用普通旧的顺序pandas dataframe.read_csv,只有一个核心上的100%cpu是pd.read_csv中的实际瓶颈,而不是磁盘。

我应该在快速视频卡总线上使用SSD,而不是在SATA6总线上使用旋转高清,加上16个CPU内核。

另外,我发现另一种在某些应用程序中运行良好的技术是并行CSV文件在一个巨大的文件中读取所有文件,以不同的偏移量将每个工作者启动到文件中,而不是将一个大文件预分割成许多零件文件。在每个并行工作程序中使用python的文件seek()和tell()以条带形式读取大文本文件,同时在大文件中的不同字节偏移的起始字节和结束字节位置读取。您可以对字节执行正则表达式查找,并返回换行计数。这是一个部分总和。最后总结部分和,以便在工人完成后地图函数返回时获得全局总和。

以下是使用并行字节偏移技巧的一些示例基准:

我使用2个文件:HIGGS.csv是8 GB。它来自UCI机器学习库。 all_bin .csv是40.4 GB,来自我当前的项目。 我使用了2个程序:Linux附带的GNU wc程序,以及我开发的纯python fastread.py程序。

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

这大约是4.5 GB / s,或45 Gb / s,文件啜泣速度。我的朋友,那不是没有旋转的硬盘。那实际上是三星Pro 950 SSD。

以下是gnu wc(纯C编译程序)对同一文件进行行计数的速度基准。

很酷的是你可以看到我的纯python程序在这种情况下基本上与gnu wc编译的C程序的速度相匹配。 Python被解释但C编译,所以这是一个非常有趣的速度壮举,我想你会同意。当然,wc确实需要改为并行程序,然后它真的会击败我的python程序。但就目前而言,gnu wc只是一个顺序程序。你尽你所能,今天python可以做到并行。 Cython编译可能能够帮助我(在其他时间)。还没有探索内存映射文件。

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

结论:与C程序相比,纯python程序的速度很快。但是,使用纯python程序而不是C程序是不够的,至少对于行计数目的而言。通常这种技术可以用于其他文件处理,所以这个python代码仍然很好。

问题:仅仅编译一次正则表达式并将其传递给所有工作人员会提高速度吗?答:正则表达式预编译对此应用程序没有帮助。我想原因是所有工人的流程序列化和创建的开销占主导地位。

还有一件事。 并行CSV文件读取是否有帮助?磁盘是瓶颈,还是CPU?他们说,stackoverflow上的许多所谓的顶级答案包含了共同的开发智慧,你只需要一个线程来读取文件,你可以做到最好。他们确定吗?

让我们找出:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

哦,是的,是的。并行文件读取效果很好。那你去吧!

聚苯乙烯。如果你们中的一些人想知道,如果使用单个工作流程时balanceFactor是2,该怎么办?嗯,这太可怕了:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

fastread.py python程序的关键部分:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

PartitionDataToWorkers的def只是普通的顺序代码。我把它留了下来以防其他人想要在并行编程方面做一些练习。为了您的学习成果,我免费赠送了更难的部分:经过测试和运行的并行代码。

感谢:由Arno和Cliff以及H2O工作人员提供的开源H2O项目,他们提供了出色的软件和教学视频,为我提供了如上所示的纯python高性能并行字节偏移阅读器的灵感。 H2O使用java进行并行文件读取,可以通过python和R程序调用,并且在读取大型CSV文件时比在地球上的任何东西都快,疯狂。

答案 5 :(得分:5)

Katrielalex提供了开放和开放的方式。读一个文件。

然而,算法运行的方式是读取文件每行的整个文件。这意味着如果N是文件中的行数,则读取文件的总量 - 并计算Levenshtein distance - 将完成N * N.由于您关注文件大小并且不想将其保留在内存中,因此我担心生成的quadratic runtime。您的算法采用O(n ^ 2)类算法,通常可以通过专业化进行改进。

我怀疑你已经知道了内存与运行时间的权衡,但是你可能想要研究是否有一种有效的方法来并行计算多个Levenshtein距离。如果是这样,在这里分享您的解决方案会很有趣。

你的文件有多少行,你的算法必须运行什么样的机器(mem& cpu power),以及运行时容忍的是什么?

代码如下:

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

但问题是你如何存储距离(矩阵?)并且你可以获得准备例如用于处理的outer_line,或缓存一些中间结果以供重用。

答案 6 :(得分:3)

#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • 打开文件阅读(r)
  • 阅读整个文件并将每一行保存到列表(文本)
  • 遍历列表打印每一行。

例如,如果您想检查长度大于10的特定行,请使用您已有的行。

for line in text:
    if len(line) > 10:
        print line

答案 7 :(得分:2)

来自fileinput的python文档.input():

  

这会迭代sys.argv[1:]中列出的所有文件的行,如果列表为空则默认为sys.stdin

此外,该功能的定义是:

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

在行之间阅读,这告诉我files可以是一个列表,所以你可以有类似的东西:

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

有关详细信息,请参阅here

答案 8 :(得分:2)

我强烈建议不要使用默认文件加载,因为它非常慢。您应该查看numpy函数和IOpro函数(例如numpy.loadtxt())。

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

然后你可以将成对操作分解为块:

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

以块为单位加载数据然后对其进行矩阵运算几乎总是要快得多,而不是逐个元素地执行!

答案 9 :(得分:0)

需要从上次读取位置中频繁读取大文件吗?

我创建了一个脚本,用于每天多次切割Apache access.log文件。 因此,我需要在上次执行期间解析的最后一行上设置位置光标。 为此,我使用了file.seek()file.seek()方法,这些方法允许将游标存储在文件中。

我的代码:

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))

答案 10 :(得分:-2)

逐行读取大文件的最佳方法是使用python 枚举功能

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line