iMind Developers Blog

iMind開発者ブログ

Pythonでファイルの行数を取るコード

概要

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, 記事投稿