【Python】エラー「IndexError: list index out of range」を出ないようにするorエラー捕捉するコード

# 概要
`IndexError: list index out of range`エラーに遭遇し、このエラーを出ないようにするor捕捉してあげることでエラーをキャッチできるコードを3つ作成したので紹介します。

# `list index out of range`とは
以下のコードを実行すると、リストの最初の要素の`fruit_name`を取得できます。

```py
def get_first_data_of_list(data, key, sub_key):
    value = ''
    if key in data:
        if sub_key in data[key][0]:
            value = data[key][0][sub_key]
    return value

information = {
    "fruit":
    [
        {"fruit_name" : "apple",
         "fruit_price" : "200" },
        {"fruit_name" : "banana",
         "fruit_price" : "100" }
    ],
}

value = get_first_data_of_list(information, "fruit", "fruit_name")
print(value) # apple
```

しかし、`information`データの中身が空で連携された場合、結果はどうなるでしょうか。

```py
def get_first_data_of_list(data, key, sub_key):
    value = ''
    if key in data:
        if sub_key in data[key][0]:
            value = data[key][0][sub_key]
    return value

information = {
    "fruit":[],
}

value = get_first_data_of_list(information, "fruit", "fruit_name")
print(value) # apple
```
結果は以下。
```
    if sub_key in data[key][0]:
IndexError: list index out of range
```

上述の通り、`list index out of range`というインデックスエラーになってしまいました。

このエラーは、リストのインデックスの範囲外、存在しない値を使用して要素にアクセスしようとすると発生するエラーです。空のリストで何も要素がないのに、`if sub_key in data[key][0]`で1番目のインデックスにアクセスしようとしているため、「範囲外のインデックス」として上述のエラーが発生しました。

もっとわかりやすい例で言うと、以下のように、「範囲外のインデックス」の場合はこのエラーが発生します。

```py
lst = [1, 2, 3]
print(lst[3])
```

## 空のリストでもエラーにならないコード１

以下のようにすると、空のリストが連携されてもエラーになりません。
これは、`data[key][0]`にアクセスする前に、`data[key]`がリストであることかつその長さが0より大きいことを条件としています。これにより、空のリストでもエラーが発生しないようにすることができます。

```py
def get_first_data_of_list(data, key, sub_key):
    value = ''
    if key in data and isinstance(data[key], list) and len(data[key]) > 0:
        if sub_key in data[key][0]:
            value = data[key][0][sub_key]
    return value

information = {
    "fruit":[],
}

value = get_first_data_of_list(information, "fruit", "fruit_name")
print(value)  # apple
```

## 空のリストでもエラーにならないコード2

以下のコードでも、空のリストでエラーになりません。

```py
def get_first_data_of_list(data):
    try:
        return data.get('fruit', [{}])[0].get('fruit_name', '')
    except IndexError:
        return ''

information = {
    "fruit":[],
}

value = get_first_data_of_list(information)
print(value)
```



このコードでは、`get`メソッドを使用して辞書からキーに対応する値を取得しています。`get`メソッドの第二引数は、キーが辞書に存在しない場合に返されるデフォルト値を指定します。
エラーについては、`try/except`ブロックを使用して例外を捕捉してあげる形にしています。


## 空のリストでもエラーにならないコード3

以下は、Pythonの組み込み関数`next`とジェネレータ式を使用して実現しているコードです。

```py

def get_first_fruit_name(data):
    fruits = data.get('fruit', [])
    return next((item.get('fruit_name', '') for item in fruits if 'fruit_name' in item), '')

information = {
    "fruit":[],
}

value = get_first_fruit_name(information)
print(value)
```

ジェネレータはあまり使ったことがないですが、大量のデータを扱う場合や大量に`for`ループを繰り返す際には、メモリを節約することができるらしいです。リスト全体をメモリにロードするのではなく、一度に1つの要素だけを生成するからですね。

上記では、`next`関数を使用して、`fruit_name`キーを持つ最初の辞書の`fruit_name`の値を取得。
`next`関数の第二引数は、ジェネレータが終了した（＝`fruit_name`キーを持つ辞書がない）場合に返されるデフォルト値を指定します。

### 参考：ジェネレータと`next`関数の参考例

2の倍数を無限に生成するジェネレータ式から、一つずつ次の要素を取り出すコード:

```py
gen = (i * 2 for i in range(100))

print(next(gen))  # 0を出力
print(next(gen))  # 2を出力
print(next(gen))  # 4を出力
```