How to get line count cheaply in Python?
我需要在python中获取一个大文件(数十万行)的行数。记忆和时间方面最有效的方法是什么?
现在我这样做了:
1 2 3 4 5 | def file_len(fname): with open(fname) as f: for i, l in enumerate(f): pass return i + 1 |
有没有可能做得更好?
一行,可能很快:
1 | num_lines = sum(1 for line in open('myfile.txt')) |
你不会比这更好的。
毕竟,任何解决方案都必须读取整个文件,找出您拥有多少
在不读取整个文件的情况下,您有更好的方法吗?不确定。。。最好的解决方案将始终是I/O绑定的,您所能做的最好的就是确保不使用不必要的内存,但看起来您已经覆盖了这一点。
我相信内存映射文件将是最快的解决方案。我尝试了四个函数:op发布的函数(
我运行了五次每个函数,并计算了120万行文本文件的平均运行时间。
Windows XP、python 2.5、2GB RAM、2 GHz AMD处理器
以下是我的结果:
1 2 3 4 | mapcount : 0.465599966049 simplecount : 0.756399965286 bufcount : 0.546800041199 opcount : 0.718600034714 |
编辑:python 2.6的数字:
1 2 3 4 | mapcount : 0.471799945831 simplecount : 0.634400033951 bufcount : 0.468800067902 opcount : 0.602999973297 |
因此,对于Windows/python 2.6来说,缓冲区读取策略似乎是最快的。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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(' ') 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)) |
我不得不把这个贴在一个类似的问题上,直到我的名誉分数跳了一点(多亏了撞我的人!).
所有这些解决方案都忽略了一种使运行速度大大加快的方法,即使用未缓冲(原始)接口、使用bytearray和执行自己的缓冲。(这仅适用于python 3。在python 2中,原始接口在默认情况下可以使用,也可以不使用,但是在python 3中,您将默认为unicode。)
使用修改过的计时工具,我相信下面的代码比提供的任何解决方案都快(而且稍微多一些Python式的代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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' ') buf = read_f(buf_size) return lines |
使用单独的生成器功能,运行速度更快:
1 2 3 4 5 6 7 8 9 10 11 | 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' ') for buf in f_gen ) |
这完全可以通过使用itertools的内联生成器表达式来完成,但是看起来很奇怪:
1 2 3 4 5 6 7 | 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' ') for buf in bufgen ) |
以下是我的时间安排:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
您可以执行一个子进程并运行
1 2 3 4 5 6 7 8 9 | 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]) |
这里有一个python程序,它使用多处理库在机器/内核之间分配行计数。我的测试使用8核Windows64服务器将2000万在线文件的计数从26秒提高到7秒。注意:不使用内存映射会使事情慢得多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 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 ) ) |
我将使用python的文件对象方法
1 2 | with open(input_file) as foo: lines = len(foo.readlines()) |
这将打开文件,在文件中创建行列表,计算列表的长度,将其保存到变量中,然后再次关闭文件。
这是我用的,看起来很干净:
1 2 3 4 5 6 7 8 9 10 11 | 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 2 3 4 5 6 | 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 |
我在这个版本中得到了一个小的(4-8%)改进,它重新使用了一个常量缓冲区,因此应该避免任何内存或GC开销:
1 2 3 4 5 6 | lines = 0 buffer = bytearray(2048) with open(filename) as f: while f.readinto(buffer) > 0: lines += buffer.count(' ') |
您可以随意使用缓冲区大小,也许会看到一些改进。
凯尔的回答
1 | num_lines = sum(1 for line in open('my_file.txt')) |
可能是最好的选择
1 | num_lines = len(open('my_file.txt').read().splitlines()) |
以下是两者的性能比较
1 2 3 4 5 | 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 |
类似于此答案的单行bash解决方案,使用现代
1 2 | def line_count(file): return int(subprocess.check_output('wc -l {}'.format(file), shell=True).split()[0]) |
一线解决方案
1 2 | import os os.system("wc -l filename") |
我的片段
os.system('wc -l *.txt')
< /块引用>< /块引用>
1
2
3
4 0 bar.txt
1000 command.txt
3 test_file.txt
1003 total这是我用纯Python找到的最快的东西。你可以通过设置缓冲区来使用你想要的任何数量的内存,尽管2*16在我的电脑上似乎是一个最佳选择。
1
2
3
4
5
6 from functools import partial
buffer=2**16
with open(myfile) as f:
print sum(x.count('
') for x in iter(partial(f.read,buffer), ''))我在这里找到答案,为什么从STDIN中读取线比Python慢得多?稍微调整了一下。了解如何快速计算行数是一个很好的读物,尽管
wc -l 仍然比其他任何东西快75%。这段代码简短明了。这可能是最好的方法:
1
2 num_lines = open('yourfile.ext').read().count('
')为了完成上面的方法,我尝试了一个文件输入模块的变体:
1
2
3
4
5 import fileinput as fi
def filecount(fname):
for line in fi.input(fname):
pass
return fi.lineno()并将一个60英里的行文件传递给上述所有方法:
1
2
3
4
5 mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974让我有点惊讶的是,文件输入如此糟糕,扩展比所有其他方法都糟糕…
简单方法:
num_lines = len(list(open('myfile.txt')))
1
2 print open('file.txt', 'r').read().count("
") + 1对于我来说,这个变种将是最快的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 #!/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('
')
buf = read_f(buf_size)
print lines
if __name__ == '__main__':
main()原因:缓存比逐行读取快,
string.count 也很快我对缓冲区的修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 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('
')
buf = read_f(buf_size)
return lines
finally:
f.close()现在还将计算空文件和最后一行(不带)。
打开文件的结果是一个迭代器,它可以转换为序列,序列的长度为:
1
2 with open(filename) as f:
return len(list(f))这比显式循环更简洁,并避免使用
enumerate 。
count = max(enumerate(open(filename)))[0] 这个怎么样?
1
2
3
4
5 def file_len(fname):
counts = itertools.count()
with open(fname) as f:
for _ in f: counts.next()
return counts.next()如果要在Linux中以较低的成本获得python中的行数,我建议使用以下方法:
1
2 import os
print os.popen("wc -l file_path").readline().split()[0]文件路径既可以是抽象文件路径,也可以是相对路径。希望这能有所帮助。
这个怎么样?
1
2
3
4
5
6
7
8
9 import fileinput
import sys
counter=0
for line in fileinput.input([sys.argv[1]]):
counter+=1
fileinput.close()
print counter这条班轮怎么样:
1
2 file_length = len(open('myfile.txt','r').read().split('
'))使用此方法在3900行文件上花费0.003秒的时间
1
2
3
4
5
6 def c():
import time
s = time.time()
file_length = len(open('myfile.txt','r').read().split('
'))
print time.time() - s
1
2
3
4
5
6 def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count您可以按以下方式使用
os.path 模块:
1
2
3 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
2
3
4 import subprocess
def num_lines_in_file(fpath):
return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])
1
2
3
4 def count_text_file_lines(path):
with open(path, 'rt') as file:
line_count = sum(1 for _line in file)
return line_count如果文件可以装入内存,则
1
2
3 with open(fname) as f:
count = len(f.read().split(b'
')) - 1创建名为count.py的可执行脚本文件:
1
2
3
4
5
6 #!/usr/bin/python
import sys
count = 0
for line in sys.stdin:
count+=1然后将文件的内容通过管道传输到python脚本:
cat huge.txt | ./count.py 。管道也可以在PowerShell上工作,因此您最终将计算行数。对我来说,在Linux上,它比:
1
2
3 count=1
with open('huge.txt') as f:
count+=1如果文件中的所有行的长度相同(并且只包含ASCII字符)*,则可以非常便宜地执行以下操作:
1
2
3 fileSize = os.path.getsize( pathToFile ) # file size in bytes
bytesPerLine = someInteger # don't forget to account for the newline character
numLines = fileSize // bytesPerLine*我怀疑如果使用像_这样的Unicode字符,需要更多的努力来确定一行中的字节数。
为什么下面的工作不行?
1
2
3
4
5
6
7
8
9
10 import sys
# input comes from STDIN
file = sys.stdin
data = file.readlines()
# get total number of lines in file
lines = len(data)
print lines在这种情况下,len函数使用输入行作为确定长度的方法。
这个怎么样?
1
2
3
4 import sys
sys.stdin=open('fname','r')
data=sys.stdin.readlines()
print"counted",len(data),"lines"为什么不读取前100行和后100行并估计平均行长度,然后将总文件大小除以这些数字?如果你不需要一个精确的值,这是可行的。
类似地:
1
2
3
4 lines = 0
with open(path) as f:
for line in f:
lines += 1