【Python】返り値がありすぎて順番忘れちゃった。。

「Effective Python 第2版」を読んでいて、調べた内容をまとめました。

## 順番ってこれであってるんだっけ？
Pythonを書いていてこういうことはないでしょうか？私はあります。

```python:unpack.py
# これは安心できる
x, y = point2d

# これは...順番あってるんだっけ？
weather, precipitation, temperature, humidity = \
    get_meteorological_info(place, time)
print(weather)
print(precipitation)
print(temperature)
print(humidity)
```
Pythonではアンパックやタプルの作成をどこでも自由に行えるために、コードの意味が曖昧になりやすいです。この例では、順番に特に意味もありません。これを改善するためには適切な制限を与えてあげれば良さそうです。Pythonではデータを表現するクラスを利用することで改善できます。


## どうするか
こんな感じでアクセスできれば分かりやすそうです。

```python:no_unpack.py
info_meteo = get_meteorological_info(place, time)
print(info_meteo.weather)
print(info_meteo.precipitation)
print(info_meteo.temperature)
print(info_meteo.humidity)
```
クラスのインスタンスを返すことによって、順番を意識する必要がなくなりました。`dict`でも似たようなことはできますが、キーが`str`などになるのでタイプミスの可能性やエディタの補完機能などの問題であまり嬉しくありません。

これを実現するためにはいくつか方法があります。

1. 素直にクラスを作成
1. namedtupleを利用
1. dataclassを利用(Python 3.7から)

実装上異なるように見えますが、クラスを作成しているという点では共通しています。個人的には下に行くほどお勧めです。

### 素直にクラスを作成
クラスさえわかれば簡単に作成できるので、誰でもわかるのが利点だと思います。例を書きましたが、`self.weather = wheather`のような記述が冗長になるのが欠点です。

```python:simple_class.py
class MeteologicalInfo:
    def __init__(
            self,
            weather: str,
            precipitation: float,
            temperature: float,
            humidity: float):
        self.weather = weather
        self.precipitation = precipitation
        self.temperature = temperature
        self.humidity = humidity
```

### namedtupleを利用
https://qiita.com/Seny/items/add4d03876f505442136
この記事が詳しいですが、簡単なまとめです。
クラスをそのまま描いた時と比べると冗長性がなくなったと思います。
`typing`を使ってクラスとして定義した方がエディタの補完機能が使えてお勧めです。
ミュータブルとして使いたい時は、困るかもしれません。小さいデータ向けだと思います。

```python:use_namedtuple.py
from collections import namedtuple

# 最も単純な定義方法
MeteologicalInfo = namedtuple(
    "MeteologicalInfo",
    [
        "weather",
        "precipitation",
        "temperature",
        "humidity"
    ]
)

# 空白区切りでも定義できます
MeteologicalInfo = namedtuple(
    "MeteologicalInfo",
    "weather precipitation temperature humidity"
)

# デフォルト値を設定できます
MeteologicalInfo.__new__.__defaults__ = ("sunny", 0.0, 25.0, 0.5)

from typing import NamedTuple

# クラスとして定義する方法
class MeteologicalInfo(NamedTuple):
    weather      : str
    precipitation: float
    temperature  : float
    humidity     : float

# デフォルト値を設定することもできます
class MeteologicalInfo(NamedTuple):
    weather      : str   = "sunny"
    precipitation: float = 0.0
    temperature  : float = 25.0
    humidity     : float = 0.5

```

### dataclassを利用(Python 3.7から)
https://qiita.com/tag1216/items/13b032348c893667862a
これの使い方はここがわかりやすかったです。
https://docs.python.org/ja/3.7/library/dataclasses.html
より詳しい情報はこちらです。
簡単にまとめます。

namedtupleに対してミュータブルとしても、イミュータブルとしても使えるのが利点になると思います。また、dataclasses.fieldなど便利な機能を使うことができるのも利点です。ただ、Python3.7以上である必要があるので、使っているモジュールや環境によって使えない場合があるのが欠点です。

```python3:use_dataclass.py
from dataclasses import dataclass

# デコレータを使うことによってクラスをdataclassとして使うことができます
@dataclass
class MeteologicalInfo:
    weather      : str
    precipitation: float
    temperature  : float
    humidity     : float

# デフォルト値も設定できます
@dataclass
class MeteologicalInfo:
    weather      : str   = "sunny"
    precipitation: float = 0.0
    temperature  : float = 25.0
    humidity     : float = 0.5

```

# まとめ
データが複雑になったり、変数が増えてきたりした時は、クラス、namedtuple, dataclassなどを利用することでバラバラなデータに意味を持たせることができます。dictやlistをそのまま使っていると、プログラムが大きくなった時に不安要素が多くなり開発のスピードが下がってしまいますが、データの構造とアルゴリズムをある程度切り離して考えることができミスも減らせると思っています。