如何在Python中便宜地获取大文件的行数?


1009

我需要在python中获取一个大文件(数十万行)的行数。内存和时间最有效的方法是什么?

目前,我这样做:

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

有可能做得更好吗?


7
您需要精确的行数还是近似值就足够了?
pico

43
我会在for循环前添加i = -1,因为此代码不适用于空文件。
Maciek Sawicki 2011年

12
@Legend:我敢打赌pico在想,获取文件大小(用seek(0,2)或equiv),然后除以近似的行长。您可以在开始时阅读几行,以猜测平均行长。
安妮

32
enumerate(f, 1)并抛弃i + 1
伊恩·麦金农

4
@IanMackinnon适用于空文件,但是您必须在for循环之前将i初始化为0
scai

Answers:


356

没有比这更好的了。

毕竟,任何解决方案都必须读取整个文件,找出有多少文件\n,然后返回该结果。

您是否有一种更好的方法,而无需读取整个文件?不确定...最好的解决方案将永远是受I / O约束的,您可以做的最好的事情是确保您不使用不必要的内存,但是看起来您已经解决了这一问题。


7
确实,甚至WC都在读取文件,但是在C语言中,它可能已经非常优化。
奥拉维尔Waage

6
据我了解,Python文件IO也是通过C完成的。docs.python.org/library/stdtypes.html#file-objects
Tomalak,2009年

9
@Tomalak这是一条红鲱鱼。虽然python和wc可能发出相同的syscall,但python具有wc所没有的操作码调度开销。
bobpoekert

4
您可以通过采样来估算行数。它可以快数千倍。请参阅:documentroot.com/2011/02/...
埃里克Aronesty

4
其他答案似乎表明该分类答案是错误的,因此应删除而不是保留为已接受。
Skippy le Grand Gourou

623

一行,可能非常快:

num_lines = sum(1 for line in open('myfile.txt'))

8
它类似于每行的总和(序列1),>>> [[range(10)中的行为1]] [1、1、1、1、1、1、1、1、1、1] >>> sum(范围(10)中的行1)10 >>>
James Sapam 2013年

4
num_lines = sum(1为open('myfile.txt')中的行,如果line.rstrip()为过滤空行
Honghe.Wu 2014年

61
当我们打开文件时,一旦遍历所有元素,该文件会自动关闭吗?是否需要'close()'?我认为我们不能在此简短声明中使用'with open()',对吗?
Mannaggia 2014年

16
@Mannaggia,您是正确的,最好使用'with open(filename)'以确保完成后文件关闭,甚至更好的是在try-except块中执行此操作,如果并抛出IOError异常,该文件无法打开。
BoltzmannBrain 2015年

17
需要注意的另一件事:这比原始问题在30万行文本文件上显示的时间慢约0.04-0.05秒
andrew

202

我相信内存映射文件将是最快的解决方案。我尝试了四个函数:OP(opcount)发布的函数;文件(simplecount)中各行的简单迭代;带有内存映射字段(mmap)的readline(mapcount); 以及Mykola Kharechko(bufcount)提供的缓冲区读取解决方案。

我对每个函数运行了五次,并计算了120万行文本文件的平均运行时间。

Windows XP,Python 2.5、2GB RAM,2 GHz AMD处理器

这是我的结果:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

编辑:Python 2.6的数字:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

因此,缓冲区读取策略似乎对于Windows / Python 2.6是最快的

这是代码:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    f = open(filename, "r+")
    buf = mmap.mmap(f.fileno(), 0)
    lines = 0
    readline = buf.readline
    while readline():
        lines += 1
    return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    return lines

def opcount(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))

1
整个内存映射文件未加载到内存中。您将获得一个虚拟内存空间,操作系统可以根据需要将其交换到RAM中或从其中交换出去。这是在Windows上处理它们的方式:msdn.microsoft.com/zh-cn/library/ms810613.aspx
Ryan Ginstrom 09年

1
抱歉,这是有关内存映射文件的更一般的参考:en.wikipedia.org/wiki/Memory-mapped_file感谢您的投票。:)
瑞安·金斯特罗姆

1
即使它只是一个虚拟内存,也正是限制了这种方法,因此它不适用于大文件。我已经尝试使用超过1.2千万的文件和超过1000万的文件。行(通过wc -l获得),并得到WindowsError:[错误8]没有足够的存储空间来处理此命令。当然,这是一个极端的情况。
SilentGhost

6
+1为实际定时数据。我们是否知道1024 * 1024的缓冲区大小是最佳的,还是有更好的缓冲区?
科伊夫

28
看来这wccount()是最快的gist.github.com/0ac760859e614cd03652
jfs

133

我不得不将其发布到一个类似的问题上,直到我的声誉得分略有提高(这要感谢任何碰到我的人!)。

所有这些解决方案都忽略了一种使运行速度显着提高的方法,即使用无缓冲(原始)接口,使用字节数组以及自己进行缓冲。(这仅适用于Python3。在Python 2中,默认情况下可能会或可能不会使用raw接口,但是在Python 3中,您将默认使用Unicode。)

使用计时工具的修改版,我相信以下代码比提供的任何解决方案都更快(并且稍微多了一些pythonic):

def rawcount(filename):
    f = open(filename, 'rb')
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.raw.read

    buf = read_f(buf_size)
    while buf:
        lines += buf.count(b'\n')
        buf = read_f(buf_size)

    return lines

使用单独的生成器函数,可以更快地运行:

def _make_gen(reader):
    b = reader(1024 * 1024)
    while b:
        yield b
        b = reader(1024*1024)

def rawgencount(filename):
    f = open(filename, 'rb')
    f_gen = _make_gen(f.raw.read)
    return sum( buf.count(b'\n') for buf in f_gen )

使用itertools内联生成器表达式可以完全完成此操作,但是看起来很奇怪:

from itertools import (takewhile,repeat)

def rawincount(filename):
    f = open(filename, 'rb')
    bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
    return sum( buf.count(b'\n') for buf in bufgen )

这是我的时间安排:

function      average, s  min, s   ratio
rawincount        0.0043  0.0041   1.00
rawgencount       0.0044  0.0042   1.01
rawcount          0.0048  0.0045   1.09
bufcount          0.008   0.0068   1.64
wccount           0.01    0.0097   2.35
itercount         0.014   0.014    3.41
opcount           0.02    0.02     4.83
kylecount         0.021   0.021    5.05
simplecount       0.022   0.022    5.25
mapcount          0.037   0.031    7.46

20
我正在使用100Gb +文件,而您的rawgencounts是到目前为止我所见的唯一可行的解​​决方案。谢谢!
soungalo 2015年

1
就是wccount在这个表中为子壳wc工具?
Anentropic

1
在另一个评论中找到了这个,我想是gist.github.com/zed/0ac760859e614cd03652
Anentropic

3
感谢@ michael-bacon,这是一个非常好的解决方案。您可以rawincount通过使用bufgen = iter(partial(f.raw.read, 1024*1024), b'')而不是takewhile和来使解决方案看起来更不奇怪repeat
Peter H.

1
哦,部分函数,​​是的,这是一个不错的小的调整。另外,我假设1024 * 1024将由解释器合并并视为常量,但这不是文档。
迈克尔·培根

90

您可以执行一个子流程并运行 wc -l filename

import subprocess

def file_len(fname):
    p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE, 
                                              stderr=subprocess.PIPE)
    result, err = p.communicate()
    if p.returncode != 0:
        raise IOError(err)
    return int(result.strip().split()[0])

6
Windows的版本是什么?
SilentGhost

1
您可以参考有关此的SO问题。stackoverflow.com/questions/247234/…–
奥拉维尔·沃奇

7
确实,在我的情况下(Mac OS X),计算“ file(...)中的x”所产生的行数需要花费0.13s而不是0.5s,而需要花费1.0s来计算对str.find或mmap.find的重复调用。(我用来测试的文件有130万行。)

1
无需涉及外壳。编辑答案并添加示例代码;
2009年

2
不跨平台。
e-info128

42

这是一个使用多处理库在机器/内核之间分配行数的python程序。我的测试使用8核Windows 64服务器将2000万行文件的计数从26秒提高到7秒。注意:不使用内存映射会使事情变慢。

import multiprocessing, sys, time, os, mmap
import logging, logging.handlers

def init_logger(pid):
    console_format = 'P{0} %(levelname)s %(message)s'.format(pid)
    logger = logging.getLogger()  # New logger at root level
    logger.setLevel( logging.INFO )
    logger.handlers.append( logging.StreamHandler() )
    logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )

def getFileLineCount( queues, pid, processes, file1 ):
    init_logger(pid)
    logging.info( 'start' )

    physical_file = open(file1, "r")
    #  mmap.mmap(fileno, length[, tagname[, access[, offset]]]

    m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )

    #work out file size to divide up line counting

    fSize = os.stat(file1).st_size
    chunk = (fSize / processes) + 1

    lines = 0

    #get where I start and stop
    _seedStart = chunk * (pid)
    _seekEnd = chunk * (pid+1)
    seekStart = int(_seedStart)
    seekEnd = int(_seekEnd)

    if seekEnd < int(_seekEnd + 1):
        seekEnd += 1

    if _seedStart < int(seekStart + 1):
        seekStart += 1

    if seekEnd > fSize:
        seekEnd = fSize

    #find where to start
    if pid > 0:
        m1.seek( seekStart )
        #read next line
        l1 = m1.readline()  # need to use readline with memory mapped files
        seekStart = m1.tell()

    #tell previous rank my seek start to make their seek end

    if pid > 0:
        queues[pid-1].put( seekStart )
    if pid < processes-1:
        seekEnd = queues[pid].get()

    m1.seek( seekStart )
    l1 = m1.readline()

    while len(l1) > 0:
        lines += 1
        l1 = m1.readline()
        if m1.tell() > seekEnd or len(l1) == 0:
            break

    logging.info( 'done' )
    # add up the results
    if pid == 0:
        for p in range(1,processes):
            lines += queues[0].get()
        queues[0].put(lines) # the total lines counted
    else:
        queues[0].put(lines)

    m1.close()
    physical_file.close()

if __name__ == '__main__':
    init_logger( 'main' )
    if len(sys.argv) > 1:
        file_name = sys.argv[1]
    else:
        logging.fatal( 'parameters required: file-name [processes]' )
        exit()

    t = time.time()
    processes = multiprocessing.cpu_count()
    if len(sys.argv) > 2:
        processes = int(sys.argv[2])
    queues=[] # a queue for each process
    for pid in range(processes):
        queues.append( multiprocessing.Queue() )
    jobs=[]
    prev_pipe = 0
    for pid in range(processes):
        p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
        p.start()
        jobs.append(p)

    jobs[0].join() #wait for counting to finish
    lines = queues[0].get()

    logging.info( 'finished {} Lines:{}'.format( time.time() - t, lines ) )

如何处理比主内存大得多的文件?例如在具有4GB RAM和2个内核的系统上的20GB文件
Brian Minton 2014年

现在很难测试,但是我认为它将对文件进行内外翻页。
Martlark 2014年

5
这是非常简洁的代码。我惊讶地发现使用多个处理器更快。我认为IO将成为瓶颈。在较旧的Python版本中,第21行需要int(),例如chunk = int((fSize / processs))+ 1
Karl Henselin 2014年

是否将所有文件加载到内存中?那比计算机上的RAM大的大火怎么办?
pelos

文件被映射到虚拟内存中,因此文件的大小和实际内存量通常不受限制。
Martlark

17

使用现代函数的类似于此答案的单行bash解决方案subprocess.check_output

def line_count(filename):
    return int(subprocess.check_output(['wc', '-l', filename]).split()[0])

对于Linux / Unix用户,应在该线程中将该答案投票最高。尽管在跨平台解决方案中有多数优先选择,但这在Linux / Unix上是一种极好的方法。对于一个1.84亿行的csv文件,我必须从中采样数据,它提供了最佳的运行时。其他纯python解决方案平均需要100秒钟以上的时间,而子流程调用则wc -l需要5秒钟左右的时间。
山豆

shell=True对安全性不利,最好避免这种情况。
Alexey Vazhnov

浮点,编辑
1英寸

15

我将使用Python的文件对象方法readlines,如下所示:

with open(input_file) as foo:
    lines = len(foo.readlines())

这将打开文件,在文件中创建行列表,计算列表的长度,将其保存到变量中,然后再次关闭文件。


6
尽管这是我想到的第一种方法,但它可能不是非常有效的内存使用,尤其是如果要对最大10 GB的文件中的行进行计数(就像我一样)的话,这是一个值得注意的缺点。
SteenSchütt2014年

@TimeSheep对于包含许多行(例如,数十亿个)的小行的文件,或者行数非常长的文件(例如,每行千兆字节),这是一个问题吗?
罗伯特

我问的原因是,似乎编译器应该能够通过不创建中间列表来对此进行优化。
罗伯特

@dmityugov每个Python文档xreadlines从2.3开始就被弃用,因为它仅返回一个迭代器。 for line in file是指定的替代品。请参阅:docs.python.org/2/library/stdtypes.html#file.xreadlines
Kumba,

12
def file_len(full_path):
  """ Count number of lines in a file."""
  f = open(full_path)
  nr_of_lines = sum(1 for line in f)
  f.close()
  return nr_of_lines

12

这是我用的,看起来很干净:

import subprocess

def count_file_lines(file_path):
    """
    Counts the number of lines in a file using wc utility.
    :param file_path: path to file
    :return: int, no of lines
    """
    num = subprocess.check_output(['wc', '-l', file_path])
    num = num.split(' ')
    return int(num[0])

更新:这比使用纯python快一点,但是以内存使用为代价。子进程将在执行命令时派生一个与父进程具有相同内存占用量的新进程。


1
顺便提一下,这当然在Windows上不起作用。
Bram Vanroy

核心utils的显然是为Windows提供“WC” stackoverflow.com/questions/247234/...。如果您的代码最终将在prod中的linux中运行,则还可以在Windows框中使用linux VM。
radtek

或WSL,如果您只有这样的工作,则强烈建议在任何VM上使用。:-)
Bram Vanroy

是的,行得通。我不是Windows专家,但通过深入学习,我了解到WSL = Linux的Windows子系统=)
radtek

3
python3.7:子流程返回字节,因此代码如下:int(subprocess.check_output(['wc','-l',file_path])。decode(“ utf-8”)。lstrip()。split(“ “)[0])
阿列克谢·阿列克森卡19/12/17

11

这是我发现使用纯python最快的东西。您可以通过设置缓冲区使用任意数量的内存,尽管2 ** 16似乎是我计算机上的最佳选择。

from functools import partial

buffer=2**16
with open(myfile) as f:
        print sum(x.count('\n') for x in iter(partial(f.read,buffer), ''))

我在这里找到了答案,为什么在C ++中从stdin读取行比在Python中慢?并稍作调整。这是一本很好的文章,可以理解如何快速计数行数,尽管wc -l仍然比其他任何东西都要快约75%。


9

我对该版本进行了小幅改进(4-8%),该版本重新使用了常量缓冲区,因此应避免任何内存或GC开销:

lines = 0
buffer = bytearray(2048)
with open(filename) as f:
  while f.readinto(buffer) > 0:
      lines += buffer.count('\n')

您可以尝试使用缓冲区大小,并且可能会看到一些改进。


真好 要考虑不以\ n结尾的文件,如果buffer和buffer [-1]!='\ n',则在循环外添加1
ryuusenshi

错误:上一轮的缓冲区可能不干净。
2014年

如果缓冲区之间的一部分以\结尾,而另一部分以n开头,该怎么办?那会错过那里的新行,我会求助于变量来存储每个块的结尾和开始,但是这可能会增加脚本的时间=(
pelos

9

凯尔的答案

num_lines = sum(1 for line in open('my_file.txt'))

可能是最好的,对此的替代方法是

num_lines =  len(open('my_file.txt').read().splitlines())

这是两者的性能比较

In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop

In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop

9

一线解决方案:

import os
os.system("wc -l  filename")  

我的片段:

>>> os.system('wc -l *.txt')

0 bar.txt
1000 command.txt
3 test_file.txt
1003 total

好主意,不幸的是,这在Windows上不起作用。
金(Kim)

3
如果你想成为python的冲浪者,请和Windows道别。相信我,有一天我会感谢我的。
TheExorcist's

6
我只是认为值得注意的是,这仅适用于Windows。我更喜欢自己在linux / unix堆栈上工作,但是在编写软件恕我直言时,应该考虑程序在不同操作系统下运行时可能产生的副作用。由于OP没有提及他的平台,并且万一有人通过Google在此解决方案上弹出并复制了该解决方案(不知道Windows系统可能有的限制),我想添加注释。

您不能将输出保存os.system()到变量中,也不能对其进行后处理。
一个SE

@AnSe你是正确的,但是没有询问它是否保存。我想你正在理解上下文。
TheExorcist

6

为了完成上述方法,我尝试了使用fileinput模块的变体:

import fileinput as fi   
def filecount(fname):
        for line in fi.input(fname):
            pass
        return fi.lineno()

并向上述所有方法传递了6000万行文件:

mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974

我感到有些惊讶的是,fileinput的性能和扩展性都比其他方法差很多。


5

对于我来说,这种变体将是最快的:

#!/usr/bin/env python

def main():
    f = open('filename')                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    print lines

if __name__ == '__main__':
    main()

原因:缓冲比逐行读取更快,而且string.count速度也非常快


1
但是吗?根据timeit.py,至少在OSX / python2.5上,OP的版本仍快约10%。
dF。

如果最后一行不以'\ n'结尾怎么办?
tzot

1
我不知道您是如何测试它的dF的,但是在我的机器上,它的速度比其他任何选项都要慢约2.5倍。
SilentGhost

34
您声明这将是最快的,然后声明您尚未对其进行测试。不是很科学吗?:)
奥拉维尔Waage

请参阅下面的Ryan Ginstrom回答提供的解决方案和统计信息。另外,请查看JF Sebastian的评论并链接相同的答案。
SherylHohman '17

5

该代码更短,更清晰。这可能是最好的方法:

num_lines = open('yourfile.ext').read().count('\n')

6
您还应该关闭文件。
rsm

6
它将整个文件加载到内存中。
伊夫林

在需要对大文件进行性能测试时并非最佳选择
mabraham

4

我修改了这样的缓冲情况:

def CountLines(filename):
    f = open(filename)
    try:
        lines = 1
        buf_size = 1024 * 1024
        read_f = f.read # loop optimization
        buf = read_f(buf_size)

        # Empty file
        if not buf:
            return 0

        while buf:
            lines += buf.count('\n')
            buf = read_f(buf_size)

        return lines
    finally:
        f.close()

现在,空文件和最后一行(不带\ n)也被计算在内。


也许还解释(或在代码中添加注释)您所做的更改以及更改的内容;)。可能会使人们更轻松地进入代码内部(而不是“解析”大脑中的代码)。
Styxxy 2012年

我认为循环优化允许Python在read_f,python.org
Red Pea

3

那这个呢

def file_len(fname):
  counts = itertools.count()
  with open(fname) as f: 
    for _ in f: counts.next()
  return counts.next()



3
def line_count(path):
    count = 0
    with open(path) as lines:
        for count, l in enumerate(lines, start=1):
            pass
    return count

3

如果要在Linux的Python中便宜地获得行数,我建议使用以下方法:

import os
print os.popen("wc -l file_path").readline().split()[0]

file_path可以是抽象文件路径,也可以是相对路径。希望这会有所帮助。


2

这个怎么样?

import fileinput
import sys

counter=0
for line in fileinput.input([sys.argv[1]]):
    counter+=1

fileinput.close()
print counter

2

一线如何?

file_length = len(open('myfile.txt','r').read().split('\n'))

使用此方法花费0.003秒在3900线文件上计时

def c():
  import time
  s = time.time()
  file_length = len(open('myfile.txt','r').read().split('\n'))
  print time.time() - s

2
def count_text_file_lines(path):
    with open(path, 'rt') as file:
        line_count = sum(1 for _line in file)
    return line_count

如果您认为这是错的,请您解释一下这是怎么回事?它为我工作。谢谢!
jciloa

我也想知道为什么这个答案也被否决了。它逐行遍历文件并将其汇总。我喜欢它,它很简短,而且要点是什么地方呢?
传承


1

打开文件的结果是一个迭代器,可以将其转换为长度为以下的序列:

with open(filename) as f:
   return len(list(f))

这比您的显式循环更为简洁,并且避免了enumerate


10
这意味着需要将100 Mb文件读入内存。
SilentGhost,

是的,很好,尽管我想知道速度(相对于内存)的差异。创建执行此操作的迭代器可能是可能的,但我认为这与您的解决方案等效。
Andrew Jaffe

6
-1,不仅是内存,还必须在内存中构造列表。
orip

0

您可以通过os.path以下方式使用该模块:

import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )

,其中Filename是文件的绝对路径。


1
这个答案有什么关系os.path
moi

0

如果文件可以放入内存,则

with open(fname) as f:
    count = len(f.read().split(b'\n')) - 1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.