text-files dataframe срез - Как дешево получить количество строк в Python?





15 Answers

Одна строка, возможно довольно быстро:

num_lines = sum(1 for line in open('myfile.txt'))
цикл pandas перебор

Мне нужно получить количество строк большого файла (сотни тысяч строк) в python. Каков наиболее эффективный способ как с точки зрения памяти, так и по времени?

На данный момент я делаю:

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

можно ли сделать лучше?




Мне пришлось опубликовать это по аналогичному вопросу, пока мой рейтинг репутации не подскочил (спасибо тому, кто набросился на меня!).

Все эти решения игнорируют один из способов сделать этот запуск значительно быстрее, а именно, используя небуферизованный (необработанный) интерфейс, используя bytearrays и делая собственную буферизацию. (Это применимо только к Python 3. В Python 2 необработанный интерфейс может использоваться или не использоваться по умолчанию, но в 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

Используя отдельную функцию генератора, это быстрее запускает smidge:

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 )

Это можно сделать полностью с выражениями генераторов in-line с использованием 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



Вот программа python для использования библиотеки многопроцессорности для распределения подсчета строк по машинам / ядрам. Мой тест улучшает подсчет 20-миллионного файла линии с 26 секунд до 7 секунд, используя 8-ядерный сервер Windows 64. Примечание: не использование карт памяти делает вещи намного медленнее.

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 ) )



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



Ответ Кайла

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



Вот что я использую, кажется довольно чистым:

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])

UPDATE: это немного быстрее, чем использование чистого python, но за счет использования памяти. Подпроцесс разветвит новый процесс с тем же объемом памяти, что и родительский процесс, когда он выполнит вашу команду.




Это самая быстрая вещь, которую я нашел, используя чистый 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), ''))

Я нашел ответ здесь. Почему чтение строк из stdin происходит намного медленнее на C ++, чем на Python? и немного изменил его. Его очень хорошо читать, чтобы понять, как быстро считать строки, хотя wc -l все еще примерно на 75% быстрее, чем что-либо еще.




результатом открытия файла является итератор, который может быть преобразован в последовательность, которая имеет длину:

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

это более кратким, чем ваш явный цикл, и избегает enumerate .




print open('file.txt', 'r').read().count("\n") + 1



Я изменил буферный случай следующим образом:

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) подсчитываются.




Как насчет этого

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



Как насчет этого однострочного лайнера:

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



Если вы хотите получить дешевое количество строк в Python в Linux, я рекомендую этот метод:

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

file_path может быть как абстрактным, так и относительным путем. Надеюсь, это поможет.




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



Так же:

lines = 0
with open(path) as f:
    for line in f:
        lines += 1



Related