【随時更新】プログラム初心者に伝えたい、これだけはやっておいてくれというポイント [#Python 編]


## 前提
データサイエンス・機械学習にあこがれてプログラミングを始めた人に、これだけはやっておいて欲しいという計算研究者からの願いを書いておきます。前職のラボ後輩たちが研究成果を外部公開できる形にまとめられていないのがもったいないなと思ったことがあるので、私の思いつくTipsをまとめます。

開発時から実践しておくと開発時間が短縮されるものもあるので試してください。なおVisual Studio Codeの利用は網羅できていません。

### `logging` を最初から使ってログを出しておく

デバッグ作業をするときにログは重要です。
どこまでの処理が走っていて、どこの処理で躓いているのかを明らかにできます。pythonでは少なくともバージョン3.7以降標準ライブラリとして`logging`があり、`import logging`でいつでもログ出力のライブラリが利用できます。関数やクラスを書いたら`logging`を仕込んで置くのが無難です。`logging`のログレベルをいじれば、出力したいログの種類を制限できます。これにより開発環境と本番環境でloglevelを変えれば見せたいログを制御できます。`logging.basicConfig`の`level`引数に指定するだけですので[ドキュメント](https://docs.python.org/3/howto/logging.html#basic-logging-tutorial)も読みつつ実装しましょう。

以下はサクッと書いたサンプルコードです。

```python
$ cat test.py # ターミナル上で実行
import logging
logging.basicConfig(
    format='[%(levelname)s]%(asctime)s: %(message)s',
    datefmt='%Y-%m-%dT%H:%M:%S',
    level=logging.INFO)

def main():
    logger = logging.getLogger(__name__)
    logging.info('GUOOOO')
    logger.error('WAWAWA')

if __name__ == '__main__':
    main()

$ python test.py # ターミナル上で実行
[INFO]2021-09-19T11:42:58: GUOOOO
[ERROR]2021-09-19T11:42:58: WAWAWA
```

### スクリプトでは`ifmain`を使う

Pythonスクリプトを書くときは、変数や関数のスコープに影響されずに処理を書く目的で`main`関数を定義して`main`関数を`if __name__=='__main__':`ブロックから呼び出します。`if __name__=='__main__':`ブロック内に記述したものだけがファイルをスクリプト実行した際に読み取られます。以下の例では、`python main.py`と実行するときだけ`if __name__=='__main__':`ブロック内が読み取られることになります。これは慣習ですが、そうすることで記述が楽になったりします。

```python:main.py

def main():
    # 最初に処理の流れを定義して良い
    # 冒頭にあることでファイルを開いてすぐに
    # ファイル内の関数の使い方の例が示されたことになる。
    func_a()
    func_b()

# 複数の関数を定義
def func_a():
    pass
def func_b():
    pass

if __name__ == '__main__':
    # スクリプト実行したときだけ以下が読み取られる
    main()
```

C.f. [Python Tips: Python で main 関数を定義することのメリット](https://www.lifewithpython.com/2021/01/python-main-function.html)

### Jupyterで作った(計算)処理で何度も使用するものがあればスクリプトにして保存しておく

何度も使うJupyter Notebookは、Jupyterで開くのではなくスクリプト実行できるようにしておきましょう。マジで。スクリプト実行とはターミナルやコマンドプロンプトから `python script_name.py`と実行することです。入力データがあるときは `python script_name.py path/to/data.csv` などとすると入力を与えることができます。

スクリプトに入力を与える一番原始的な方法は、 `sys.argv`を用いることです。Pythonに与えた入力全てが配列として与えられるのでスクリプト実行する場合はスクリプト名自体も与えられます。以下のように使います。

```python:feed.py
$ cat feed.py
import pandas as pd
import sys

def main():
    argv = sys.argv
    print(argv) # print文でデバッグ
    filename = argv[1]
    df = pd.read_csv(filename)
    print(df.shape)

if __name__ == '__main__':
    main()

$ python feed.py data.txt
['feed.py', 'data.txt']
(1, 2)
$ cat data.txt
sample, 10
example, 40

```

### `sys.argv`の代わりに`argparse.ArgumentParser`を使う

直前で書いた`sys.argv`を`argparse.ArgumentParser`で書き直していきます。`sys.argv`では配列で与えていたので変数を辞書のキーで指定することができませんでした。これは可読性に影響します。

そこで、`argparse.ArgumentParser`を使うと柔軟にスクリプトへの入力を指定することができます。以下のスクリプト`feed.py`ではprint文を`logging`へ差し替えました。それっぽいですね。

```python:feed.py
$ cat feed.py
import pandas as pd
import argparse
import logging
logging.basicConfig(level=logging.INFO)
def main():
    parser = argparse.ArgumentParser(description='Description')
    parser.add_argument('filename', help='Give csv file name to read with pandas.')
    args = parser.parse_args()
    logging.info(args) # logging.info文でデバッグ
    logging.info(f'args.filename: {args.filename}')
    filename = args.filename
    df = pd.read_csv(filename)
    logging.info(df.shape)

if __name__ == '__main__':
    main()

$ python feed.py data.txt
INFO:root:Namespace(filename='data.txt')
INFO:root:args.filename: data.txt
INFO:root:(1, 2)
```

## まとめ

利用シーンにもよりますが、他人に使われなかったら効率が悪いことも多々あります。生産性高く生きていきましょう。