Lesson 6

エラーと例外

Lesson 6 Chapter 1
SyntaxError

プログラムを実行するうえで、エラーの発生を切り離すことはできません。良いプログラムを作り上げるには、エラーと上手に付き合う必要があります。
このLessonでは、エラーの扱い方について解説します。エラーには「エラー」と「例外」の2つが定義されていることを前提にこれからのChapterは解説していきますので、この2つの違いについてしっかりと理解をしてください。
まずは「エラー」についてですが、エラーは構文エラーがその範囲であると覚えてください。例えば、print関数を使用して文字出力する際は、ダブルクォーテーションか、シングルクォーテーションで囲む決まりがありますが、「print('あ'')」のようにシングルクォーテーションが一つ多い場合は、print関数に与える引数の構文としては誤りです。その他にも、第二引数までしか指定できない関数に、第三引数を指定してしまったなどといった場合も構文エラーです。
では、「例外」とは何でしょうか。例外とは、構文誤りがなく、何ら問題のないプログラムを作成したとしても、実行中にエラーが発生する可能はあります。それは、メモリ不足やNW障害でサーバにアクセスできないなど、様々なエラー要因が考えられます。足し算を実行する計算処理をきちんとプログラムしたとしても、数値以外の文字列を入力されてしまったら(例:「1+あ」は計算不可)エラーになります。
このように、構文が正しくても発生するエラーが例外であると覚えてください。
「エラー」と「例外」、この2つがどのような意味も持つのか、それぞれの定義を混同させないようChapterを進めていきましょう。

SyntaxErrorとは

「SyntaxError」とは、エラーに該当します。つまり、構文エラーが発生した際に出力されるエラーになります。SyntaxErrorが発生している状態ではプログラムを実行することはできません。エラーの内容から誤っている箇所を探り当て、修正する必要があります。SyntaxErrorが出力される場合は、どの箇所が誤りかそれが分かる内容も出力されます。

SyntaxErrorの確認方法

SyntaxErrorの出力内容をもとに、その確認方法を解説していきます。

引用符不整合

引用符や括弧があえて不整合となるprint関数の記述をしてその出力内容を確認します。

Error_test.py
print('Hello World'')

出力結果

print('Hello World'')
                    ^
SyntaxError: unterminated string literal (detected at line 1)

SyntaxErrorの後のコロン(:)以降に、エラーの具体的な内容が出力されます。今回の場合は「unterminated string literal」が出力されています。英語での出力となりますが、Google翻訳などを利用することで、大まかな意味は捉えることが可能です。
今回のunterminated string literalをGoogle翻訳で訳すと、「未終了の文字列リテラル」となります。文字列リテラルの記述に何かしらの誤りがあると予測できます。
SyntaxErrorの出力箇所よりも上段には、「'Hello World''」の最後のシングルクォーテーションの下にハット(^)マークが付いています。ハット(^)マークが付いている箇所が構文誤りであることを意味しています。

コロン(:)の付け忘れ

for文の処理において、コロン(:)を付け忘れた場合も構文エラーである、SyntaxErrorが出力されます。以下のコードを記述して実行しましょう。

Error_test1.py
for x in range(10)
    print(x)

出力結果

  File "Error_test.py", line 1
    for x in range(10)
                      ^
SyntaxError: expected ':'

エラーの具体的な内容には、「expected ':'」と出力されました。これは、「期待される」と訳せます。つまり、「コロン(:)が期待される」といった内容であると判断できるため、どこにコロンが期待されるのかを先程同様、SyntaxErrorより上段の出力内容で確認します。出力内容には、for文の最後にハットマークが付いています。このことから、エラーの原因は、for文の最後にコロンの記述が不足してることであると判断できます。
出力結果の最初の行には「File "Error_test.py"」と出力があり、どのファイルで発生したのかを確認可能であり、「line 1」によって1行目で例外が検知されたと解析ができます。

このように、SyntaxErrorは構文エラーであることを理解できていれば、どの箇所が誤っているのかをエラーの出力結果から導き出しやすくなり、解析もスムーズに行うことができるようになります。

Lesson 6 Chapter 2
ZeroDivisionError

このChapterからは、例外について解説します。例外は、予期せぬエラーであり、プログラムの実行中に検知されるエラーとなります。
Chapter2では、「ZeroDivisionError」について解説します。構文エラーとの違いを念頭に置いて理解を深めてください。

ZeroDivisionErrorとは

ZeroDivisionErrorとは、ゼロ除算を実行した際に発生するエラーです。ゼロ除算は、ある数値を0で割る演算を意味します。ゼロで割ることは数学的には成立しない演算であるため、コンピュータにおいても扱えない処理となります。電卓でゼロ除算を計算しても、エラーになるはずです。
Pythonでは、演算子"/"を使用した割り算や、演算子"%"を使用した割り算の剰余を実行する際に、0で割ってしまうとZeroDivisionErrorが発生します。

ZeroDivisionErrorの確認方法

実際にZeroDivisionErrorを発生させ、その出力内容を確認しましょう。
以下のコードを記述して、実行してみましょう。

Error_test.py
x = 1
y = 0

z = x/y

print(z)

出力結果

ZeroDivisionError: division by zero

出力結果に「ZeroDivisionError」と出力されました。このエラーが出力された場合は、プログラムの演算処理内でゼロ除算を行ってしまったことが確定となります。
割り算を実行するプログラムを作成する際は、ZeroDivisionErrorが発生する可能性があるということを覚えておいてください。

Lesson 6 Chapter 3
NameError

このChapterでは、例外の一つである「NameError」について解説します。NameErrorが発生した際の解析方法までを含めて、しっかり理解してください。

NameErrorとは

NameErrorは、未定義の変数や関数を指定した際に発生する例外です。例えば、変数名を「sampleArgument」と定義したにもかかわらず、「sampleArg」として使用してしまったといった場合に発生する例外です。

NameErrorの確認方法

実際にどのような場合にNameErrorが発生するのか、サンプルコードを実行して確認すると同時に、その出力内容の確認方法も合わせて解説します。

未定義の変数指定

定義した変数名と、それを使用する際の変数名に違いが生じた場合の実行結果を確認してみましょう。
以下のコードは、関数testFunctionの引数に変数sampleArgumentを指定するプログラムですが、あえてその変数名を「sampleArg」と誤った変数名で指定しています。
実際に実行して、結果を確認しましょう。

Error_test.py
def testFunction(x):
    print(x)

sampleArgument = 'Hello'
testFunction(sampleArg)

出力結果

  File "Error_test.py", line 5, in <module>
    testFunction(sampleArg)
NameError: name 'sampleArg' is not defined

「NameError」が出力されていることが確認できます。NameErrorの後のコロン(:)以降に、エラーの具体的な内容が出力されます。今回は、「name 'sampleArg' is not defined」と出力されています。これを直訳すると「名前 'sampleArg' が定義されていません」となり、変数sampleArgの指定が誤っていると判断できます。
NameErrorの出力箇所よりも上段の「File "Error_test.py"」の出力結果により、どのファイルで発生したのかを確認可能であり、「line 5」によって5行目で例外が検知されたと解析ができます。

未定義の関数指定

未定義の関数を指定した場合も、NameErrorが出力されます。以下のコードは、関数呼出時の指定であえてその関数名を「testFunc」と誤った指定にしています。
実際に実行し、結果を確認しましょう。

Error_test2.py
def testFunction(x):
    print(x)

sampleArgument = 'Hello'
sampleFunction(sampleArg)

出力結果

  File "Error_test2.py", line 5, in <module>
    testFunc(sampleArgument)
NameError: name 'testFunc' is not defined

出力結果の確認方法は、変数の時と同様です。

Lesson 6 Chapter 4
TypeError

このChapterで解説する「TypeError」は例外の一つです。

TypeErrorとは

TypeErrorは、演算や関数にて、異なるデータ型で処理を実行した場合に発生する例外です。対象処理においてデータの型が誤っていることを意味します。

TypeErrorの確認方法

実際にどのような場合にNameErrorが発生するのか、サンプルコードを実行して確認すると同時に、その出力内容の確認方法も合わせて解説します。
以下は簡単な加算のプログラムですが、変数yには、シングルクォーテーションで囲んだ文字列の"2"が設定されています。
コードを記述し実行してみましょう。

Error_test1.py
x  = 1
y = '2'

z = x + y

print(z)

出力結果

  File "Error_test.py", line 4, in <module>
    z = x + y
TypeError: unsupported operand type(s) for +: 'int' and 'str'

「TypeError」が出力されていることが確認できます。TypeErrorの後のコロン(:)以降に、エラーの具体的な内容が出力されます。今回は、「unsupported operand」と出力されています。これを直訳すると「サポートされていないオペランド」と訳せます。オペランドとは、式で使用する数値を意味します。つまり、サポートされていない数値が原因で例外が発生したことになります。
unsupported operand以降には、「for +: 'int' and 'str'」の出力が確認できるはずです。これはデータ型を意味しているので、読み替えると「for +: 数値 and 文字列」になり、式で使用された数値と文字列の+(加算)はサポートされていないことが例外の原因であると判断出来ます。
TypeErrorの出力箇所よりも上段の「File "Error_test.py"」の出力結果により、どのファイルで発生したのかを確認可能であり、「line 4」によって4行目で例外が検知されたと解析ができます。

Lesson 6 Chapter 5
try-except文

これまで、エラーと例外がどのような状況で発生するのか、また、発生した際の確認方法について解説してきました。構文エラーについては、エラーを解消してプログラムを完成させることは可能ですが、例外に関しては、実行状況次第でその発生を事前に防ぎ切ることは難しい場合もあります。だからといって、例外が発生する都度、プログラムの処理が中断してしまっては意味がありません。例外が発生した際に処理が中断しないよう、例外の発生に対する備えが必要です。
このChapterでは、 例外が発生した際の扱い方について解説していきます。

try-except文とは

try-except文は、例外が発生した際に、その例外をキャッチすることを可能にします。例えば、通常、ZeroDivisionErrorが発生した場合は処理が中断しますが、try-except文を用いることで、ZeroDivisionErrorが発生した場合は、処理を中断させる代わりにわかりやすいエラーメッセージを画面に出力させることが可能になります。
try-except文を用いることで、例外発生時でも処理を中断させることなく、最後までプログラムが実行されます。

try-except文の使い方

try-except文を使って例外をキャッチし、処理の中断を防ぐコードを記述します。
まずは、ゼロ除算によるZeroDivisionErrorが発生する処理を記述してその動作を確認しましょう。

Error_test.py
x = 1
y = 0
msg = '完了'
z = x/y

print(z)
print(msg)

出力結果

   File "Error_test.py", line 4, in <module>
    z = x/y
ZeroDivisionError: division by zero

ZeroDivisionErrorが発生した影響で、変数msgで定義した文字列"完了"を出力するprint関数が実行されることなく、処理が中断しました。
次は、try-except文を使用して、例外をキャッチしてみましょう。
以下のコードは、ゼロ除算が発生する可能性のある割り算処理を、try〜exceptの間に記述しています。実行して結果を確認しましょう。

Error_test.py
x = 1
y = 0
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

try:
    z = x/y
    print(z)
except:
    print(msg1)

print(msg2)

出力結果

ゼロで割ることはできません。

変数msg1・msg2で定義した文字列がどちらも出力され、処理は終了しています。
ZeroDivisionErrorの発生による中断がされることなく、最後のprint関数(変数msg2出力)の処理まで実行されています。

例外の指定方法

発生した例外のキャッチを可能とするのがtry-except文ですが、それは全ての例外をキャッチしてしまうことを意味します。
先程の例では、ZeroDivisionErrorをキャッチした際のメッセージを出力するように、try-except文を記述しましたが、例外全てをキャッチするのがtry-except文なので、ZeroDivisionError以外の例外でもtry-except文内の処理が実行されることになります。
ZeroDivisionError以外の例外をキャッチした場合の動作を、以下のコードを実行して確認してください。

Error_test1.py
x = 4
y = '2'
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

try:
    z = x/y
    print(z)
except:
    print(msg1)

print(msg2)

出力結果

ゼロで割ることはできません。

try-except文を外して、本来発生している例外を確認しましょう。

Error_test1.py
x = 4
y = '2'
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

z = x/y

print(z)
print(msg1)
print(msg2)

出力結果

 File "Error_test1.py", line 6, in <module>
    z = x/y
TypeError: unsupported operand type(s) for /: 'int' and 'str'

変数yには文字列を定義しているため、数値と文字列の計算を実行したことによる例外の「TypeError」が発生しました。
このように、try-except文内で例外が発生した場合は、ZeroDivisionError以外の例外でもキャッチしてしまうことが確認できました。
本来、ZeroDivisionErrorが発生した事を想定した処理としたいのに、これでは発生した全ての例外に対して変数msg1の「ゼロで割ることはできません。」を出力してしまいます。
そこで、必要になるのが例外の指定です。
except節の後に、キャッチしたい例外を指定します。今回は、ZeroDivisionErrorをキャッチしたいので、以下のようにコードを記述します。

Error_test1.py
x = 4
y = '2'
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

try:
    z = x/y
    print(z)
except ZeroDivisionError:
    print(msg1)

print(msg2)

出力結果

  File "Error_test1.py", line 7, in <module>
    z = x/y
TypeError: unsupported operand type(s) for /: 'int' and 'str'

「TypeError」が出力されました。つまり、TypeErrorはtry-except文ではキャッチしなかったことになります。
変数yをゼロに変えて、ZeroDivisionErrorがキャッチされるか確認しましょう。

Error_test1.py
x = 4
y = 0
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

try:
    z = x/y
    print(z)
except ZeroDivisionError:
    print(msg1)

print(msg2)

出力結果

ゼロで割ることはできません。
完了

「ゼロで割ることはできません。」が出力され、かつ「完了」のメッセージも出力されました。処理が中断されることなく全て実行されたことが確認できます。
このように、キャッチしたい例外を指定するには、except節の後に指定することで可能になります。

全ての例外キャッチについて

発生する例外を指定せずに、全ての例外をキャッチすることは推奨されていません。発生するであろう例外を想定し、それを指定して適切な処理をtry-except文内に記述するプログラムの作成が必要です。

例外の複数指定方法

except節の後に例外を指定することで、意図したとおりに例外のキャッチが可能になることを解説しましたが、例外の指定は複数可能です。
以下のコードは、変数yに文字列を定義しているので、そのまま実行すると「TypeError」が発生します。except節を追加して、TypeError発生時の処理を追加します。

Error_test2.py
x = 4
y = '2'
msg1 = 'ゼロで割ることはできません。'
msg2 = '数値以外が設定されました。'
msg3 = '完了'

try:
    z = x/y
    print(z)
except ZeroDivisionError:
    print(msg1)
except TypeError:
    print(msg2)

print(msg3)

出力結果

数値以外が設定されました。
完了

「数値以外が設定されました。」が出力され、かつ「完了」のメッセージも出力されました。TypeErrorが発生しましたが、処理が中断されることなく全て実行されています。

例外の内容も合わせて出力したい場合は、"as"を用いて発生した例外の出力内容を変数に格納し、それをprint関数で出力します。

Error_test2.py
x = 4
y = 0
msg1 = 'ゼロで割ることはできません。'
msg2 = '数値以外が設定されました。'
msg3 = '完了'

try:
    z = x/y
    print(z)
except ZeroDivisionError as exc:
    print(f'{exc}:{msg1}')
except TypeError as exc:
    print(f'{exc}:msg2')

print(msg3)

出力結果

division by zero:ゼロで割ることはできません。
完了

elseを使用した正常処理時の扱い方

例外が発生しなかった場合、つまりは正常に処理が実行された後に行う処理を、else節を用いて指定することが可能です。以下のサンプルコードを記述して実行してみましょう。

Error_test3.py
x = 4
y = 2
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'

try:
    z = x/y
    print(z)
except:
    print(msg1)
else:
    print(msg2)

出力結果

2.0
完了

例外が発生しなかった場合は、else節が実行されていることが確認できます。

常に実行すべき処理の扱い方

例外の発生有無に関わらず、必ず実行させたい処理がある場合には、finally節を用いて処理を記述します。

Error_test4.py
x = 1
y = 0
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'
msg3 = '全ての処理が完了しました。'

try:
    z = x/y
    print(z)
except:
    print(msg1)
else:
    print(msg2)
finally:
    print(msg3)

出力結果

ゼロで割ることはできません。
全ての処理が完了しました。

ZeroDivisionErrorが発生していますが、finally節で指定した変数msg3の出力が行われていることが確認できます。
次に、正常に割り算が実行されるよう、変数xとyを修正して実行してみます。

Error_test4.py
x = 4
y = 2
msg1 = 'ゼロで割ることはできません。'
msg2 = '完了'
msg3 = '全ての処理が完了しました。'

try:
    z = x/y
    print(z)
except:
    print(msg1)
else:
    print(msg2)
finally:
    print(msg3)

出力結果

2.0
完了
全ての処理が完了しました。

例外が発生しなかった場合に実行されるelse節の出力が確認できますが、それに加えてfinally節も実行されています。
このように、finally節は例外の発生有無に関わらず必ず実行されます。

Lesson 6 Chapter 6
raise文

ここまで学んできた例外処理とは、プログラム中で発生したエラーを検出し、適切な処理を行うための仕組みです。 Pythonでの具体的な例外処理には、try-except文やtry-finally文がありましたが、このレッスンでは「raise文」について学んでいきます。

raise文とは

raise文は、意図的に例外を発生させるための文です。プログラムの中で何か問題が発生した場合に、自分で例外を発生させることで処理を中断し、適切なエラー処理を行うことができます。 raise文を使うことでプログラムの実行状態を確認し、問題が発生した場所を特定することができます。それにより、プログラム実行中に発生したエラーを検出し、適切な処理を行うことができます。

raise文の基本構文

raise文は、以下のように書きます。

raise 例外クラス名("エラーメッセージ")

例外クラス名には、Pythonの標準ライブラリに用意されているクラスを使うことができます。また、自分で独自の例外クラスを定義することもできます。 raise文は、主にtry-except文の中で使用され、raise文が実行されると、特定の例外を発生させることができ、そこで処理を中断します。
次に、Pythonでよく使用される標準の例外クラスについて紹介していきます。

標準例外クラス一覧

  • SyntaxError(シンタックスエラー)
  • 文法に誤りがある場合に発生する例外です。例えば、不正な演算子の使用や不正な構文が原因で発生します。

  • IndentationError(インデンテーションエラー)
  • インデント(字下げ)に関するエラーが発生した場合に発生する例外です。例えば、インデントが必要な箇所にない場合が原因で発生します。

  • NameError(ネームエラー)
  • 変数やクラスの名前が定義されていない場合に発生する例外です。例えば、未定義の変数を参照しようとした場合や、未定義の関数を呼び出そうとした場合が原因で発生します。

  • TypeError(タイプエラー)
  • 予期した型でない場合に発生する例外です。例えば、数値と文字列を混ぜて演算を行おうとした場合や、リストのインデックスを文字型の数字で参照しようとした場合が原因で発生します。

  • ValueError(バリューエラー)
  • 予期した値でない場合に発生する例外です。例えば、整数型に変換できない文字列を渡した場合や、不正な引数を関数に渡した場合が原因で発生します。

  • AttributeError(アトリビュートエラー)
  • 属性が存在しない場合に発生する例外です。例えば、オブジェクトに存在しない属性を参照しようとした場合や、属性に対して不正な操作を行おうとした場合が原因で発生します。

  • IndexError(インデックスエラー)
  • インデックスが範囲外の場合に発生する例外です。リストやタプルのシーケンス型の要素に対して、存在しないインデックスを指定しようとした場合が原因で発生します。

  • KeyError(キーエラー)
  • キーが見つからなかった場合に発生する例外です。辞書型のマッピング型の要素に対して、存在しないキーを指定しようとした場合が原因で発生します。

  • ModuleNotFoundError(モジュールノットファウンドエラー)
  • インポートしようとしたモジュールが見つからなかった場合に発生する例外です。インポートするモジュールが存在しない場合や、インポート先のパスが正しく設定されていない事が原因で発生します。

  • ZeroDivisionError(ゼロディビジョンエラー)
  • 0で除算をしようとした場合に発生する例外です。0で割ろうとした場合や、0で割られる値を指定した場合が原因で発生します。

raise文を使用したプログラム例

以下のプログラムは、0で割ろうとした場合にZeroDivisionErrorを発生させる例です。

sample.py
def divide(a, b):
      if b == 0:
          raise ZeroDivisionError("0で割ることはできません")
      return a / b

  print(divide(10, 2)) # エラーが発生しないケース
  print(divide(10, 0)) # エラーが発生するケース

出力結果

Traceback (most recent call last):
                      File "Main.py", line 7, in <module>
      print(divide(10, 0)) # エラーが発生するケース
    File "Main.py", line 3, in divide
      raise ZeroDivisionError("0で割ることはできません")
  ZeroDivisionError: 0で割ることはできません

このように、raise文を使うことで、意図的に例外を発生させ、処理を中断することができます。また、エラーメッセージを指定することで、エラーの原因を人間に分かりやすく伝えることができます。

独自例外クラスとtry-except文との併用

以下のプログラムは独自の例外クラスを定義し、raise文を使用してそれを発生させる例です。

sample.py
class MyException(Exception):
      pass

  try:
      raise MyException("独自例外が呼び出されました。")
  except MyException as e:
      print(e)

出力結果

独自例外が呼び出されました。

上記のコードでは、MyExceptionという名前の例外クラスを定義し、raise文を使用してそれを発生させています。try-except文で、MyExceptionが発生した場合の例外処理を行っています。

raise文の注意点

raise文を使う際には、以下の点に注意しましょう。

  • raise文は基本的にtry-except文の中で使用します。raise文を使用した際にtry-except文がない場合は、raise文の箇所でプログラムが停止します。
  • エラーメッセージは、できるだけ明確であるようにしましょう。エラーメッセージを指定することで、エラーの原因を明確にすることができます。

以上が、Pythonのraise文についての説明でした。raise文を使うことで、プログラムの中で発生したエラーを検出し、適切な処理を行うことができます。 また、独自の例外クラスを定義することで、プログラム特有のエラーを定義することもできます。