カスタム例外

Pythonでは、例外はプログラムの実行中に発生し得るエラーに対処する方法です。リストに存在しないインデックスにアクセスしようとするとIndexErrorが発生したり、サポートされていない型で操作を試みるとTypeErrorが発生したりするなど、多くの組み込み例外があります。しかし、独自の例外を定義すると便利な場合があります。これにより、意味のあるエラーメッセージを提供し、組み込みの例外では対処できない特定の状況に対応できます。カスタム例外は、組み込みのExceptionクラスまたはそのサブクラスを継承する新しいクラスを定義することで作成します。
仮想の図書館システムで動作するプログラムを考えてみましょう。このシステムでは、ユーザーが一度に5冊以上の本を借りることは許可されていません。これに対して、次のようにカスタム例外を作成できます:
class TooManyBooksError(Exception):  # 新しい例外クラスを定義
    pass

def checkout_books(user, num_books):
    if num_books > 5:  
        # ユーザーが5冊以上の本を借りようとした場合、例外を発生させる
        raise TooManyBooksError('You cannot check out more than 5 books at a time.')  # カスタム例外を発生
    # それ以外の場合は、本をユーザーのアカウントに追加
    user.books += num_books
上記の例では、ユーザーが一度に5冊以上の本を借りようとすると、特定のエラーメッセージとともにカスタム例外TooManyBooksErrorが発生します。例外が捕捉されない場合、プログラムは実行を停止し、エラーメッセージがコンソールに表示されます。
この例外は、組み込みの例外と同様にtry / exceptブロックを使用して捕捉できます:
try:
    checkout_books(user, 7)     # 7冊の本を借りようと試みる
except TooManyBooksError as e:  # カスタム例外を捕捉
    print(e)                    # エラーメッセージを表示
もしcheckout_books(user, 7)の行でTooManyBooksErrorが発生した場合、プログラムはそれを捕捉し、エラーメッセージ「You cannot check out more than 5 books at a time.」を表示します。こうすることで、エラーを適切に処理し、プログラムを継続して実行できます。
他のクラスと同様に、例外クラスにメソッドを追加することもできます。例えば、ユーザーが希望する冊数を借りるために返却する必要のある本の数を計算するメソッドを追加してみましょう:
class TooManyBooksError(Exception):  
    def __init__(self, attempted, limit=5, checked_out=0):  
        self.attempted = attempted
        self.limit = limit
        self.checked_out = checked_out
        self.message = f'You tried to check out {self.attempted} books, but the limit is {self.limit} books.'
        super().__init__(self.message)
        
    def books_to_return(self):
        return self.checked_out + self.attempted - self.limit
例外を捕捉した後、このメソッドを使用できます:
try:
    checkout_books(user, 7)
except TooManyBooksError as e:
    print(e)                                                   # あなたは7冊の本を借りようとしましたが、制限は5冊です。
    print(f'You need to return {e.books_to_return()} books.')  # あなたは2冊の本を返却する必要があります。
このように、カスタム例外は独自の属性やメソッドを持つことができ、プログラムの特定のニーズに合わせてエラーを管理する強力なツールとなります。
 

カスタム例外を定義する際のベストプラクティス

カスタム例外を作成する際、Exceptionを継承した空のクラスを定義し、例外を発生させるたびにエラーメッセージ全体を文字列として渡したくなるかもしれません。この方法でも動作しますが、コードの繰り返しが増え、例外メッセージの一貫性を保つことが難しくなります。
代わりに、動的なデータを例外クラス内に格納するのが良い方法です。こうすることで、例外メッセージを内部で構築でき、コードをよりクリーンで整理されたものにできます:
👎 悪い実践 – 毎回メッセージ全体を渡す方法:
class TooManyBooksError(Exception):
    pass

# 毎回メッセージを渡す必要がある
raise TooManyBooksError('You tried to check out 7 books, but the limit is 5!')

# 長期的にはこれは良くないアイデア
👍 より良いアプローチ – 動的な詳細を例外クラスに渡す方法:
class TooManyBooksError(Exception):
    def __init__(self, attempted, limit=5):
        self.attempted = attempted
        self.limit = limit
        # データからエラーメッセージを構築
        self.message = f'You tried to check out {self.attempted} books, but the limit is {self.limit}.'
        super().__init__(self.message)

# 例外を発生させるとき、関連するデータだけを渡す
raise TooManyBooksError(7)
💡
例外クラスにエラーメッセージの作成を任せることで、繰り返しを避けられます。メッセージの文言や書式を変更する必要があっても、例外の__init__メソッド内の一箇所だけを修正すれば済みます。
 

チャレンジ: 年齢データベース

誰もが互いに知り合っているある都市で、地元政府はバーやクラブ、劇場などの市内施設で年齢確認システムを導入しようとしています。彼らは、人々の年齢を格納した辞書を受け取り、名前を入力するとその人の年齢を返すプログラムを必要としています。
入力は自動的に処理されます。あなたは、名前を与えるとその人の年齢を返す関数find_age(name)を実装してください。
しかし、すべての人が市のデータベースに登録されているとは限りません。もしプログラムがデータベースで名前を見つけられない場合、NameNotFoundErrorというカスタム例外を発生させ、適切なエラーメッセージ {name} not found in the database を出力する必要があります。このメッセージはNameNotFoundErrorクラス内で作成してください。
入力
出力
5 Alice - 24 Bob - 32 Charlie - 21 Daisy - 27 Edgar - 30 Edgar
Edgar - 30
5 Alice - 24 Bob - 32 Charlie - 21 Daisy - 27 Edgar - 30 George
NameNotFoundError: George not found in the database.
注意: 名前と年齢は大文字小文字を区別し、スペースを含む場合があります。入力された名前はデータベース内の名前と完全に一致している必要があります。
 

Constraints

Time limit: 2 seconds

Memory limit: 512 MB

Output limit: 1 MB

To check your solution you need to sign in
Sign in to continue