581

我想遍历整个文件的每一行。一种方法是读取整个文件,将其保存到列表中,然后遍历感兴趣的行。此方法使用大量内存,因此我正在寻找替代方法。

到目前为止我的代码:

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 距离。

4

11 回答 11

1344

读取文件的正确的、完全 Pythonic 的方法如下:

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

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

应该有一种——最好只有一种——明显的方法来做到这一点。

于 2011-11-04T13:46:44.787 回答
144

按排名顺序的两种内存有效方式(第一个是最好的) -

  1. 使用with- 从 python 2.5 及更高版本支持
  2. yield如果您真的想控制阅读量,请使用

1.使用with

with是读取大文件的一种不错且高效的 Pythonic 方式。优点 - 1) 文件对象在退出with执行块后自动关闭。2)块内的异常处理with。3) 内存循环逐行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 chunk 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()调用按顺序读取行,但在读取它们之后不会将它们保存在内存中,甚至只是这样,因为file在 python 中是可迭代的。

参考

  1. Python with 语句
于 2011-11-04T13:31:42.240 回答
38

去除换行符:

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

有了通用换行符支持,所有文本文件行似乎都以 . 结尾'\n',无论文件中的终止符是'\r'、、'\n'还是'\r\n'

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

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

newline参数仅在 Python 3 中受支持,默认为None. 该参数在所有情况下都mode默认为。'r'UPython 3 中已弃用。在 Windows 上的 Python 2 中,一些其他机制似乎可以转换\r\n\n.

文件:

要保留本机行终止符:

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

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

感谢@katrielalex回答、Python 的open()文档和iPython实验。

于 2015-09-15T15:07:52.737 回答
17

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

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

它没有分配一个完整的列表。它遍历线条。

于 2011-11-04T13:33:37.617 回答
13

关于我来自哪里的一些背景信息。代码片段在最后。

如果可以的话,我更喜欢使用像 H2O 这样的开源工具来进行超高性能的并行 CSV 文件读取,但是这个工具在功能集上是有限的。我最终编写了大量代码来创建数据科学管道,然后再将其馈送到 H2O 集群以进行适当的监督学习。

我一直在读取来自 UCI 存储库的 8GB HIGGS 数据集,甚至是用于数据科学目的的 40GB CSV 文件,通过使用多处理库的池对象和映射函数添加大量并行性,速度明显更快。例如,最近邻搜索的聚类以及 DBSCAN 和 Markov 聚类算法需要一些并行编程技巧来绕过一些严重具有挑战性的内存和挂钟时间问题。

我通常喜欢首先使用 gnu 工具将文件按行分成几部分,然后将它们全部 glob-filemask 以在 python 程序中并行查找和读取它们。我通常使用 1000 多个部分文件。使用这些技巧可以极大地提高处理速度和内存限制。

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

我应该补充一下,我在快速视频卡总线上使用 SSD,而不是在 SATA6 总线上使用旋转 HD,以及 16 个 CPU 内核。

此外,我发现在某些应用程序中效果很好的另一种技术是并行 CSV 文件读取一个大文件中的所有内容,以不同的偏移量启动每个工作程序到文件中,而不是将一个大文件预先拆分为多个部分文件。在每个并行工作程序中使用 python 的文件 seek() 和 tell() 以条状读取大文本文件,在大文件中不同的字节偏移开始字节和结束字节位置,同时同时进行。您可以对字节执行正则表达式查找,并返回换行数。这是部分金额。最后在worker完成后map函数返回时将部分和相加得到全局和。

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

我使用 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 的文件 slurping 速度。那不是没有旋转的硬盘,我的朋友。那实际上是三星 Pro 950 SSD。

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

很酷的是,在这种情况下,您可以看到我的纯 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程序的速度是好的。但是,在 C 程序上使用纯 python 程序还不够好,至少对于行计数目的。一般来说,该技术可以用于其他文件处理,所以这个python代码还是不错的。

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

还有一件事。并行 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 只是普通的顺序代码。我把它留了下来,以防其他人想练习一下并行编程是什么样的。我免费赠送了更难的部分:经过测试和工作的并行代码,以供您学习。

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

于 2017-02-02T16:48:32.087 回答
5

Katrielalex 提供了打开和读取一个文件的方法。

但是,您的算法采用的方式是读取文件每一行的整个文件。这意味着如果 N 是文件中的行数,则读取文件的总量 - 并计算Levenshtein 距离- 将完成 N*N。由于您担心文件大小并且不想将其保存在内存中,因此我担心产生的二次运行时。您的算法属于 O(n^2) 类算法,通常可以通过专门化来改进。

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

您的文件有多少行,您的算法必须在哪种机器(内存和 cpu 功率)上运行,以及允许的运行时间是多少?

代码如下所示:

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)

但问题是您如何存储距离(矩阵?),您能否获得优势,例如准备用于处理的外线,或缓存一些中间结果以供重用。

于 2011-11-04T14:09:14.623 回答
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) 

以块的形式加载数据然后对其执行矩阵运算几乎总是比逐个元素地执行要快得多!

于 2014-10-17T19:39:11.767 回答
2

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

这会遍历 中列出的所有文件的行,如果列表为空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)

请参阅此处了解更多信息

于 2011-11-04T13:32:05.290 回答
2

需要经常从最后一个位置读取大文件?

我创建了一个脚本,用于每天多次剪切 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()))
于 2020-01-07T13:18:47.143 回答
2
#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
于 2016-07-30T02:01:01.027 回答
-3

逐行读取大文件的最佳方法是使用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
于 2017-08-24T07:02:13.733 回答