파이썬에서 줄 수를 저렴하게 얻는 방법은 무엇입니까?
파이썬으로 큰 파일 (수십만 줄)의 줄 수를 얻어야합니다. 메모리 및 시간 측면에서 가장 효율적인 방법은 무엇입니까?
지금 내가하는 일 :
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
더 잘할 수 있습니까?
당신은 그것보다 더 나아질 수 없습니다.
결국 모든 솔루션은 전체 파일을 읽고 얼마나 많은지 \n
를 파악한 다음 그 결과를 반환해야합니다.
전체 파일을 읽지 않고 더 나은 방법이 있습니까? 확실하지 않습니다 ... 최상의 솔루션은 항상 I / O 바운드입니다. 최선의 방법은 불필요한 메모리를 사용하지 않는지 확인하는 것입니다.
한 줄, 아마도 꽤 빠름 :
num_lines = sum(1 for line in open('myfile.txt'))
메모리 매핑 파일이 가장 빠른 솔루션이 될 것이라고 믿습니다. 나는 네 가지 기능을 시도했다 : OP ( opcount
); 파일의 행에 대한 간단한 반복 ( simplecount
); 메모리 매핑 파일이있는 readline (mmap) ( mapcount
); 및 Mykola Kharechko가 제공하는 버퍼 읽기 솔루션 ( bufcount
).
각 함수를 5 번 실행하고 120 만 줄 텍스트 파일의 평균 런타임을 계산했습니다.
Windows XP, Python 2.5, 2GB RAM, 2GHz 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))
나는 내 평판 점수가 약간 올라갈 때까지 비슷한 질문에 이것을 게시해야했다.
이러한 모든 솔루션은 버퍼링되지 않은 (원시) 인터페이스를 사용하고, 바이트 배열을 사용하고, 자체 버퍼링을 수행하는 등이 작업을 훨씬 빠르게 실행하는 한 가지 방법을 무시합니다. (이것은 Python 3에서만 적용됩니다. Python 2에서는 원시 인터페이스가 기본적으로 사용되거나 사용되지 않을 수 있지만 Python 3에서는 기본적으로 유니 코드가 사용됩니다.)
수정 된 버전의 타이밍 도구를 사용하면 다음 코드가 제공된 솔루션보다 더 빠르며 약간 더 파이썬 적이라고 생각합니다.
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
하위 프로세스를 실행하고 실행할 수 있습니다. 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])
다음은 멀티 프로세싱 라이브러리를 사용하여 머신 / 코어에 라인 카운팅을 배포하는 파이썬 프로그램입니다. 내 테스트는 8 코어 Windows 64 서버를 사용하여 2 천만 줄 파일을 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 ) )
readlines
다음과 같이 Python의 파일 객체 메서드 를 사용합니다.
with open(input_file) as foo:
lines = len(foo.readlines())
이렇게하면 파일이 열리고 파일에 줄 목록이 생성되고 목록의 길이가 계산되고 변수에 저장되고 파일이 다시 닫힙니다.
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
내가 사용하는 것은 다음과 같습니다.
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])
업데이트 : 이것은 순수한 파이썬을 사용하는 것보다 약간 빠르지 만 메모리 사용 비용이 있습니다. 하위 프로세스는 명령을 실행하는 동안 상위 프로세스와 동일한 메모리 공간을 가진 새 프로세스를 분기합니다.
한 줄 솔루션 :
import os
os.system("wc -l filename")
내 스 니펫 :
>>> os.system('wc -l *.txt')
0 bar.txt
1000 command.txt
3 test_file.txt
1003 total
이 버전에서는 일정한 버퍼를 재사용하여 메모리 나 GC 오버 헤드를 피해야하는 작은 (4-8 %) 개선이있었습니다.
lines = 0
buffer = bytearray(2048)
with open(filename) as f:
while f.readinto(buffer) > 0:
lines += buffer.count('\n')
버퍼 크기를 가지고 놀 수 있으며 약간의 개선을 볼 수 있습니다.
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
현대 함수를 사용 하여이 답변 과 유사한 한 줄 bash 솔루션 subprocess.check_output
:
def line_count(file):
return int(subprocess.check_output('wc -l {}'.format(file), shell=True).split()[0])
이것은 순수한 파이썬을 사용하여 찾은 가장 빠른 것입니다. 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에서 줄을 읽는 것이 훨씬 느립니까? 그리고 약간 조정했습니다. 줄을 빨리 세는 방법을 이해하는 데 아주 좋은 읽기이지만 wc -l
여전히 다른 것보다 약 75 % 빠릅니다.
위의 방법을 완료하기 위해 fileinput 모듈로 변형을 시도했습니다.
import fileinput as fi
def filecount(fname):
for line in fi.input(fname):
pass
return fi.lineno()
그리고 위에서 언급 한 모든 메소드에 60mil 라인 파일을 전달했습니다.
mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974
파일 입력이 그다지 나쁘고 다른 모든 방법보다 훨씬 더 나쁘다는 것은 나에게 약간 놀랍습니다.
이 코드는 더 짧고 명확합니다. 아마도 가장 좋은 방법 일 것입니다.
num_lines = open('yourfile.ext').read().count('\n')
나에게는이 변종이 가장 빠를 것입니다.
#!/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
또한 매우 빠름
다음과 같이 버퍼 케이스를 수정했습니다.
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 없음)도 계산됩니다.
print open('file.txt', 'r').read().count("\n") + 1
간단한 방법 :
1)
>>> f = len(open("myfile.txt").readlines())
>>> f
430
2)
>>> f = open("myfile.txt").read().count('\n')
>>> f
430
>>>
3)
num_lines = len(list(open('myfile.txt')))
What about this
def file_len(fname):
counts = itertools.count()
with open(fname) as f:
for _ in f: counts.next()
return counts.next()
count = max(enumerate(open(filename)))[0]
def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count
If one wants to get the line count cheaply in Python in Linux, I recommend this method:
import os
print os.popen("wc -l file_path").readline().split()[0]
file_path can be both abstract file path or relative path. Hope this may help.
the result of opening a file is an iterator, which can be converted to a sequence, which has a length:
with open(filename) as f:
return len(list(f))
this is more concise than your explicit loop, and avoids the enumerate
.
How about this?
import fileinput
import sys
counter=0
for line in fileinput.input([sys.argv[1]]):
counter+=1
fileinput.close()
print counter
How about this one-liner:
file_length = len(open('myfile.txt','r').read().split('\n'))
Takes 0.003 sec using this method to time it on a 3900 line file
def c():
import time
s = time.time()
file_length = len(open('myfile.txt','r').read().split('\n'))
print time.time() - s
You can use the os.path
module in the following way:
import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )
, where Filename
is the absolute path of the file.
Another possibility:
import subprocess
def num_lines_in_file(fpath):
return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])
def count_text_file_lines(path):
with open(path, 'rt') as file:
line_count = sum(1 for _line in file)
return line_count
참고URL : https://stackoverflow.com/questions/845058/how-to-get-line-count-cheaply-in-python
'code' 카테고리의 다른 글
Bootstrap 열을 모두 같은 높이로 만들려면 어떻게해야합니까? (0) | 2020.09.28 |
---|---|
NSString 값을 NSData로 어떻게 변환합니까? (0) | 2020.09.28 |
다른 생성자에서 하나의 생성자 호출 (0) | 2020.09.28 |
Swift Beta 성능 : 배열 정렬 (0) | 2020.09.28 |
JSLint가 갑자기보고합니다. "use strict"의 함수 형식을 사용합니다. (0) | 2020.09.28 |