code

파이썬에서 줄 수를 저렴하게 얻는 방법은 무엇입니까?

codestyles 2020. 9. 28. 09:09
반응형

파이썬에서 줄 수를 저렴하게 얻는 방법은 무엇입니까?


파이썬으로 큰 파일 (수십만 줄)의 줄 수를 얻어야합니다. 메모리 및 시간 측면에서 가장 효율적인 방법은 무엇입니까?

지금 내가하는 일 :

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

반응형