1202

Python で大きなファイル (数十万行) の行数を取得する必要があります。メモリと時間の両方で最も効率的な方法は何ですか?

現時点で私は:

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

もっとうまくやることは可能ですか?

4

44 に答える 44

741

おそらくかなり速い1行:

num_lines = sum(1 for line in open('myfile.txt'))
于 2009-06-19T19:07:06.063 に答える
422

あなたはそれよりも良くなることはできません。

結局のところ、ソリューションはファイル全体を読み取り、ファイルの数を把握し\nて、その結果を返す必要があります。

ファイル全体を読み取らずにそれを行うより良い方法はありますか? わかりません...最善の解決策は常にI / Oバウンドです。不要なメモリを使用しないようにすることが最善ですが、それはカバーされているようです。

于 2009-05-10T10:37:42.677 に答える
221

メモリ マップド ファイルが最速のソリューションになると思います。私は4つの機能を試しました.OP(opcount)によって投稿された機能。ファイル内の行に対する単純な反復 ( simplecount); メモリマップされたフィールド (mmap) を持つ readline ( mapcount); Mykola Kharechko ( bufcount) によって提供されたバッファー読み取りソリューション。

各関数を 5 回実行し、120 万行のテキスト ファイルの平均実行時間を計算しました。

Windows XP、Python 2.5、2 GB RAM、2 GHz 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))
于 2009-05-12T02:49:04.590 に答える
103

サブプロセスを実行して実行できます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])
于 2009-05-10T10:28:29.610 に答える
46

マルチプロセッシング ライブラリを使用して、ライン カウントを複数のマシン/コアに分散する Python プログラムを次に示します。私のテストでは、8 コアの Windows 64 サーバーを使用して、2000 万行のファイルのカウントが 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 ) )
于 2011-07-26T06:51:01.980 に答える
18

次のように、 Python のファイル オブジェクト メソッドを使用readlinesします。

with open(input_file) as foo:
    lines = len(foo.readlines())

これにより、ファイルが開き、ファイル内の行のリストが作成され、リストの長さがカウントされ、それが変数に保存され、ファイルが再び閉じられます。

于 2013-10-08T12:46:12.630 に答える
13

これは、純粋な 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), ''))

私はここで答えを見つけた 少しだけ微調整しました。行をすばやくカウントする方法を理解するには、非常に良い読み物ですwc -lが、それでも他の何よりも約 75% 高速です。

于 2016-12-09T20:34:47.467 に答える
12
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
于 2009-05-10T10:33:41.173 に答える
11

1 行のソリューション:

import os
os.system("wc -l  filename")  

私のスニペット:

>>> os.system('wc -l *.txt')

0 bar.txt
1000 command.txt
3 test_file.txt
1003 total
于 2017-01-06T08:29:12.090 に答える
8

カイルの答え

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
于 2014-10-15T05:22:12.937 に答える
7

このバージョンでは、定数バッファーを再利用するため、メモリやGCのオーバーヘッドを回避できるため、わずかな改善(4〜8%)が得られました。

lines = 0
buffer = bytearray(2048)
with open(filename) as f:
  while f.readinto(buffer) > 0:
      lines += buffer.count('\n')

バッファサイズを試してみて、少し改善されるかもしれません。

于 2013-02-25T19:31:53.873 に答える
5

私にとっては、このバリアントが最速になります:

#!/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また非常に高速です

于 2009-05-10T11:29:12.350 に答える
5

上記の方法を完了するために、 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

fileinput がそれほど悪く、他のすべての方法よりもはるかに悪いことに少し驚いています...

于 2010-05-05T11:48:21.900 に答える
4

このコードはより短く、より明確です。それはおそらく最良の方法です:

num_lines = open('yourfile.ext').read().count('\n')
于 2015-02-23T18:38:13.633 に答える
4

次のようにバッファ ケースを変更しました。

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 なし) もカウントされるようになりました。

于 2011-11-25T14:55:52.607 に答える
2

Linux の Python で安価に行数を取得したい場合は、次の方法をお勧めします。

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

file_path は、抽象ファイル パスまたは相対パスの両方にすることができます。これが役立つことを願っています。

于 2014-08-28T09:09:45.287 に答える
2

すでに多くの答えがありますが、残念ながらそれらのほとんどは、ほとんど最適化できない問題に関する小さな経済です...

私は、行数がソフトウェアのコア機能であるいくつかのプロジェクトに取り組み、膨大な数のファイルをできるだけ速く処理することが最も重要でした。

行数の主なボトルネックは I/O アクセスです。改行文字を検出するために各行を読み取る必要があるため、これを回避する方法はありません。2 番目の潜在的なボトルネックはメモリ管理です。一度にロードする量が多いほど、処理が速くなりますが、このボトルネックは最初のボトルネックに比べて無視できます。

したがって、行カウント関数の処理時間を短縮する主な方法は 3 つありますが、gc コレクションの無効化やその他の細かな管理トリックなどの小さな最適化は別として、次のようになります。

  1. ハードウェア ソリューション:主要かつ最も明白な方法は、非プログラムです。非常に高速な SSD/フラッシュ ハード ドライブを購入します。これまでのところ、これが最大のスピードブーストを得る方法です.

  2. データ準備ソリューション:処理するファイルを生成するか、生成方法を変更できる場合、またはそれらを前処理することが許容される場合は、最初に改行を UNIX スタイル ( \n) に変換します。これにより、Windows またはMacOS スタイル (大きな節約にはなりませんが、簡単に得られます)、そして次に最も重要なこととして、固定長の行を書くことができる可能性があります。可変長が必要な場合は、いつでも小さな行を埋め込むことができます。このようにして、ファイルサイズの合計から行数を即座に計算できるため、アクセスがはるかに高速になります。多くの場合、問題に対する最善の解決策は、最終目的により適合するように前処理することです。

  3. 並列化 + ハードウェア ソリューション:複数のハードディスク (および可能であれば SSD フラッシュ ディスク) を購入できる場合は、並列化を利用して、ディスク間でバランスのとれた方法でファイルを保存することにより (最も簡単なのは合計サイズでバランスを取ることです)、1 つのディスクの速度を超えることさえできます。 、そしてそれらすべてのディスクから並行して読み取ります。そうすれば、所有しているディスクの数に比例して乗数が増加することが期待できます。複数のディスクを購入できない場合、並列化は役に立たない可能性があります (プロ仕様のディスクのようにディスクに複数の読み取りヘッダーがある場合を除きますが、それでもディスクの内部キャッシュ メモリと PCB 回路がボトルネックになる可能性があります)。すべてのヘッドを並行して完全に使用することを防ぎます。さらに、このハードドライブ用の特定のコードを考案する必要があります。ファイルを異なるヘッドの下のクラスターに保存し、後で異なるヘッドでそれらを読み取ることができるように、正確なクラスターマッピングを知る必要があるため、使用します)。実際、シーケンシャル読み取りはほとんどの場合ランダム読み取りより高速であり、単一ディスクでの並列化のパフォーマンスはシーケンシャル読み取りよりもランダム読み取りに似ていることが一般的に知られています (たとえば、CrystalDiskMark を使用して両方の側面でハード ドライブの速度をテストできます)。 .

これらのどれも選択肢にない場合は、行カウント関数の速度を数パーセント向上させるためにマイクロ管理のトリックに頼ることしかできませんが、実際に重要なことは何も期待できません. むしろ、微調整に費やす時間は、目に見える速度向上の見返りに比べて不釣り合いになると予想できます。

于 2020-11-09T19:12:57.913 に答える
1

これはどうですか

def file_len(fname):
  counts = itertools.count()
  with open(fname) as f: 
    for _ in f: counts.next()
  return counts.next()
于 2009-05-10T18:20:28.523 に答える
1

このワンライナーはどうですか:

file_length = len(open('myfile.txt','r').read().split('\n'))

この方法を使用して 3900 行のファイルで時間を計るのに 0.003 秒かかります

def c():
  import time
  s = time.time()
  file_length = len(open('myfile.txt','r').read().split('\n'))
  print time.time() - s
于 2013-10-03T00:55:36.040 に答える
1

これはどう?

import fileinput
import sys

counter=0
for line in fileinput.input([sys.argv[1]]):
    counter+=1

fileinput.close()
print counter
于 2011-07-19T15:55:39.633 に答える
1

ファイルを開いた結果はイテレータであり、長さを持つシーケンスに変換できます。

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

これは、明示的なループよりも簡潔であり、enumerate.

于 2009-05-10T11:35:26.983 に答える
1

count = max(enumerate(open(filename)))[0]

于 2011-03-11T21:09:52.690 に答える
1
def line_count(path):
    count = 0
    with open(path) as lines:
        for count, l in enumerate(lines, start=1):
            pass
    return count
于 2014-06-02T21:45:10.887 に答える
1
print open('file.txt', 'r').read().count("\n") + 1
于 2014-03-21T06:10:30.660 に答える
0

os.pathモジュールは次の方法で使用できます。

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ファイルの絶対パスです。

于 2014-11-02T03:58:48.893 に答える
0

最初の 100 行と最後の 100 行を読み取って平均行長を推定し、その数値で合計ファイル サイズを割りませんか? 正確な値が必要ない場合は、これでうまくいく可能性があります。

于 2009-05-10T18:36:20.650 に答える
-1

別の可能性:

import subprocess

def num_lines_in_file(fpath):
    return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])
于 2015-01-27T07:08:00.337 に答える
-2

なぜ次のことがうまくいかないのですか?

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関数は、長さを決定する手段として入力行を使用します。

于 2010-12-05T16:45:54.443 に答える
-2

これはどうですか?

import sys
sys.stdin=open('fname','r')
data=sys.stdin.readlines()
print "counted",len(data),"lines"
于 2010-06-25T15:17:32.480 に答える