Python パッケージを作成する際の確認事項

## 初めに

Python パッケージを作成する際，個人的に気を付けていることをまとめます．

以下，Python 3.5 以降の使用を前提とします．

## 確認事項

### フォーマットを統一する

開発者によって文字コードや改行コードが異なると厄介なので，[EditorConfig](https://editorconfig.org) を利用して統一しましょう．

```.editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true

[*.py]
indent_style = space
indent_size = 4
```

### 依存パッケージのバージョンを明記する

他の開発者が実行環境を再現できるように，`setup.py` や `requirements.txt` に依存パッケージのバージョンを明記しましょう．

```setup.cfg
[options]
install_requires = numpy>=1.11.0, scipy>=0.17.0
```

`pipenv` を使うと尚良いです．

### コーディング規約に準拠する

複数人が一貫したコードを記述できるように，コーディング規約に準拠しましょう．

[PEP 8 -- Style Guide for Python Code | Python.org](https://www.python.org/dev/peps/pep-0008/)

[Flake8](http://flake8.pycqa.org/en/latest/) や [Pylint](https://www.pylint.org/) を利用して違反しているかどうかを確認します．無視したい警告がある場合，`setup.cfg` や `.flake8` にその旨を記述して下さい．

```setup.cfg
[flake8]
ignore = E221, E241, E251, E402
max-line-length = 79
```

`autopep8` や `black` を使うと，コードを自動で整形してくれます．

#### エンコーディング宣言を記述しない

UTF-8 でコードを記述する場合，次のエンコーディング宣言は推奨されないため，削除しましょう．

```python
# coding: utf-8
```

#### ファイルの先頭でインポートする

特に理由がなければ，ファイルの先頭でインポートしましょう．ただし，依存パッケージを制限したい場合，関数の内部でインポートすることがあります．以下をご参照下さい．

- [python - Is it pythonic to import inside functions? - Stack Overflow](https://stackoverflow.com/questions/1024049/is-it-pythonic-to-import-inside-functions)
- [Should import statements always be at the top of a module? - Stack Overflow](https://stackoverflow.com/questions/128478/should-import-statements-always-be-at-the-top-of-a-module)
- [In Python, what happens when you import inside of a function? - Stack Overflow](https://stackoverflow.com/questions/3095071/in-python-what-happens-when-you-import-inside-of-a-function)

#### 命名規約を遵守する

次のように命名しましょう．

| 種類       | 例                   |
|------------|----------------------|
| パッケージ | my_favorite_package  |
| モジュール | my_favorite_module   |
| クラス     | MyFavoriteClass      |
| メソッド   | my_favorite_method   |
| 関数       | my_favorite_function |
| 変数       | my_favorite_variable |
| 定数       | MY_FAVORITE_CONSTANT |

##### 組み込み関数を関数名（変数名）に充てない

Python は，60 以上の組み込み関数を備えています．

[組み込み関数 — Python 3.8.0 ドキュメント](https://docs.python.org/ja/3/library/functions.html)

例えば，`dict = {'a': 1.0}` と書くと，組み込み関数 `dict` が上書きされてしまうので注意しましょう．

##### マジックナンバーを定数として保持する

マジックナンバーに適切な名前を付けて保持することで，意味が与えられ，後々の修正も容易になります．

##### 品詞に配慮する

クラス名や変数名は名詞，メソッド名や関数名は動詞というように，品詞に配慮しましょう．

### 循環的複雑度を抑制する

循環的複雑度は，コードの複雑度を測る指標で，値が大きいほどバグを含む可能性が高いことを示唆します． `setup.cfg`  や `.flake8` で設定することで，循環的複雑度を計測することができます．

```setup.cfg
[flake8]
ignore = E221, E241, E251, E402
max-complexity = 10
max-line-length = 79
```

分岐を減らす，または，メソッド（関数）を適当な単位に分割して，循環的複雑度を 10 以下に抑制してください．10 以下が難しい場合，任意の値を定めて下さい．

#### 入れ子を浅くする

入れ子を浅くすることで，可読性が高くなります．ただし，循環的複雑度は下がらないことに注意して下さい．

### アクセサを実装しない

Python は，属性を隠蔽しないため，直接代入，参照が可能です．したがって， 純粋なアクセサの実装は，無意味です．

### ログを出力する

[PEP 282 -- A Logging System | Python.org](https://www.python.org/dev/peps/pep-0282/)

ログを出力する場合，`print` 関数ではなく，`logging.Logger` を利用しましょう．

### 処理したい例外を明示的に指定する

実行に失敗したとき，原因の特定が容易になります．

```python
def div(a, b):
    try:
        ret = a / b
    except Exception as e:
        ret = np.nan

    return ret
```

```python
def div(a, b):
    try:
        ret = a / b
    except ZeroDivisionError as e:
        ret = np.nan

    return ret
```

### docstring を記述する

[PEP 257 -- Docstring Conventions | Python.org](https://www.python.org/dev/peps/pep-0257/)

多くのパッケージは，次の記法を採用しています．

- [Example Google Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
- [Example Numpy Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html)

```python
def div(a, b):
    """Division.

    Parameters
    ----------
    a
        Dividend.

    b
        Divisor.

    Returns
    -------
    ret
        Result.
    """

    try:
        ret = a / b
    except ZeroDivisionError as e:
        ret = np.nan

    return ret
```

PEP257 に準拠していることを `pydocstyle` で確認して下さい．

### doctest を記述する

全てのパブリッククラスや関数に doctest を記述してください．

```python
def div(a, b):
    """Division.

    Parameters
    ----------
    a
        Dividend.

    b
        Divisor.

    Returns
    -------
    ret
        Result.

    Examples
    --------
    >>> a = 8.0
    >>> b = 4.0
    >>> div(a, b)
    2.0
    """

    try:
        ret = a / b
    except ZeroDivisionError as e:
        ret = np.nan

    return ret
```

### 型ヒントを記述する

[PEP 484 -- Type Hints | Python.org](https://www.python.org/dev/peps/pep-0484/)

型に関する注釈を付けることで，可読性が高くなります．

```python
def div(a: float, b: float) -> float:
    """Division.

    Parameters
    ----------
    a
        Dividend.

    b
        Divisor.

    Returns
    -------
    ret
        Result.

    Examples
    --------
    >>> a = 8.0
    >>> b = 4.0
    >>> div(a, b)
    2.0
    """

    try:
        ret = a / b
    except ZeroDivisionError as e:
        ret = np.nan

    return ret
```

PEP484 に準拠していることを `mypy` で確認して下さい．

### 単体テストを実施する

開発途中であってもテストコードを必ず記述しましょう．

`unittest` や `pytest`，`nose` を適宜利用して下さい．特に，`pytest` を利用する場合，以下のような便利なプラグインがあります．

- `pytest-runner`: setuptools で pytest を実行
- `pytest-cov`: 単体テスト時に網羅率を計測
- `pytest-pydocstyle`: 単体テスト時に `pydocstyle` を実行
- `pytest-flake8`: 単体テスト時に `flake8` を実行
- `pytest-mypy`: 単体テスト時に `mypy` を実行

単体テストは，CircleCI などの CI サービス上で実行し，coveralls や codecov に網羅率を記録できると望ましいです．

### ドキュメントを作成する

Sphinx を用いてドキュメントを作成して下さい．

## 終わりに

CONTRIBUTING.md に以上のことを明記して開発の秩序を保ちましょう．

## 参考文献

[Python のコーディング規約 PEP8 に準拠する - Qiita](https://qiita.com/ynakayama/items/8616f4c0c6e372de9a42)
[循環的複雑度を下げたい - Qiita](https://qiita.com/0shoke/items/2800d17c046a4c87f312)
[ネストの深さは闇の深さ - Qiita](https://qiita.com/DeployCat/items/1ec901864d4ab11c8d6f)
[Pythonでロギングを学ぼう - Qiita](https://qiita.com/__init__/items/91e5841ed53d55a7895e)
[Python の logging について - Qiita](https://qiita.com/kitsuyui/items/5a7484a09eeacb564649)
[pythonのloggingモジュールでログ出力をする時に付加的な情報を出力する方法 - Qiita](https://qiita.com/podhmo/items/efb65ed90871ba2ac544)
[Codecovを使ってカバレッジを計測する - Qiita](https://qiita.com/nasum/items/aff9bf09d49b136bbf94)
[PythonプロジェクトのドキュメントをSphinxで作成する - Qiita](https://qiita.com/icoxfog417/items/9e2eb932b61aa9b9e427)