1253 votes

Comment obtenir le nombre de lignes à bas prix en Python?

J'ai besoin d'obtenir un nombre de lignes d'un fichier volumineux (plusieurs centaines de milliers de lignes) en python. Quel est le moyen le plus efficace de la mémoire et du temps - sage?

Moi pour l'instant:

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

est-il possible de faire mieux?

772voto

Kyle Points 1423

Une ligne, probablement assez rapide:

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

426voto

Yuval Adam Points 59423

Vous ne pouvez pas faire mieux que cela.

Après tout, toute solution devra lire l'intégralité du fichier, la figure combien de \n vous avez, et de revenir à ce résultat.

Avez-vous un meilleur moyen de le faire sans avoir à lire tout le fichier? Pas sûr... La meilleure solution sera toujours liées aux e/S, le meilleur que vous pouvez faire est de vous assurer que vous n'utilisez pas inutile de mémoire, mais il semble que vous avez couvert le.

224voto

Ryan Ginstrom Points 8354

Je crois qu'un fichier mappé en mémoire sera la solution la plus rapide. J'ai essayé de quatre fonctions: la fonction affichée par les OP (opcount); une simple itération sur les lignes dans le fichier (simplecount); readline avec un mappés en mémoire déposé (mmap) (mapcount); et le tampon de lire la solution proposée par Mykola Kharechko (bufcount).

J'ai couru à chaque fonction de cinq fois, et calculé la moyenne de la durée d'exécution de 1,2 million de ligne d'un fichier texte.

Windows XP, Python 2.5, 2 go de RAM, 2 GHz processeur AMD

Voici mes résultats:

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

Edit: les chiffres pour la version 2.6 de Python:

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

De sorte que le tampon de lecture de la stratégie semble être la manière la plus rapide pour Windows/version 2.6 de Python

Voici le code:

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

104voto

Ólafur Waage Points 40104

Vous pouvez exécuter un sous-processus et exécutez 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])

48voto

Martlark Points 5064

Voici un programme en python pour utiliser le multitraitement de la bibliothèque de distribuer la ligne de comptage sur des machines/cœurs. Mon test améliore le décompte s'élevant à 20 millions de ligne de fichier à partir de 26 secondes à 7 secondes à l'aide d'un 8 core de windows 64 serveur. Remarque: l'utilisation du mappage de la mémoire rend les choses beaucoup plus lent.

  #LineCount multiprocessing.py
  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")
    m1 = mmap.mmap( physical_file.fileno(), 0, None, 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
    seekStart = chunk * (pid)
    seekEnd = chunk * (pid+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 ) )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X