Pythonの例外処理を1から解説

# はじめに

Pythonには例外処理の書き方がいくつかあります。

この記事では、例外処理の基本となるtry-except構文、ファイル操作などでお馴染みのwith文を紹介し、最後にコンテキストマネージャについて解説します。

:::note info
Pythonコード多め・文章少なめです。
:::

:::note info
例外処理について初歩の初歩から始まり、Python独自の書き方まで解説します。
:::


# 例外とは

IndexErrorやValueErrorなど、コードを動かすとエラーを起こすことがあります。これを **例外** と呼びます。

```Python
lst = [2, 4, 6, 8]
element = lst[4]  # IndexError (リストの範囲外を見に行っているため)
```
```Python
word = "Python"
value = int(word)  # ValueError (アルファベットをint型にしようとしたため)
```

# try-except構文

## try節, except節

例外が出るかもしれないとき、例外を出して急に終了しないようにしたいです。

try-exceptをつかえば、例外が出たときの対応も書けます。


```Python
import logging


lst = [2, 4, 6, 8]
idx = 4

try:
    # 例外が出るかもしれない処理
    element = lst[idx]
    print(f"lstの{idx}番目の値は{element}です。")
except IndexErroe:
    # IndexErrorが出たときの処理
    logging.error({len(lst)=}, but {idx=}")
```

## finally節

finally節を入れることもできます。この節では最後に必ず行う処理を書きます。

ファイルを開いたりデータベース接続をしたりした後、そのリソースを閉じるときによく使われます。

```Python
file_path = "test.txt"
file = None

try:
    file = open(file_path, 'r')  # ファイルを開く
    content = file.read()  # ファイルを読み込む
    print(content)
except FileNotFoundError:
    print(f"Error: {file_path} not found.")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
finally:
    # 最後に開いたファイルを閉じる
    if file is not None:
        file.close()
```

## else節

最後にelse節を入れることもできます。この節では、try節が正常終了したときの処理を書きます。

else節をうまく使うと、可読性が向上します。

```Python
file_path = "test.txt"
file = None

try:
    file = open(file_path, 'r')  # ファイルを開く
except FileNotFoundError:
    print(f"Error: {file_path} not found.")
except Exception as e:
    print(f"An unexpected error occurred: {e}")
else:
    content = file.read()  # ファイルを読み込む
    print(content)
    file.close()
finally:
    print("called finally.")
```


# with文

今度はデータベース接続をしたときのことを考えてみます。

```Python
import sqlite3

conn = None
try:
    conn = sqlite3.connect('example.db')  # データベースに接続
    cursor = conn.cursor()
except sqlite3.Error as e:
    print(f"An error occurred: {e}")
else:
    # ここに100行以上の処理が入る (今回は省略)
finally:
    if conn is not None:
        conn.close()
```

else節にとても長いコードが入ることもあります。そんな中、本当にfinally節でちゃんと接続を閉じられるでしょうか？

そのようなことを防ぎコードを簡潔にしたのが with文です。

```Python
import sqlite3

with sqlite3.connect('example.db') as conn:
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM users')
    rows = cursor.fetchall()
    for row in rows:
        print(row)
```

`conn.close()` のようにデータベース接続を閉じなくても、with節を抜けたら自動で閉じてくれます。

ファイルを開くときにもよく使われます。

```Python
try:
    with open(file_path, 'r') as file:
        content = file.read()
        print(content)
except FileNotFoundError:
    print("File not found.")
```

:::note info
with文を使うと、try, finallyの処理を簡潔に書けます。
:::

# コンテキストマネージャ

`open`関数のように、with文に入れられるものを **コンテキストマネージャ** と呼びます。

自作することもできます。

## 自作クラス

with節に入るときの処理は `def __enter__()`, with節から出るときの処理は `def __exit__()` に書きます。

```Python: main.py
class MyContextManager:
    def __enter__(self):
        print("Entering context")
        return "MyContextManager returned"

    def __exit__(self, exc_type, exc_value, exc_traceback):
        print("Exiting context")


with MyContextManager() as cm:
    print("with", cm)
```

```bash: 出力結果
Entering context
with MyContextManager returned
Exiting context
```

## contextmanagerデコレータ

クラスよりも簡潔に自作できるのがcontextmanagerデコレータです。

```Python
from contextlib import contextmanager

@contextmanager
def my_context_manager():
    print("Entering context")
    yield "MyContextManager returned"
    print("Exiting context")

with my_context_manager() as cm:
    print("with", cm)
```
```bash: 出力結果
Entering context
with MyContextManager returned
Exiting context
```

with節に入るときに、`yield` よりも前に書いた処理が呼ばれます。
cmの値は `yield` で返す値です。
with節から出るときに、`yield` よりも後に書いた処理が呼ばれます。

# おわりに

ファイル操作やデータベース接続など、例外処理を使う場面は多いです。
これらの技術を適切に活用することで、コードの安全性と可読性が向上します。


# 参考文献

[O'REILLY - Effective Python](https://www.oreilly.co.jp/books/9784873119175/)