[Python] ファイルを読み込みコメント行と空行以外を表示

# まえがき

本記事は、Perl で書いたものの Python 移植版です。
(https://qiita.com/okateim/items/4df7ebca399901660072)

同じ内容で言語を変えたときにPV数にどれくらい差がでるのだろうと
気になって同じ内容2言語で書いてみた次第です。
（やっぱり Python のほうがたくさん見られてるみたいです）

Python での変数定義、ファイル読み込み、
および正規表現処理の復習と備忘録を兼ねて記事に残します。
Better な書き方があれば是非ご教授くださいませ。。

なお、今回用いた Python のバージョンは以下のとおりです。

```shell-session
[MBA ~/work/python]$ python --version
Python 3.6.3 :: Anaconda, Inc.
[MBA ~/work/python]$ 
```

# 概要

指定されたファイルを読み込み、
コメント行（# で始まる行）と空行を除いて
標準出力に出力するだけのプログラム `remove_comments.py` を書きました。

```shell-session
[MBA ~/work/python]$ cat test.txt 

hoge

# hogehoge
    # huga
    huga

### hugahuga

[MBA ~/work/python]$ 
[MBA ~/work/python]$ python remove_comments.py test.txt 
hoge
    huga
[MBA ~/work/python]$ 
```

引数がなかったり、2つ以上指定されていた場合はエラー終了します。
指定されたファイルが無い場合もエラー終了します。
（エラーメッセージの雑さは許してください）

```shell-session
[MBA ~/work/python]$ python remove_comments.py test.txt /etc/hosts
Invalid arguments
[MBA ~/work/python]$ python remove_comments.py
Invalid arguments
[MBA ~/work/python]$ python remove_comments.py hoge
hoge: No such file or directory
[MBA ~/work/python]$ 
```

# コード

以下が実装です。

```python:remove_comments.py
import sys
import os
import re

EXIT_SUCCESS = 0
EXIT_FAILURE = 1

ERR_MSG = {
    'INVALID_ARGS': "Invalid arguments\n",
    'FILE_NOT_FOUND': "No such file or directory\n",
}

def main():
    check_args()
    (_, filename) = sys.argv
    with open(filename, "r") as f:
        match_pattern = r'^\s*(#.*|)$'
        line = f.readline()
        while line:
            match = re.match(match_pattern, line)
            if not match:
                print(line, end="")
            line = f.readline()
    return EXIT_SUCCESS

def check_args():
    ARGS_NUM = 2;
    if len(sys.argv) != ARGS_NUM:
        sys.stderr.write(ERR_MSG['INVALID_ARGS'])
        sys.exit(EXIT_FAILURE);
    (_, filename) = sys.argv
    if not os.path.isfile(filename):
        sys.stderr.write(filename + ": " + ERR_MSG['FILE_NOT_FOUND'])
        sys.exit(EXIT_FAILURE)

if __name__ == '__main__': 
    main()
```

## モジュールインポート部分・定数定義部分

```python
import sys
import os
import re

EXIT_SUCCESS = 0
EXIT_FAILURE = 1

ERR_MSG = {
    'INVALID_ARGS': "Invalid arguments\n",
    'FILE_NOT_FOUND': "No such file or directory\n",
}
```

`import ...` で、プログラム内で使うモジュールをインポートします。
辞書変数 `ERR_MSG` で、エラーメッセージを定義しています。
実際は定数定義用のクラス `config.py` なんかを作って、それを `import` するのがよいと思います。
今回はコードの練習ということで...

## 引数チェック: check_args()

引数の数の確認および指定されたファイルの存在を確認します。

```python
def check_args():
    ARGS_NUM = 2;
    if len(sys.argv) != ARGS_NUM:
        sys.stderr.write(ERR_MSG['INVALID_ARGS'])
        sys.exit(EXIT_FAILURE);
    (_, filename) = sys.argv
    if not os.path.isfile(filename):
        sys.stderr.write(filename + ": " + ERR_MSG['FILE_NOT_FOUND'])
        sys.exit(EXIT_FAILURE)
```

## メインルーチン: main()

```python
def main():
    check_args()
    (_, filename) = sys.argv
    with open(filename, "r") as f:
        match_pattern = r'^\s*(#.*|)$'
        line = f.readline()
        while line:
            match = re.match(match_pattern, line)
            if not match:
                print(line, end="")
            line = f.readline()
    return EXIT_SUCCESS
```

`with open(filename, "r") as f:` で、ファイルを読み出します。書き込みのときは `"w"`。
`with` を利用することで、処理終了時に自動でファイルの閉じてくれます。
インデントは深くなりますが、便利です。ファイルを開くときは、必ず使うべきです。

`f.readline()` で、1行だけ読み出し、変数 `line` に格納します。
`while line: line = f.readline()` とすることで、1行ずつの読み出しを繰り返します。

`match_pattern = r'^\s*(#.*|)$'` で正規表現のパターンを定義し、
`match = re.match(match_pattern, line)` として、マッチオブジェクトを得ます。
このパターンにマッチしないものが欲しいので、
`if not match:` とします。
マッチしたものが欲しいときは `if match:` です。
マッチした部分の文字列が欲しい場合は、マッチオブジェクト `match` に対して、
`match.group(0)`, `match.group(1)`などによって取得します。詳細はここには書きません。

ちなみに、`re.match()` は、先頭一致のみマッチします。
文字列のどこかでマッチすればよい場合は `re.search()` を使います。

# おわりに

Python で、基本的なファイル操作および正規表現操作を実装しました。
久しぶりに Python でコードを書いたので文法やメソッドをかなり忘れていました。。
やはりコードは定期的に書くべきですね（反省）。