ほぼ中立ブログ

少しだけ趣味に偏った雑記ブログ

Pythonのfileinputでフィルタとかファイルの書き換えとか

Pythonでファイルの読み込みや書き込みをする際にはopen関数を使うのが基本ですが、下に挙げるような少し複雑な操作をしたい場合には頭を抱えることになります。

  1. 標準入力からも読み込みたい、ただし指定したときにはファイルからも読み込んでほしい。
  2. ファイルの読み書きを同時に行いたい。

特に2.が個人的に深刻で、良い方法が見つからず毎回無駄な中間ファイルを作っては、消すほどではないし残しとくかぁみたいな思考になること度々でした。今回上の問題をまとめて解決してくれる便利なモジュールfileinputを見つけたので備忘録を兼ねたまとめです。

標準入力から読み込む

以下公式のドキュメントからのコピペですが、fileinputからファイルを読み込む基本形は次の通りです。

import fileinput
for line in fileinput.input():
    process(line)

要するにfileinputのinput関数にコマンドライン引数として与えたファイルが順次渡されて開かれていきますよ、ということらしいです。開かれた後は1行ずつ処理することができます。複数のファイルを渡す場合でもループ処理のコードが1回で済むので便利ですね。

ファイル名を省略したり-にしたりすると標準入力から読み込む挙動のため、フィルタプログラムのようなモノを簡単に作成することができます。例えばファイルまたは標準入力を1行ずつ全て足して出力するというプログラムは次のように書けます。

上の例と少し違いますが、with文と一緒に使えるのでそちらで書いています。まぁ、この辺りはopenを使う場合と同じですね。

#!/usr/bin/python3

import fileinput

total = 0
with fileinput.input() as f:
    for line in f:
        line = line.rstrip()
        total += int(line)
print(total)

上のスクリプトを仮にsum.pyとすると次のように動作します。

$ echo -e "1\n2\n3\n4\n5" | ./sum.py
15

こういった処理はawkでやるのが王道な気がしますがPythonで書けるのは便利ですね。

ファイルの書き換え

fileinputのinput関数にはinplace引数と呼ばれるものがあり、これをTrueにすることでファイルの読み書きを同時に行うことができます。

公式ドキュメントの説明によると、inplace=Trueを指定した場合には入力ファイルがバックアップファイルに一時的に移動され(デフォルトではバックファイルの名前は入力ファイル名+.bak)、標準出力が入力ファイルに設定されます。また、バックアップファイルは標準出力に設定されたファイルが閉じられると自動で削除されます。

これだけ聞くと、新しくファイルを作ってそこに書き込んだ後、元の入力ファイルと同じ名前にリネームするような単純な処理と大差ない気もするのですが、コードの行数が減るのは良いですね。

例えばファイル中のアルファベットを全て小文字に書き換えるようなプログラムは次のようになります。

#!/usr/bin/python3

import fileinput

with fileinput.input(inplace=True) as f:
    for line in f:
        line = line.rstrip()
        line = line.lower()
        print(line)

このプログラムを仮にto_lower.pyとすると挙動は次の通りです。

$ cat upper.txt
AAA
BBB
$ ./lower.py upper.txt
$ cat upper.txt
aaa
bbb

行を削除したい場合には単純にスキップすれば良いようです。>で始まる行を削除するコードを考えてみました。

#!/usr/bin/python3

import fileinput

with fileinput.input(inplace=True) as f:
    for line in f:
        if line.startswith(">"):
            continue
        print(line, end="")

挙動は次の通りです。一応スクリプト名はdelete.pyです。

$ cat test.fas
>A
aaa
>B
bbb
$ ./delete.py test.fas
$ cat test.fas
aaa
bbb

惜しむらくは置換したり、削除する部分をプログラム中で指定してしまっている点ですね。argparseと組み合わせるとsedもどきが作れそうな気がするので、機会があれば挑戦してみたい所です。

何はともあれ最後までお読みいただきありがとうございました。

参考リンク