Python KeyError例外とそれらの処理方法

Python KeyError例外とそれらの処理方法

PythonのKeyError例外は、初心者が遭遇する一般的な例外です。 KeyErrorを上げることができる理由と、それがプログラムを停止しないようにするためのいくつかの解決策を知ることは、Pythonプログラマーとして向上するための重要なステップです。

このチュートリアルを終了すると、次のことがわかります。

  • PythonKeyErrorの通常の意味

  • 他の場所では、標準ライブラリのKeyErrorが表示される可能性があります

  • あなたがそれを見たときにKeyErrorを処理する方法

Free Bonus:Click here to get our free Python Cheat Sheetは、データ型、辞書、リスト、Python関数の操作など、Python3の基本を示しています。

PythonKeyErrorの通常の意味

PythonKeyErrorexceptionは、dictionarydict)にないキーにアクセスしようとしたときに発生するものです。

Pythonのofficial documentationは、マッピングキーにアクセスしたときにKeyErrorが発生し、マッピングに見つからないことを示しています。 マッピングは、1つの値セットを別の値セットにマップするデータ構造です。 Pythonで最も一般的なマッピングは辞書です。

PythonKeyErrorLookupError例外の一種であり、探していたキーの取得に問題があったことを示しています。 KeyErrorが表示されている場合、意味的な意味は、探しているキーが見つからなかったことです。

以下の例では、3人の年齢で定義された辞書(ages)を見ることができます。 辞書にないキーにアクセスしようとすると、KeyErrorが発生します。

>>>

>>> ages = {'Jim': 30, 'Pam': 28, 'Kevin': 33}
>>> ages['Michael']
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 'Michael'

ここで、agesディクショナリのキー'Michael'にアクセスしようとすると、KeyErrorが発生します。 トレースバックの下部で、関連情報を取得します。

  • KeyErrorが発生したという事実

  • 見つからなかったキー、つまり'Michael'

最後から2番目の行は、どの行で例外が発生したかを示しています。 この情報は、ファイルからPythonコードを実行する場合に役立ちます。

Note: Pythonで例外が発生すると、tracebackで例外が発生します。 トレースバックを使用すると、すべての関連情報が得られ、例外が発生した理由とその原因を特定できます。

Pythonトレースバックの読み方を学び、それがあなたに何を伝えているかを理解することは、Pythonプログラマーとして改善するために重要です。

以下のプログラムでは、agesディクショナリが再度定義されているのを確認できます。 今回は、次の年齢を取得する人の名前を入力するよう求められます。

 1 # ages.py
 2
 3 ages = {'Jim': 30, 'Pam': 28, 'Kevin': 33}
 4 person = input('Get age for: ')
 5 print(f'{person} is {ages[person]} years old.')

このコードは、プロンプトで指定した名前を使用して、その人の年齢を取得しようとします。 プロンプトで入力したものはすべて、4行目のages辞書のキーとして使用されます。

上記の失敗した例を繰り返すと、別のトレースバックが得られます。今回は、KeyErrorが発生したファイル内の行に関する情報が含まれています。

$ python ages.py
Get age for: Michael
Traceback (most recent call last):
File "ages.py", line 4, in 
  print(f'{person} is {ages[person]} years old.')
KeyError: 'Michael'

辞書にないキーを指定すると、プログラムは失敗します。 ここで、トレースバックの最後の数行は問題を示しています。 File "ages.py", line 4, in <module>は、どのファイルのどの行でKeyError例外が発生したかを示します。 次に、その行が表示されます。 最後に、KeyError例外は、欠落しているキーを提供します。

したがって、KeyErrorトレースバックの最後の行では、それだけでは十分な情報が得られないことがわかりますが、その前の行では、何が悪かったのかをより深く理解することができます。

Note:上記の例と同様に、このチュートリアルの他の例のほとんどは、Python 3.6で導入されたf-stringsを利用しています。

他の場所で標準ライブラリにPythonKeyErrorが表示される可能性があります

ほとんどの場合、キーがディクショナリまたはディクショナリサブクラス(os.environなど)に見つからないため、PythonKeyErrorが発生します。

まれに、アイテムがZIPアーカイブに見つからない場合、zipfileモジュールなど、Pythonの標準ライブラリの他の場所で発生することがあります。 ただし、これらの場所は、要求されたキーを検出しないPythonKeyErrorと同じ意味を保持します。

次の例では、zipfile.ZipFileクラスを使用して、.getinfo()を使用してZIPアーカイブに関する情報を抽出していることがわかります。

>>>

>>> from zipfile import ZipFile
>>> zip_file = ZipFile('the_zip_file.zip')
>>> zip_file.getinfo('something')
Traceback (most recent call last):
File "", line 1, in 
File "/path/to/python/installation/zipfile.py", line 1304, in getinfo
  'There is no item named %r in the archive' % name)
KeyError: "There is no item named 'something' in the archive"

これは実際には辞書キー検索のようには見えません。 代わりに、例外を発生させるのはzipfile.ZipFile.getinfo()の呼び出しです。

トレースバックも少し異なって見えますが、欠落しているキーKeyError: "There is no item named 'something' in the archive"よりも少し多くの情報が提供されています。

ここで最後に注意することは、KeyErrorを上げた行がコードにないことです。 これはzipfileコードにありますが、トレースバックの前の行は、コードのどの行が問題を引き起こしたかを示しています。

独自のコードでPythonKeyErrorを発生させる必要がある場合

you to raiseの場合、独自のコードでPythonKeyError例外が意味をなす場合があります。 これは、raiseキーワードを使用し、KeyError例外を呼び出すことで実行できます。

raise KeyError(message)

通常、messageは欠落しているキーになります。 ただし、zipfileパッケージの場合と同様に、次の開発者が何が悪かったのかをよりよく理解できるように、もう少し情報を提供することを選択できます。

独自のコードでPythonKeyErrorを発生させることにした場合は、ユースケースが例外の背後にあるセマンティックな意味と一致することを確認してください。 探しているキーが見つからなかったことを示す必要があります。

あなたがそれを見たときにPythonKeyErrorを処理する方法

KeyErrorに遭遇した場合、それを処理するためのいくつかの標準的な方法があります。 ユースケースによっては、これらのソリューションのいくつかは他のソリューションよりも優れている場合があります。 最終的な目標は、予期しないKeyError例外が発生しないようにすることです。

通常の解決策:.get()

KeyErrorが、独自のコードで失敗したディクショナリキールックアップから生成された場合、.get()を使用して、指定されたキーで見つかった値またはデフォルト値を返すことができます。

前の年齢検索の例と同様に、次の例は、プロンプトで提供されるキーを使用して辞書から年齢を取得するより良い方法を示しています。

 1 # ages.py
 2
 3 ages = {'Jim': 30, 'Pam': 28, 'Kevin': 33}
 4 person = input('Get age for: ')
 5 age = ages.get(person)
 6
 7 if age:
 8     print(f'{person} is {age} years old.')
 9 else:
10     print(f"{person}'s age is unknown.")

ここで、5行目は、.get()を使用してagesから年齢値を取得する方法を示しています。 これにより、age変数は、提供されたキーのディクショナリで見つかった経過時間値、またはデフォルト値(この場合はNone)になります。

今回は、キーに直接アクセスしようとするのではなく、より安全な.get()メソッドを使用して経過時間を取得するため、KeyError例外が発生することはありません。

$ python ages.py
Get age for: Michael
Michael's age is unknown.

上記の実行例では、不正なキーが提供されたときにKeyErrorが発生しなくなりました。 キー'Michael'は辞書にありませんが、.get()を使用すると、発生したKeyErrorではなくNoneが返されます。

age変数には、辞書にある人の年齢またはデフォルト値(デフォルトではNone)のいずれかが含まれます。 2番目の引数を渡すことにより、.get()呼び出しで別のデフォルト値を指定することもできます。

これは上記の例の5行目で、.get()を使用して異なるデフォルトの経過時間を指定しています。

age = ages.get(person, 0)

ここでは、'Michael'Noneを返す代わりに、キーが見つからないため0を返し、返されるデフォルト値は0になりました。

まれな解決策:キーの確認

辞書内のキーの存在を判断する必要がある場合があります。 このような場合、.get()を使用しても、正しい情報が得られない可能性があります。 .get()の呼び出しからNoneが返されるということは、キーが見つからなかったか、ディクショナリのキーで見つかった値が実際にはNoneであることを意味している可能性があります。

辞書または辞書のようなオブジェクトでは、in演算子を使用して、キーがマッピングに含まれているかどうかを判別できます。 この演算子は、キーが辞書にあるかどうかを示すブール値(TrueまたはFalse)を返します。

この例では、APIの呼び出しからresponseディクショナリを取得しています。 この応答には、応答で定義されたerrorキー値が含まれている可能性があります。これは、応答がエラー状態にあることを示します。

 1 # parse_api_response.py
 2 ...
 3 # Assuming you got a `response` from calling an API that might
 4 # have an error key in the `response` if something went wrong
 5
 6 if 'error' in response:
 7     ...  # Parse the error state
 8 else:
 9     ...  # Parse the success state

ここでは、errorキーがresponseに存在するかどうかを確認することと、キーからデフォルト値を取得することには違いがあります。 これは、実際に探しているのはキーが辞書にあるかどうかであり、そのキーの値ではないというまれなケースです。

一般的な解決策:tryexcept

他の例外と同様に、いつでもtryexceptブロックを使用して、潜在的な例外発生コードを分離し、バックアップソリューションを提供できます。

以前と同様の例でtryexceptブロックを使用できますが、今回は、通常の場合にKeyErrorが発生した場合に出力されるデフォルトのメッセージを提供します。

 1 # ages.py
 2
 3 ages = {'Jim': 30, 'Pam': 28, 'Kevin': 33}
 4 person = input('Get age for: ')
 5
 6 try:
 7     print(f'{person} is {ages[person]} years old.')
 8 except KeyError:
 9     print(f"{person}'s age is unknown.")

ここでは、人の名前と年齢を印刷するtryブロックで通常のケースを確認できます。 バックアップケースはexceptブロックにあり、通常のケースでKeyErrorが発生した場合、バックアップケースは別のメッセージを出力します。

tryexceptブロックソリューションは、.get()またはin演算子をサポートしていない可能性のある他の場所にも最適なソリューションです。 KeyErrorが他の人のコードから取得されている場合にも、これは最良の解決策です。

これは、zipfileパッケージを再度使用した例です。 今回は、tryexceptブロックにより、KeyError例外によるプログラムの停止を停止する方法が提供されます。

>>>

>>> from zipfile import ZipFile
>>> zip = ZipFile('the_zip_file.zip')
>>> try:
...     zip.getinfo('something')
... except KeyError:
...     print('Can not find "something"')
...
Can not find "something"

辞書のようにZipFileクラスは.get()を提供しないため、tryexceptソリューションを使用する必要があります。 この例では、.getinfo()に渡すのに有効な値を事前に知る必要はありません。

結論

これで、PythonのKeyError例外が発生する可能性のあるいくつかの一般的な場所と、それらがプログラムを停止しないようにするために使用できるいくつかの優れたソリューションがわかりました。

さて、次にKeyErrorが発生したのを見ると、それはおそらく辞書キーのルックアップが悪いだけであることがわかります。 また、トレースバックの最後の数行を調べることで、エラーの原因を特定するために必要なすべての情報を見つけることができます。

問題が独自のコードでの辞書キー検索である場合は、辞書でキーに直接アクセスすることから、デフォルトの戻り値でより安全な.get()メソッドを使用するように切り替えることができます。 問題が独自のコードに起因するものではない場合は、tryexceptブロックを使用することが、コードのフローを制御するための最善の策です。

例外は怖い必要はありません。 トレースバックで提供された情報と例外の根本原因を理解する方法がわかったら、これらのソリューションを使用して、プログラムの流れをより予測可能にすることができます。