相手に寄り添った Python コードの作成

# 相手に寄り添った Python コードの作成

## はじめに

はじめまして。私は大学院生です。

この記事では、Python コードの書き方に気を使うことで、他の人のストレス軽減に繋がるような記事を書きます。既に他のエンジニアの方々も多く記事を公開されていますので、被っている部分が多いです。

あくまでも私にとって良いコードなので、より良いコードの書き方もあると思います。気軽に見ていただけると嬉しいです。

この記事が、Python を使うエンジニアの負担軽減に寄与できれば幸いです。

## コメント

コードにコメントを残すことはありますが、不要なコメントは消すべきです。残すべきコメントとしては、以下のようなコメントです。

- To do などの今後やるべきことを書いたコメント

- 一眼見て理解することが難しいコードに対するコメント
    - 関数
    - クラス
    - （三項演算子）
    - （内包表記）

## ライブラリのインポートの位置

ライブラリは、基本的には各ファイルの先頭に記載した方が良いと思います。

```python:main.py
import numpy as np

def main():
    array = np.array([-1, 0, 1])
    print(array)

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

条件分岐などの都合上、必ずしもインポートされないライブラリは、上記に従わなくてもいいと思います。

```python
tensor_type = "pt"

if tensor_type=="pt":
    import torch

else:
    import tensorflow
```

## 命名

研究や開発では、多くの変数が登場します。そのため、初めて見た人が素早く理解できる変数であることは重要です。良い変数の条件は以下のような条件です。

- 英単語を使う
    - 日本語を使わない
    - 単数形と複数形を必要に応じて使い分ける

- 明示的な単語を使う

- 具体的な説明をする

| 命名規則 | 説明 | 例 |
| :--: | :--: | :--: |
| スネークケース | 単語をアンダースコア `_` で区切り、全て小文字にする。 | `snake_case` |
| アッパーキャメルケース | 単語の先頭を大文字にして区切る。 | `UpperCamelCase` |

個人的には、スネークケースとアッパーキャメルケースを以下のように使い分けることが多いです。

- スネークケース：変数や関数名

- アッパーキャメルケース：クラス名

## 関数・クラス

### 型ヒント

Python の型ヒントを使うことで、以下の効果が期待できます。

- コードの可読性向上

- 統合開発環境 (IDE) コード補間の充実

- 静的型チェックの実行

```python: 二次関数
def quadratic_function(x: float) -> float:
    
    y = x**2
    
    return y
```

```python: 自己紹介
class MyProfile:

    def __init__(self, name: str):
        
        self.name = name
    
    def say_name(self):
        
        print(f"私は{self.name}です。")
    
    def say_name_and_age(self, age: int):
        
        print(f"私は{self.name}です。年齢は{age}歳です。")
```

### ドキュメンテーション文字列

関数やクラスの先頭に、`"""` を使って関数やクラスの説明を書きます。記載した方が良い内容は、大きく分けて三つあります。

- 関数・クラスの概要
- 引数の説明
- 返り値の説明

```python: 二次関数
def quadratic_function(x: float) -> float:
    
    """
    受け取った値を二乗して返す関数です。
    
    引数:
        x (`float`):
            浮動小数点数の実数です。
    
    返り値:
        y (`float`):
            引数で受け取った値の二乗をした値です。
    """
    
    y = x**2
    
    return y
```

```python: 自己紹介
class MyProfile:

    """
    自己紹介をするクラスです。
    """

    def __init__(self, name: str):

        """
        コンストラクタです。

        引数:
            name (`str`):
                名前を表す文字列です。
        """
        
        self.name = name
    
    def say_name(self):

        """
        名前を表示するメソッドです。
        """
        
        print(f"私は{self.name}です。")
    
    def say_name_and_age(self, age: int):

        """
        名前と年齢を表示するメソッドです。

        引数:
            age (`int`):
                年齢を表す整数です。
        """
        
        print(f"私は{self.name}です。年齢は {age} 歳です。")
```

### デコレータを付与する

デコレータは、関数やクラスの前後に特定の処理を追加する機能です。デコレータは見る人によっては可読性が下がる場合もありますが、一般的に使われるデコレータは積極的に導入するべきです。例えば、`@staticmethod` や `@classmethod` があります。PyTorch を使っている人の場合だと、推論時に `@torch.no_grad()` を使う人もいます。

#### staticmethod を使う

staticmethod は、特定のインスタンスに依存しないメソッドを作成することができます。通常のメソッドは、クラスのインスタンス `self` を最初の引数として受け取りますが、staticmethod では必要ありません。
以下の場合、staticmethod を使った方が好ましいです。

- メソッドがクラスやインスタンスの状態にアクセスせず、外部から渡された引数だけで処理を行う場合
- メソッドがクラスやインスタンスの他の部分と関係がなく、単に関連するユーティリティ関数として機能する場合

```python
class MyProfile:
    
    @staticmethod
    def say_age(age: int):

        """
        年齢を表示するメソッドです。

        引数:
            age (`int`):
                年齢を表す整数です。
        """
        
        print(f"年齢は {age} 歳です。")
```

```python
class MyProfile:

    """
    自己紹介をするクラスです。
    """

    def __init__(self, name: str):

        """
        コンストラクタです。

        引数:
            name (`str`):
                名前を表す文字列です。
        """
        
        self.name = name
    
    def say_name(self):

        """
        名前を表示するメソッドです。
        """
        
        print(f"私は{self.name}です。")
    
    @staticmethod
    def say_age(age: int):

        """
        年齢を表示するメソッドです。

        引数:
            age (`int`):
                年齢を表す整数です。
        """
        
        print(f"年齢は{age}歳です。")
```

## 不要な返り値の代入

必要以上に変数の定義はしない方がいいです。ただ、ライブラリなどの返り値には不要な値も含まれていることが多々あります。そこで、不要な値の代入には、アンダースコア `_` を使うことが一般的です。

```python
name_list = [["山田", "太朗"], ["田中", "次郎"], ["佐藤", "花子"]]

for family, _ in name_list:
    print(family)
```

```python
member_info = {
    "山田": {
        "年齢": 20,
        "出身地": "東京"
    },
    "田中": {
        "年齢": 25,
        "出身地": "大阪"
    }
}

def get_member_info(member_info: dict) -> tuple[list[int], list[str]]:
    
    """
    メンバーの情報を取得する関数です。

    引数:
        member_info (`dict`):
            メンバーの情報が格納された辞書です。

    返り値:
        age_list (`list` of `int`):
            メンバーの年齢が格納されたリストです。
        name_list (`list` of `str`):
            メンバーの出身地が格納されたリストです
    """
    
    age_list = []
    name_list = []

    for name in member_info.keys():
        age_list.append(member_info[name]["年齢"])
        name_list.append(member_info[name]["出身地"])

    return age_list, name_list

age_list, _ = get_member_info(member_info)
```

## 空白・改行規則の統一

空白や改行は、統一した方が見やすいです。
ここは、人によって好みが分かれると他より分かれると思います。

### 空白の統一

変数の定義の場合は、以下のように `変数名` + `空白` + `=` + `空白` + `値` のように書いています。

```python
name = "山田太朗"
age = 20
```

一方、関数の引数では、以下のように`変数名` + `=` + `値` のように書いています。

```python
def quadratic_function(x, a=1.0):
    y = a * x**2
    return y

def quadratic_function(x: float, a: float=1.0) -> float:
    y = a * x**2
    return y

quadratic_function(2, a=3)
```

### 改行の統一

改行は空白よりも統一が難しいと思っています。理由は、いくつかあります。

- コードのどのタイミングで、一行空けるのか

- 長い文字列でどこに `\` を入れるのか

- （ネストを避けた構文）

正直なところ、私自身もここは悩みどころですが、外部ツールを使うことである程度は統一できると思います。例えば、コード整形のツールや VSCode の拡張機能などがあります。共同でコードを作成する方と同じツールを使うことである程度のストレスを軽減できると思います。

## n 重の内包表記は避ける

Python では、内包表記という記法がありますが、特に $3$ 重以上の内包表記は避けて方が良いです。
次のコードは、$1$ 重の内包表記です。上と下の書き方は違いますが、結果は同じになります。

```python
squared_list = []
for x in range(10):
    squared_list.append(x**2)

squared_list = [x**2 for x in range(10)]
```

以下に、$2$ 重の内包表記を書きます。

```python
all_squared_list = []
for x in range(10):
    squared_list = []
    for a in range(3):
        squared_list.append(a * x**2)
    all_squared_list.append(squared_list)

all_squared_list = [[a * x**2 for a in range(3)] for x in range(10)]
```

上記のコードでは、二次元の配列になるように実装しましたが、一次元の配列のコードではこれ以上に見にくいと思います。

```python
all_squared_list = []
for x in range(10):
    for a in range(3):
        all_squared_list.append(a * x**2)

all_squared_list = [a * x**2 for x in range(10) for a in range(3)]
```

## おわりに

この記事では、相手に寄り添った Python コードの作成について書きました。

この記事が、Python を使うエンジニアの負担軽減に繋がれば幸いです。