Pythonプログラムをしっかりと書く虎の巻

Pythonを今まで適当に書いてきましたが、プログラムをしっかりと作りたいと考え、いろいろ勉強・調査しました。そこで学んだことをこの記事に書きます。

# 参考情報
- [Google Python Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md): 全部読み切れていませんが、いいこと書いてあります。
- [エキスパートPythonプログラミング改訂2版](https://amzn.to/30L5NgF): 初中級というより中級者向け。読み飛ばしている部分も多いですが、懐に置いておいて必要時に読みたい本。一皮むけたい人にはとてもおすすめ。

# コーディング規約
Pythonには[PEP8](https://pep8-ja.readthedocs.io/ja/latest/)というコーディング規約があります。せっかくあるので従うべきでしょう。[PyCharm](https://www.jetbrains.com/pycharm/)のようなIDEを使うと警告を出してくれるので効率よくPEP8に準拠したプログラムを書くことが出来ます。
私感で重要と考えるものだけ簡潔に抜粋しておきます。

- [コードのレイアウト](https://pep8-ja.readthedocs.io/ja/latest/#id4)
    - [1レベルインデントするごとに、スペースを4つ使う](https://pep8-ja.readthedocs.io/ja/latest/#id5)
    - [(タブではなく)スペースが好ましいインデントの方法](https://pep8-ja.readthedocs.io/ja/latest/)
    - [行長を最大79文字までに制限](https://pep8-ja.readthedocs.io/ja/latest/#id8)
    - [トップレベルの関数やクラスは2行、クラス内部のメソッドは1行ずつ空ける](https://pep8-ja.readthedocs.io/ja/latest/#id11)
    - [import文は通常は行を分ける(`from xxx import a, b`はOK)](https://pep8-ja.readthedocs.io/ja/latest/#import)
- [シングルコーテーションかダブルコーテーションかを統一する](https://pep8-ja.readthedocs.io/ja/latest/#id14)
- [式や文中の空白文字に関する規則](https://pep8-ja.readthedocs.io/ja/latest/#id15)
- [関数アノテーション](https://pep8-ja.readthedocs.io/ja/latest/#id42)
- [変数アノテーション](https://pep8-ja.readthedocs.io/ja/latest/#id45)
- [命名規約](https://pep8-ja.readthedocs.io/ja/latest/#id23)

|オブジェクト|ルール|例|
|:---:|:---|:---:|
|[パッケージ(フォルダ名)](https://pep8-ja.readthedocs.io/ja/latest/#id30)|全小文字の短い名称。アンダースコア非推奨|sample|
|[モジュール](https://pep8-ja.readthedocs.io/ja/latest/#id30)|全小文字の短い名称。読みやすいならアンダースコア可能|sapmle_module|
|[クラス](https://pep8-ja.readthedocs.io/ja/latest/#id31)|パスカルケース|SampleClass|
|[例外](https://pep8-ja.readthedocs.io/ja/latest/#id33)|パスカルケースで末尾は"Error"|SampleError|
|[関数・変数](https://pep8-ja.readthedocs.io/ja/latest/#id35)|全小文字でアンダースコア可能|sample_function|
|[関数やメソッドに渡す引数](https://pep8-ja.readthedocs.io/ja/latest/#id36)|インスタンスメソッドの第一引数名`self`<br>クラスメソッドの第一引数名は`cls`<br>引数名が予約語と衝突していた場合、アンダースコアを末尾に追加|sample_parameter|
|[メソッド名とインスタンス変数](https://pep8-ja.readthedocs.io/ja/latest/#id37)|全小文字でアンダースコア可能<br>非公開のメソッドやインスタンス変数は接頭辞がアンダースコア|sample_public<br>_sample_private|
|[定数](https://pep8-ja.readthedocs.io/ja/latest/#id38)|全大文字でアンダースコア可能|SAMPLE_CONSTANT|

## [命名規約(アンダーバーを変数前後につける場合)](https://pep8-ja.readthedocs.io/ja/latest/#id25)
変数の前後にアンダーバーをつける場合があり、その説明を紹介します。基本的にクラス内のPrivateな変数はアンダーバーを2つ接頭辞にするのがよさそうです。

> `_single_leading_underscore`: "内部でだけ使う" ことを示します。 たとえば from M import * は、アンダースコアで始まる名前のオブジェクトをimportしません。

> `single_trailing_underscore_`: Python のキーワードと衝突するのを避けるために使われる規約です。例を以下に挙げます:

> `__double_leading_underscore`: クラスの属性に名前を付けるときに、名前のマングリング機構を呼び出します (クラス Foobar の __boo という名前は _FooBar__boo になります。以下も参照してください)

> `__double_leading_and_trailing_underscore__`: ユーザーが制御する名前空間に存在する "マジック"オブジェクト または "マジック"属性です。 たとえば __init__, __import__, __file__ が挙げられます。この手の名前を再発明するのはやめましょう。ドキュメントに書かれているものだけを使ってください。

# プロジェクト構造
残念ながらプロジェクト構造についてPEP8に記載がないです。いくつか参考にした情報です。

## シンプルな方式
[ゼロから学ぶ Python](https://rinatz.github.io/python-book/ch04-06-project-structures/)から抜粋。

```
(project)
├── (project)  ............ プログラムのソースコードディレクトリ
│  ├── __init__.py
│  └── *.py
└── tests  ................ 単体テストのソースコードディレクトリ
   ├── __init__.py
   └── *.py
```

## 機械学習系
[データサイエンスプロジェクトのディレクトリ構成どうするか問題](https://takuti.me/note/data-science-project-structure/)から抜粋。notebook以下がメインとなるプログラム？

```
.
├── README.md
├── config
│   └── example.ini
├── converter
│   ├── __init__.py
│   └── converter.py
├── data
├── experiment.py
├── notebook
│   └── sample.ipynb
├── requirements.txt
├── results
└── tool
    └── tool.py
```

## Google AI Platform式
[Recommended project structure](https://cloud.google.com/ml-engine/docs/tensorflow/packaging-trainer#project-structure)から抜粋。このくらいシンプルな方が好きなのですが、ローカルでも動かすとなるとデータ・ログはどこに置く？という感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/f1b4bd70-2b28-c011-922b-b20ddc1ee514.png)

# [docstring](https://en.wikipedia.org/wiki/Docstring)を使う
[docstring](https://en.wikipedia.org/wiki/Docstring)は関数やクラスなどへのソースコード上のコメントによる説明です。あとで自分で読み返した時や、他人に共有するときなどやはり書いておくといいですよね。また、書いておくとツールを使ってドキュメントの自動生成もできるようです。
Pythonでは、主に[Google Style](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings)と[Numpy Style]((https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard))があるようです。
[「Google vs NumPy」](http://www.sphinx-doc.org/ja/stable/ext/napoleon.html#google-vs-numpy)を見ると比較がわかりやすいのですが、Google Styleの方が行数を少なく書くことができます。行数が少ないソースが好きなので、私はGoogle Styleを使います。
PyCharmでは[Python 統合ツール](https://pleiades.io/help/pycharm/settings-tools-python-integrated-tools.html)から設定すると、Styleに応じた補完などをしてくれます。

## [docstring](https://en.wikipedia.org/wiki/Docstring)のセクション一覧
よく使うセクション一覧です。

|セクション|内容|
|:-:|:--|
|Args|関数引数|
|Returns|関数戻り値|
|Raises|関数例外|
|Examples|関数の使用例|
|Notes|関数/クラスの注記|
|Attributes|クラス属性|

## [docstring](https://en.wikipedia.org/wiki/Docstring)の例
###例1 関数

```python
def fetch_bigtable_rows(big_table, keys):
    """関数に対する1行のサマリ説明

    サマリ説明から1行を空けて詳しい説明を書きます
    Retrieves rows pertaining to the given keys from the Table instance
    represented by big_table.  Silly things may happen if
    other_silly_variable is not None.

    Args:
        big_table: 引数1の説明
        keys: 引数2の説明

    Returns:
        戻り値の説明。複数行に渡って書くのもOK。ここでの例は戻り値の具体例まで出していてわかりやすい。
        example:

        {'Serak': ('Rigel VII', 'Preparer'),
         'Zim': ('Irk', 'Invader'),
         'Lrrr': ('Omicron Persei 8', 'Emperor')}

        If a key from the keys argument is missing from the dictionary,
        then that row was not found in the table.

    Raises:
        IOError: 例外の説明

    Example:
        result = fetch_bigtable_rows(big_table, keys)
    """
```

### 例2 クラス

```python
class SampleClass(object):
    """関数に対する1行のサマリ説明

    サマリ説明から1行を空けて詳しい説明を書きます
    Longer class information....

    Attributes:
        likes_spam: 属性1の説明
        eggs: 属性2の説明
    """

    def __init__(self, likes_spam=False):
        """Inits SampleClass with blah."""
        self.likes_spam = likes_spam
        self.eggs = 0

    def public_method(self):
        """Performs operation blah."""
```

# 型ヒント(Type Hints)を使う
型ヒント(Type Hints)を使ってアノテーションして**関数の引数・戻り値に対して型を明示することができます**。Pythonは動的型付け言語なので型指定をしないでいいという反面、関数の引数・戻り値がわかりにくいです。型ヒント(Type Hints)を使えば、PyCharmなどのIDEで色々とサポート(代入する値が違っているのではとの警告など)をしてくれたりします。
**書くのが面倒な反面、可読性が上がるので基本的には型ヒントを使うのが望ましい**かと思います。
型ヒントは[PEP484](https://www.python.org/dev/peps/pep-0484)と[PEP526](https://www.python.org/dev/peps/pep-0526)で規定されていて、[typingパッケージ](https://docs.python.org/ja/3/library/typing.html)がサポートします。「`typing モジュールは暫定的に標準ライブラリに追加されました`」と書かれていてまだ「暫定」扱いだそうです。

## 型ヒントの基本
引数はコロンの後にスペースをはさんで型を書きます。
戻り値は`->`の後に型を書きます。

```python
def greeting(name: str) -> str:
    return 'Hello ' + name
```

以下が基本的な型です。

- str
- int
- float
- bool
- None

## 型ヒント(辞書型、タプル型、リスト型)
よく使う辞書型、タプル型、リスト型です。
[typingパッケージ](https://docs.python.org/ja/3/library/typing.html)からインポートして使います。
returnが複数の場合は、`Tuple`を使います。

```python
from typing import Dict, Tuple, List

def test(dict_: Dict[str, int], list_: List[str]) -> Tuple[str, int]:
    print(dict_)
    print(list_)
    return 'aa', 1
```

## 型ヒント(オブジェクト)
オブジェクト名を書きます。オブジェクト名を調べるのが少し面倒です。

```python
from logging import getLogger, Logger

def get_module_logger() -> Logger:
    logger = getLogger(__name__)
    return logger
```

この辺を参考にしました。とてもわかりやすかったです。当記事では、基本だけ書いたのですがもっと複雑なこともできます。

- [Pythonではじまる、型のある世界](https://qiita.com/icoxfog417/items/c17eb042f4735b7924a3)
- [Pythonの型ヒントチートシート](https://www.utakata.work/entry/20190209/1549675226)

# GitHubを使う
開発・検証したプログラムをすべてローカルに置いておくのは面倒です。GitHubは情報共有もできて、やはり非常に便利。共同開発をいない場合にも使わない手はないです。
記事[「Git初心者がプログラムをGitHubに置いておく方法」](https://qiita.com/FukuharaYohei/items/78cddf7dd805a429b069)のレベルで使っています。
Pythonに限定するなら、[Python用".gitignore"テンプレート](https://github.com/github/gitignore/blob/master/Python.gitignore)を使うと、少しリポジトリがすっきりします。

# ログ出力
いつまでも`print`だけに頼っていられません。
"logging"パッケージを使うことで、ファイルにログを簡単に残しておくことができたり、出力元ソースコードや日時を自動出力できたりとメリット多いです。
詳しくは、記事[「Pythonでprintを卒業してログ出力をいい感じにする」](https://qiita.com/FukuharaYohei/items/92795107032c8c0bfd19)を見てください。