PythonでZipの使い道

### 本記事の背景
PythonでZipが非常にたくさん使うので、その使い道についてまとめてみました。

### Listの合成
以下は二つの異なるListから別のListに合成して出力する場合の事例
```python
age_min = [10, 20, 30, 40, 50, 60, 70, 80]
age_max = [19, 29, 39, 49, 59, 69, 79, 90]
labels = ['{}歳-{}歳の間'.format(f1, f2) for f1, f2 in zip(age_min, age_max)]
print(labels)
```
出力結果
```terminal
['10歳-19歳の間', '20歳-29歳の間', '30歳-39歳の間', '40歳-49歳の間', '50歳-59歳の間', '60歳-69歳の間', '70歳-79歳の間', '80歳-90歳の間']
```
### Listから辞書へ合成
KeyとなるListとValueとなるListから辞書型変数に合成する場合の事例
```Python
name = ["yamada", "suzuki", "sato"]
age = [20, 25, 30]
dict_data = {}
for k, v in zip(name, age):
    dict_data[k] = v
print(dict_data)
```
出力結果
```
{'yamada': 20, 'suzuki': 25, 'sato': 30}
```
下記のように内包表記で書くこともできる（結果は同じ）
```python
# 内包
dict_data = {k:v for k,v in zip(name, age)}
# 直接変換
dict_data = dict(zip(name,age))
```

### 関数の合成
以下はそれぞれ関数の戻り値を合成して出力する場合の事例
```python
import pandas as pd
start_date = '20230101'
month_count = 12
def first_day_of_month():
    date_index = pd.date_range(start_date, periods=month_count, freq="MS")
    date_ary = date_index.to_series().dt.strftime("%Y/%m/%d")
    return date_ary.values

def end_day_of_month():
    date_index = pd.date_range(start_date, periods=month_count, freq="M")
    date_ary = date_index.to_series().dt.strftime("%Y/%m/%d")
    return date_ary.values
month_list = [x for x in range(1,13)]
for m, v1, v2 in zip(month_list, first_day_of_month(), end_day_of_month()):
    s = f'{m}月の開始日{v1}：終了日{v2}'
    print(s)
```
出力結果
```terminal
1月の開始日2023/01/01：終了日2023/01/31
2月の開始日2023/02/01：終了日2023/02/28
3月の開始日2023/03/01：終了日2023/03/31
4月の開始日2023/04/01：終了日2023/04/30
5月の開始日2023/05/01：終了日2023/05/31
6月の開始日2023/06/01：終了日2023/06/30
7月の開始日2023/07/01：終了日2023/07/31
8月の開始日2023/08/01：終了日2023/08/31
9月の開始日2023/09/01：終了日2023/09/30
10月の開始日2023/10/01：終了日2023/10/31
11月の開始日2023/11/01：終了日2023/11/30
12月の開始日2023/12/01：終了日2023/12/31
```
### アンパックとの組み合わせ
上記の関数を少し改良して以下のようにアンパックして、使うこともできる
```python
def get_first_and_end_day_of_month():
    date_index_first = pd.date_range(start_date, periods=month_count, freq="MS")
    date_first = date_index_first.to_series().dt.strftime("%Y/%m/%d")
    date_index_end = pd.date_range(start_date, periods=month_count, freq="M")
    date_end = date_index_end.to_series().dt.strftime("%Y/%m/%d")
    return date_first, date_end

month_list = [x for x in range(1,13)]
# アンパック
m1, m2 = get_first_and_end_day_of_month()
for m, v1, v2 in zip(month_list, m1, m2):
    s = f'{m}月の開始日{v1}：終了日{v2}'
    print(s)
```
Zipの中で直接入れるとエラーになる（以下に示す）
```python
for m, v1, v2 in zip(month_list, get_first_and_end_day_of_month()):
    s = f'{m}月の開始日{v1}：終了日{v2}'
    print(s)
```

```terminal
Traceback (most recent call last):
  File "D:\python_pgm\test_gs.py", line 78, in <module>
    for m, v1, v2 in zip(month_list, get_first_and_end_day_of_month()):
ValueError: not enough values to unpack (expected 3, got 2)
```
上記のエラーメッセージに従い、以下のように修正するとエラーは解消できるが、for文の中で取り出す値が意図したループでなくなってしまうので、注意が必要
```python
for m, v in zip(month_list, get_first_and_end_day_of_month()):
    s = f'{m}月：アンパック：{v}'
    print(s)
```
出力結果
```terminal
1月：アンパック：2023-01-01    2023/01/01
2023-02-01    2023/02/01
2023-03-01    2023/03/01
2023-04-01    2023/04/01
2023-05-01    2023/05/01
2023-06-01    2023/06/01
2023-07-01    2023/07/01
2023-08-01    2023/08/01
2023-09-01    2023/09/01
2023-10-01    2023/10/01
2023-11-01    2023/11/01
2023-12-01    2023/12/01
Freq: MS, dtype: object
2月：アンパック：2023-01-31    2023/01/31
2023-02-28    2023/02/28
2023-03-31    2023/03/31
2023-04-30    2023/04/30
2023-05-31    2023/05/31
2023-06-30    2023/06/30
2023-07-31    2023/07/31
2023-08-31    2023/08/31
2023-09-30    2023/09/30
2023-10-31    2023/10/31
2023-11-30    2023/11/30
2023-12-31    2023/12/31
Freq: M, dtype: object
```

### 結論
zipとアンパックの組み合わせで使う場合は、ちゃんとした手順を踏まないと意図しない結果になる場合があるため、一つずつ、結果を確認しながら実装する必要がある