平均点を計算するだけの簡単なプログラムなのに、ZeroDivisionError: division by zero と表示されて止まってしまった。Pythonを学び始めると、一度はこんな場面に出会うのではないでしょうか。
自分では0で割った覚えなんてないのに、と首をかしげた方も多いはずです。実はこのエラーは、コードを書いた時点ではなく、データの中身によって起こることがほとんどです。
この記事では、ZeroDivisionErrorの意味とメッセージの読み方から、よくある原因、そして落ちないコードの書き方までを順番に解説します。エラーの正体がわかれば、怖がる必要はまったくありません。
サンプルの実行結果は、手元のLinux環境のPython 3.11から3.13で確認したものです。WindowsやmacOSでも、エラーの名前と考え方は同じです。
ZeroDivisionErrorとはどんなエラーなのか¶
まずは、このエラーが何を知らせているのかを押さえておきましょう。ひとことで言うと、割り算や余りの計算で、割る数が0だったときに出るエラーです。
算数でも、0で割ることはできないと習いましたよね。10個のりんごを0人で分けると1人何個になるか、という問いには答えがありません。
Pythonも同じ考え方をします。答えが決められない計算を無理に進めるのではなく、例外を出してその場で止まるのです。
ZeroDivisionErrorは、ArithmeticErrorという例外の仲間です。ArithmeticErrorは、計算そのものがうまくいかなかったときに使われる例外のグループです。
実際にクラスの関係を確かめると、次のようになっています。
print(ZeroDivisionError.__mro__)
# (<class 'ZeroDivisionError'>, <class 'ArithmeticError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
except ArithmeticError と書けば、ZeroDivisionErrorも受け止められます。とはいえ範囲が広くなるので、ふだんはZeroDivisionErrorと具体的に書くほうが意図が伝わりやすいです。
エラーメッセージの読み方¶
次に、メッセージの中身を見てみましょう。実はZeroDivisionErrorは、どの演算子で割ったかによって、少しずつ違うメッセージを出します。
手元で試した結果を、次の表にまとめました。
| 書いたコード | 表示されたメッセージ |
|---|---|
| 1 / 0 | division by zero |
| 1 // 0 | integer division or modulo by zero |
| 1 % 0 | integer modulo by zero |
| 1.0 / 0 | float division by zero |
| 1.0 // 0 | float floor division by zero |
| divmod(1, 0) | integer division or modulo by zero |
| 0 ** -1 | 0.0 cannot be raised to a negative power |
メッセージにintegerとあれば整数どうしの計算、floatとあれば小数を含む計算で起きたことがわかります。どの行で起きたのかと合わせて読むと、原因の変数を絞り込みやすくなります。
最後の0 ** -1も見落とせません。マイナス乗は1を割る計算と同じなので、0のマイナス乗も同じエラーになります。
なお、メッセージの細かい文言はバージョンによって変わることがあります。たとえば1.0 % 0は、3.12まではfloat moduloでしたが、3.13ではfloat modulo by zeroと表示されました。
エラーが起きた行を探すときは、メッセージより上に表示されるtracebackを読みます。読み方に自信がない方は、こちらの記事が参考になります。 【関連記事】Pythonのエラー文はどこを読めばいい?初心者向けtracebackの見方
ZeroDivisionErrorのよくある原因¶
ここからが本題です。わざわざ1 / 0と書く人はいませんから、実際のエラーはもっと気づきにくい形でやってきます。
空のリストで平均を求めた¶
いちばん多いのが、このパターンです。合計をlen()で割って平均を出すコードは、リストが空だと割る数が0になります。
scores = []
average = sum(scores) / len(scores)
# ZeroDivisionError: division by zero
テストでは必ずデータがあったのに、本番では検索結果が0件だった。そんな理由で、ある日突然エラーが出るのです。
私は10年ほどエンジニアとして開発に関わってきましたが、月次レポートのバッチがこのエラーで止まった経験があります。新しく追加した店舗の売上データがまだ1件もなく、件数0で割っていたのが原因でした。
ユーザーの入力や外部のデータが0だった¶
人数や個数をinput()で受け取って割り算に使う場合も要注意です。利用者が0を入力する可能性は、いつでもあります。
CSVやAPIから受け取った値も同じです。データが欠けていて0として扱われていた、ということは実務でもよくあります。
計算の途中で0になった¶
割る数が、別の計算の結果になっていることもあります。たとえば、a - b のように引き算した結果で割ると、aとbが同じ値のときに0になります。
このタイプはコードを読むだけでは気づきにくいのが難点です。エラーが出たら、割る数にあたる変数を一度printして、どんな値になっているか確かめてみてください。
0で割らないための防ぎ方¶
原因がわかったところで、対策を見ていきましょう。方法は大きく分けて、先に確かめる方法と、起きてから受け止める方法の2つがあります。
先にif文で確かめる¶
もっともシンプルなのは、割る前に0でないかを確かめることです。次の関数は、空のリストが渡されたら平均の代わりにNoneを返します。
def safe_average(values):
if not values:
return None
return sum(values) / len(values)
print(safe_average([80, 90, 70])) # 80.0
print(safe_average([])) # None
if not values は、リストが空かどうかを調べる書き方です。空のリストは偽として扱われるので、これだけで判定できます。
0の代わりに何を返すかは、場面によって変わります。平均点ならNone、達成率なら0として表示する、というように0件のときに何を見せたいかを先に決めておくことが大切です。
try-exceptで受け止める¶
もうひとつは、まず計算してみて、ZeroDivisionErrorが起きたら対処する書き方です。割る数がどこで0になるか予想しにくいときに便利です。
def calc_rate(done, total):
try:
return done / total * 100
except ZeroDivisionError:
print(f"totalが0のため達成率を計算できません(done={done})")
return 0.0
print(calc_rate(3, 4)) # 75.0
print(calc_rate(0, 0)) # メッセージを表示して 0.0
ここで大事なのは、エラーをただ隠すのではなく、どの値で失敗したかを残しておくことです。except ZeroDivisionError: pass のように黙って握りつぶすと、あとで原因を探すのに苦労します。
どちらの方法を選ぶかは、好みよりも状況で決めるのがおすすめです。違いを表にまとめておきます。
| 方法 | 向いている場面 | 注意点 |
|---|---|---|
| if文で先に確かめる | 0になる条件がはっきりしている | 割る数が複数あると条件が増える |
| try-exceptで受け止める | 0になる場所を予想しにくい | 握りつぶさず、値を記録する |
try-exceptの基本や、else・finallyの使い方をおさらいしたい方は、こちらの記事でくわしく解説しています。 【関連記事】Pythonのtry・except・else・finallyとは?例外処理の基本の流れ
floatの小ささと割り算の落とし穴¶
ZeroDivisionErrorを防ぐために、割る数が0かどうかを == 0 で比べることがあります。整数なら問題ありませんが、小数の計算では少し注意が必要です。
たとえば 0.1 + 0.2 - 0.3 は、数学的には0になるはずです。ところがPythonで計算すると、ごくわずかな誤差が残って0にはなりません。
x = 0.1 + 0.2 - 0.3
print(x == 0) # False
print(1 / x) # 1.8014398509481984e+16
0ではないのでエラーにはならず、とてつもなく大きな数が返ってきました。エラーが出ないぶん、気づかないまま間違った結果を使ってしまう危険があります。
小数の比較では、math.isclose()で十分に近いかどうかを判定する方法が使えます。誤差が生まれる仕組みが気になる方は、こちらの記事もあわせて読んでみてください。 【関連記事】Pythonの丸め誤差とは?初心者にもわかる原因と対策
decimalやstatisticsではどうなるのか¶
標準ライブラリの中には、0での割り算を独自に扱うものもあります。代表的な2つを見ておきましょう。
ひとつ目は、お金の計算などで使われるdecimalモジュールです。Decimal(1) / 0 を計算すると、decimal.DivisionByZeroという例外が出ます。
このDivisionByZeroは、ZeroDivisionErrorを継承しています。そのため、except ZeroDivisionError と書いておけば、ふつうの割り算と同じように受け止められます。
ただし、Decimal(0) / 0 のように0を0で割ると、手元ではdecimal.InvalidOperationという別の例外になりました。こちらはZeroDivisionErrorの仲間ではないので、except ZeroDivisionErrorでは捕まえられません。
二つ目は、平均や中央値を求めるstatisticsモジュールです。statistics.mean([]) のように空のデータを渡すと、ZeroDivisionErrorではなくStatisticsErrorが出ます。
メッセージは mean requires at least one data point で、平均には最低1つのデータが必要だと教えてくれます。0で割ったという結果ではなく、データが空だという原因を直接伝えてくれるので、自分でsum() / len()を書くより親切です。
それぞれの違いを、次の表に整理しました。
| 計算 | 出る例外 | except ZeroDivisionErrorで捕まるか |
|---|---|---|
| 1 / 0 | ZeroDivisionError | 捕まる |
| Decimal(1) / 0 | decimal.DivisionByZero | 捕まる |
| Decimal(0) / 0 | decimal.InvalidOperation | 捕まらない |
| statistics.mean([]) | statistics.StatisticsError | 捕まらない |
ライブラリを使うと、同じ0で割る場面でもエラーの種類が変わることがあります。ドキュメントで、どんな例外が出るかを確かめておくと安心です。
statisticsモジュールの使い方は、こちらの記事でくわしく紹介しています。 【関連記事】Pythonのstatisticsとは?平均値にだまされないための基本の統計を初心者向けに解説
ZeroDivisionErrorに強いコードを書くコツ¶
最後に、実務でも役立つ考え方を紹介します。大切なのは、エラーを消すことよりも、0件という状態をきちんと設計することです。
画面に平均を表示するなら、0件のときは平均をハイフンで表示する。集計のバッチなら、0件の店舗はスキップしてログに残す。このように、0のときの振る舞いを仕様として決めておくと迷いません。
先ほど紹介した月次レポートの失敗のあと、私のチームでは割り算を書くたびに、割る数が0になるのはどんなときかを話し合うようにしました。地味な習慣ですが、それ以来同じ原因で止まったことはありません。
/や//、%の違いをもう一度整理したい方は、こちらの記事が参考になります。 【関連記事】Pythonの割り算とは?/・//・%・divmod()の違いを初心者向けに解説
まとめ¶
ZeroDivisionErrorは、割り算や余りの計算で割る数が0だったときに出るエラーです。メッセージのintegerやfloatという言葉から、どんな値どうしで計算したのかを読み取れます。
原因の多くは、空のリストで平均を出したり、入力や外部のデータが0だったりする場面です。割る数がいつ0になるのかを考えることが、いちばんの予防になります。
防ぎ方は、if文で先に確かめる方法と、try-exceptで受け止める方法の2つです。どちらを使う場合も、0のときに何を返すかを決め、失敗した値を記録しておきましょう。
decimalやstatisticsのように、独自の例外を出すライブラリもあります。エラーの名前をよく読む習慣をつければ、次に同じ場面に出会っても落ち着いて対処できるはずです。
ここまでお読みいただきありがとうございました。