概要
Pythonでファイルの行数を取るコードをシンプルな記述で書きたかった。
各コードに実行時間を記載しているが、これは1億行のファイル(520MB)をカウントした場合の数字。
バージョン情報
- Python 3.7.1
単純にループしながらinclement
何も考えずに書くならこんな感じだろうか。
line_count = 0 with open('test.txt') as f: for line in f: line_count += 1
実行時間 : 9.645秒
enumerateを使う
enumerateでループすればinclementをしなくても済む。
with open('test.txt') as f: for line_count, _ in enumerate(f, 1): pass
実行時間 : 8.669秒
Pythonでの加算が発生しない分、1つ前のコードより若干速い。
reduce版
enumerateをreduceしてindexを取るようにすれば2行(import除く)で済む。
from functools import reduce with open('test.txt') as f: line_count = reduce((lambda x, y: y[0]), enumerate(f, 1))
実行時間 : 12.507秒
無駄にループ内で結果を受け渡すせいか単純なループより若干遅くなった。
tailっぽい書き方
こういう書き方もできる、けど一時的にメモリにファイルの全容量が乗るので大きいファイルで使うとメモリをけっこう使ってしまう。
with open('test.txt') as f: *_, (line_count, _) = enumerate(f, 1)
コードの意味としては、 *_, tail = [1, 2, 3, 4] のような書き方でアンスコに[1, 2, 3]が、tailに4が入るので、その流れでenumerateのindex側だけ取った。
1をsumする
ラインの数だけ1が入った要素を作って
with open('test.txt') as f: line_count = sum([1 for line in f])
実行時間 : 5.491秒
あれ、意外と速い。ただし1億行を処理する場合1が1億個入ったListができるわけなのでそれだけメモリは消費する。
wc -l
wcコマンドを呼び出せば1行で且つこれまで書いてきたコードよりずっとはやい!!
import subprocess line_count = int(subprocess.check_output(['wc', '-l', 'test.txt']).decode().split(' ')[0])
実行結果 : 0.408秒
なんとなく悔しいからCython
wcの速度を見てなんとなく悔しかったのでCythonで少しだけ速度を向上させてみる。
下記の3つのファイルを作成。
# ファイル名 : foo.pyx def count_line(filepath): cdef int line_count cdef bytes line line_count = 0 with open(filepath, 'rb') as f: for line in f: line_count += 1 return line_count
# ファイル名 : setup.py from distutils.core import setup from Cython.Build import cythonize setup( ext_modules = cythonize("foo.pyx") )
# ファイル名 : bar.py import foo line_count = foo.count_line('test.txt') print(line_count)
buildして実行時間計測。
$ python setup.py build_ext --inplace $ time python bar.py
実行時間 : 2.500秒
まずまずの結果。でもコード長くなるし1億行もカウントすることはあまりないのでここまでする意味はあまりない。
改定履歴
Author: Masato Watanabe, Date: 2019-01-24, 記事投稿