プログラムのエラー

概要

プログラムに誤りがあると、そこでエラーが発生して停止することがあります。 たとえばif文のコロン(:)を忘れたり、インデントの深さを間違うといった文法レベルの問題から、 0を持つ変数で割り算してしまうといった間違いです。

間違いを直して正しいプログラムを作るには、 停止時のエラー内容である「スタックトレース」を呼んで問題箇所を把握し修正する必要があります。

また、プログラムの文法や使い方自体は正しいものの、 自分が意図した通りにプログラムが動いていない場合もあります。 そのような場合はプログラムの動きを追って問題箇所を探すことが必要になります。

プログラムの間違い

数行、数十行のプログラムならともかく、ある程度の大きさのプログラムを書くと必ず間違いがあります。 間違いにもいろいろあり、Pythonが「 エラー(例外) 」として実行を停止してしまうものから、 Pythonのプログラムとしては間違っていないためエラーにはならいものの、 プログラマの意図通りに動いていないものなどがあります。

本ページではそれらの間違いをどのように正すかについて扱います。

なお、「発生することが期待されるエラー」というものがプログラミングにはあります。 たとえば外部サーバーから情報を取ってくるプログラムで、その相手のサーバーがダウンしている場合のエラーなどです。 そういったトラブルは「 例外処理 」と呼ばれる手法で対応しますが、それは例外処理のページにて解説します。

エラー

プログラムに文法的な間違いがあった場合に、 Pythonは「どこが、どういう理由で間違っている」ということを、エラーとして出力します。

エラーは大まかに2つあり、「 構文解析エラー(文法エラー) 」と「 実行時エラー 」と呼ばれるものです。

Pythonはモジュールを読み込んだ段階で、そのモジュールを上から下まで読み込み処理をします。 その際に「 構文解析 」という「どのようなプログラム構造になっているかの把握」をしています。 その構文解析中に問題が発生すると構文解析エラーが発生します。 Pythonの文法のトラブルですので、文法エラーともいえます。

構文解析時に問題がなかったプログラムであっても、その実行時にエラーが発生することがあります。 実行時エラーと呼ばれるもので、例えば宣言されていない変数を参照しようとしたり、 0で割り算をした場合に発生します。

構文解析エラー

例えば以下のようなプログラムがあるとしましょう。 プログラムは「sample.py」というプログラムファイルに書かれています。

プログラム: 2行目に間違いがある

print(1)
if True
  print(2)
print(3)

このプログラムは2行目のif文に間違いがあります。 正しくは「if True:」となるべきところを、「:」を忘れています。 これはPythonの文法として誤りがあります。

このプログラムを実行すると、以下のようなエラー出力が得られます。

コンソール: 実行するとエラーが発生

$ python3 sample.py
  File "sample.py", line 2
    if True
          ^
SyntaxError: invalid syntax

エラーの出力を上から順に確認すると、以下のことがわかります。

  • sample.pyというファイルの2行目
  • 「if True」の後ろ側に問題がある
  • 「SyntaxError: invalid syntax」というエラーが発生

どこでどう間違えているか出力しているため、修正箇所はひと目でわかります。

エラー出力以外に着目してほしいのは「print(1)」が実行されていないことです。 エラー箇所は2行目にあるものの、1行目に間違いはありません。 上から順に実行されるのであれば、1行目は実行されて、実行結果に「1」と表示されそうな気がします。

これには理由があり、Pythonは「構文解析を実行前に行う」という動きをしているためです。 モジュールが実行されるのは構文解析が終わった後なので、文法に誤りがあると実行する段階まで進めません。

実行時エラー

構文解析にパスをしたら、Pythonのプログラムが実行されます。

実行される段階で発生したエラーは「実行時エラー」と呼ばれており、 そのプログラムファイルのなかのその行が実行されたタイミングでエラーが発生します。

たとえば、以下の文法的には間違いがないプログラムがあるとします。

print(1)
if True:
  a = 5 / 0
  print(2)
print(3)

このプログラムは文法的にはあっていますが、 3行目の「a = 5 / 0」が「0による割り算」なので間違っています。

これを実行すると、以下のようなエラーが発生します。

$ python3 sample.py
1
Traceback (most recent call last):
  File "sample.py", line 3, in <module>
    a = 5 / 0
ZeroDivisionError: division by zero

エラーの内容を読むと以下のことが分かります。

  • sample.pyというファイルの3行目
  • 「a = 5 / 0」に問題がある
  • 「ZeroDivisionError: division by zero」というエラーが発生

「Division」は割り算という意味なので、0による割り算エラーが発生しているということです。

先ほどの文法エラーと頃なるのは出力の1行目に「1」という出力があることです。 これはプログラムの1行目にある「print(1)」が実行されているためです。

実行時エラーはその行が実行されて始めてエラーとなるため、 その行にいたるまでの他の行は実行されています。 ただ、エラーが発生した以降の処理は出力に「print(2)」に相当する「2」がないことからも、 実行されていないことが分かります。

検知されないエラー

実行時エラーは実行されることで始めてエラーであると分かります。 逆にいえば「エラーがあっても、それが実行されないとエラーにならない」ということです。

たとえば先ほどのプログラムを少し書き換えて、以下のようなコードとします。

print(1)
if False:
  a = 5 / 0
  print(2)
print(3)

if文の条件式がFalseとなっているため、そのコードブロックは実行されません。 ただ、そのコードブロック中の処理にはエラーとなる「a = 5 / 0」があります。

このプログラムを実行すると、以下のような出力となりエラーは発生しません。

$ python3 sample3.py
1
3

実行時エラーは実行時にはじめて発生します。 そのため、条件分岐などの関係で「実行したり実行されなかったりする行の問題」は検知しづらい場合があります。

実行時にエラーとなることを防ぐためには、きちんとした「 テスト 」が必要です。 ただ、初心者は他に優先して学ぶべきことが多いので、 ある程度のレベルに達するまではエラーが発生した際に直す姿勢で問題ないと思います。 一人前のコードが書けるようになったらテストなども気にかけて下さい。

スタックトレース

グローバルレベルで書かれている処理で実行時エラーが発生した場合は「何行目でエラー」と表示されるだけです。 ただ、関数や後述するクラスのメソッドでエラーが発生した場合は「どのように呼び出されたか」という 「 スタックトレース 」とともにエラーが表示されます。

例えば以下のプログラムでは「function2」にバグがあり、 グローバルレベルからfunction1を経由してそれが呼び出されています。

def function1():
  return function2()   # line 2

def function2():
  return 5/0           # line 5

function1()            # line 7

これを実行すると、以下のようなスタックトレースが得られます。

python3 sample.py
Traceback (most recent call last):
  File "sample.py", line 7, in <module>
    function1()
  File "sample.py", line 2, in function1
    return function2()
  File "sample.py", line 5, in function2
    return 5/0
ZeroDivisionError: division by zero

最終的に「0による割り算エラー」が発生してはいるものの、 「Traceback」に続いてエラーに至るまでの呼び出しの流れが上から下に順に記載されています。

自分で作成した関数も様々な箇所から呼び出されます。 毎回エラーがでるのであれば対処も簡単ですが「X行目からの呼び出しは大丈夫」 「Y行目からの呼び出しはエラーになる」といった状況だと、 このスタックトレースを見ながら「どのような流れでエラーになったのか読み解く」という作業が必要になります。

printデバッグ

エラーの発生原因が複雑だと、問題箇所がすぐに特定できない場合があります。 そのような場合は「printデバッグ」と呼ばれる手法を使って調査ができます。

たとえば「剰余」という「割り算の余り」の計算を使う「FizzBuzz」と呼ばれる有名なプログラムを組むとします。 ある範囲の数字(ここでは1から10とする)を2で割り切れるときはFizzと出力し、3で割り切れる時はBuzzと出力します。 そして2でも3でも割り切れる場合はFizzBuzzと出力します。

剰余の計算は「%」記号を使います。 2で割り切れるということは、「整数 % 2」が0ということで、 3で割り切れるということは「整数 % 3」が0ということです。

>>> 5 % 2
1
>>> 4 % 2
0
>>> 5 % 3
2
>>> 3 % 3
0

このfizzbuzzの間違いがあるコードを以下に示します。

a = [1,2,3,4,5,6,7,8,9,10]

for i in a:
  if i % 2 == 0:
    print('fizz')
  elif i % 3 == 0:
    print('buzz')
  elif i % 6 == 0:
    print('fizzbuzz')

2でも3でも割り切れるというのは6で割り切れるということなので、 「i%6 == 0」としています。

このプログラムを実行すると、以下のようにfizzbuzzがないことが分かります。

fizz
buzz
fizz
fizz
fizz
buzz
fizz

この原因を調べる時にprintデバッグを使うと便利です。 たとえば以下のようにコードを書き換えます。

a = [1,2,3,4,5,6,7,8,9,10]

for i in a:
  print(i)
  if i % 2 == 0:
    print('fizz')
  elif i % 3 == 0:
    print('buzz')
  elif i % 6 == 0:
    print('fizzbuzz')
  print('----')

ループの中に何の値を調べているかを「print(i)」、 ループごとの区切りのを「print('fizzbuzz')」で表示するコードを追加いたしました。

これを実行すると、以下のような結果が得られます。

1
----
2
fizz
----
3
buzz
----
4
fizz
----
5
----
6
fizz
----
7
----
8
fizz
----
9
buzz
----
10
fizz
----

値6の箇所で「fizzbuzz」ではなく「fizz」と出力されていることがわかります。 if文の説明にて「elifはそれより前の条件が合致していた場合はチェックされない」としましたが、 今回は「elif i%6 == 0:」のチェックより前に「if i%2 == 0:」が合致してしまっているため、 fizzbuzzのチェックが実施されていません。

問題箇所がわかったので、プログラムを修正できます。

a = [1,2,3,4,5,6,7,8,9,10]

for i in a:
  if i % 6 == 0:
    print('fizzbuzz')
  elif i % 2 == 0:
    print('fizz')
  elif i % 3 == 0:
    print('buzz')

正しくfizzbuzzが表示されるようになりました。

fizz
buzz
fizz
fizzbuzz
fizz
buzz
fizz

代表的なエラー

Pythonのエラーの種類は豊富であり、自分で作ることさえできます。 それら全てを覚える必要はありませんが、よく見かけるエラーを紹介します。

宣言されていない変数や関数の利用

名前エラー 」は宣言されていない変数や関数を参照したときに発生するエラーです。

>>> a = 123
>>> print(a)
123
>>> print(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

変数aは初期化されており、その参照は問題なくできています。 一方、bは特に宣言無く参照されているため、「bは定義されていない」と名前エラーが発生しています。

よくあるのは変数名や関数名をタイプミスしている場合です。 たとえば「print('hello')」と書くつもりが「prinr('hello')」となっていれば、 prinrは存在しないので名前エラーが発生します。

あまり大きなモジュールを作らずに適切に内部を構造化していれば、 問題がおきてもすぐに何を修正すればよいかが分かります。

リストの範囲外のアクセス

リストや文字列などはインデックス番号を指定して、その要素にアクセスできます。 ただ、その指定するインデックス番号がリストの範囲を越える場合は「 インデックスエラー 」が発生します。

>>> a = [1,2,3]
>>> print(a[1])
2
>>> print(a[100])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

変数aには長さが3のリストが格納されています。 指定できるのはインデックスの0から2までなので、100を指定すれば範囲外のアクセスとなります。

Pythonのリストでループ処理をする場合は「for 変数 in リスト」を使うため、 インデックス番号の心配はいりません。 ただ、たまにインデックス番号を使う必要がある場面が発生するので、 そのような時は範囲外にアクセスしないように注意をしてください。 特にインデックス番号と長さの比較をする場合に「index < X」とするか「index <= X」とするかなどは、 よくミスをする箇所です。

関数の引数の数の間違い

関数を呼び出す際は引数を渡します。 関数が求める引数の数と、渡す引数の数が異なると「 タイプエラー 」が発生します。

>>> len([1,2,3])
3
>>> len(1,2,3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: len() takes exactly one argument (3 given)

len関数はリストや文字列などの長さを求める関数です。 この関数は引数を1つ取りますが、誤って3つを渡すとエラーになります。

エラーにも「len関数は引数を1つとるが、3つ渡された」とあります。

外部環境に依存するエラー

Pythonのプログラムは外部環境にアクセスすることがあります。 たとえばPCにあるファイルを読み込んだり、ネットワークの機能を使ったりといった具合です。

Pythonのプログラムとしては間違いがなくても、「読み込むファイルがなくなった」 「接続するサーバーがダウンしている」といった外部環境に依存して発生するトラブルは存在します。 これらは自分ではどうしようもない問題ですので、 「例外処理」というテクニックを使って「もしファイルがなかったらこうする」 「もしサーバーに繋がらなかったらこうする」といった具合でエラーが発生してもプログラムを継続できるようにします。

>>> import requests
>>> requests.get('http://0.0.0.0/')
Traceback (most recent call last):
...省略...
ConnectionRefusedError: [Errno 61] Connection refused