自分でプログラムを書いているときに迷った部分なので、メモとして書き残しておきます。
もっと良い対処法をご存知でしたら、ご指摘をお願いします。
※環境
- Python 3.7.5
- numpy 1.18.1
##numpyの0除算
Pythonで通常の0除算を行うとZeroDivisionError
が発生します。
try:
divided = 1 / 0
except ZeroDivisionError as e:
print(f'ZeroDivisionError: {e}')
# -> ZeroDivisionError: division by zero
一方、numpyで0除算を行うとRuntimeWarning
が発生しますが、処理が止まることなく結果が出力されます。
import numpy as np
divided = 1 / np.array(0)
print(devided)
# -> inf
# -> RuntimeWarning: divide by zero encountered in true_divide
1 / np.array(0)
の計算結果は、numpyで無限大を表すオブジェクトnumpy.inf
になります。
警告は出してくれるけど処理が止まるわけではないので、中途半端な感じです。おそらく、「一応計算できるけど、警告を出しておくよ。どうするかは任せるよ」と使用者に委ねられているのではないでしょうか。
そこで、処理を止めたい場合と、処理を許容して警告を無視する場合にどうするか考えてみます。
##warnings.simplefilterを使う
warnings.simplefilter
を使えば、RuntimeWarning
を例外扱いにしたり、無視扱いにすることができます。
- 例外扱いにして処理を止めたいとき
import warnings
import numpy as np
warnings.simplefilter('error', category=RuntimeWarning) # RuntimeWarningを例外扱いに設定
a = np.array(0) # 0 が入ることを想定していない変数
divided = 1 / a
print(divided)
# -> RuntimeWarning: divide by zero encountered in true_divide
# divided = 1 / aで処理が止まり、print(divided)が実行されない
- 計算を許容して警告を無視したいとき
import warnings
import numpy as np
warnings.simplefilter('ignore', category=RuntimeWarning) # RuntimeWarningを無視扱いに設定
a = np.array(0) # 0 が入ってもよい変数
divided = 1 / a
print(divided)
# -> inf
# 警告が出ず、処理が続行される
しかし、このとき0除算以外のRuntimeWarning
にも制御が適用されるため、特に無視する場合にはバグを補足できなくなる危険性があります。そこで、別の方法を考えます
##numpy.seteerを使う
(コメントによるご指摘を受け、numpy.seteer
を追記しました。)
numpy.seteer
を使えば、より細かいWarning
の制御を設定できます。
※以下、例外扱いにするときも無視扱いにするときもほぼ同様のため、無視扱いするときのソースコードのみを記載します。
import numpy as np
np.seterr(divide='ignore') # 0除算のRuntimeWarningのみを無視扱いとする
a = np.array(0)
divided = 1 / a
print(divided)
# -> inf
これで、0除算のみに制御を加えることができました。warnings
をimport
する煩雑さも解消されています。
しかし、この場合ソースコード全体にこの制御が適用されます。できれば、制御のスコープを最小限にしたいです。
除算の前後でnumpy.seterr
を書いて制御を切り替えてもいいのですが、もっとスマートな記述を考えます。
##numpy.errstateを使う
(こちらも、コメントによるご指摘から追記した部分です)
numpy.errstate
を使うことで、コンテキストマネージャーによる制御が可能になります。
import numpy as np
a = np.array(0)
with np.errstate(divide='ignore'): # withスコープ内のみ制御を適用する
divided = 1 / a
print(divided)
# -> inf
divided = 1 / a # 無視扱いのスコープ外なので、RuntimeWarningが発生する
# -> RuntimeWarning: divide by zero encountered in true_divide
こうすることで制御変更のスコープを小さくし、他の箇所の(許可していない)0除算によるバグを補足できます。
例では分かりやすさのため数値の除算を考えましたが、配列の除算も同様に制御できます。
import numpy as np
a = np.array([1, 2, 0])
with np.errstate(divide='ignore'): # withスコープ内のみ制御を適用する
divided = 1 / a
print(divided)
# -> [1. 0.5 inf]
1つの数値を除算する場合単純にif文を書けばいいのですが、配列の場合それが難しいので、この制御方法が活躍すると思います。
※配列の要素一つ一つをif文で判定するのはナンセンスだと思っています。可能な限り避けたいです。
##おまけ
当初、numpy.errstate
を知らず、次のような書き方を考えました。
import numpy as np
a = np.array([1, 2, 0])
divided = np.full(len(a), np.inf) # 全ての要素がnp.infの配列を作っておく
nonzero_indices = np.where(a != 0)[0] # aの0以外の要素を持つindexの配列
divided[nonzero_indices] = 1 / a[nonzero_indices] # nonzero_arrayの要素だけ除算に書き換える
print(divided)
# -> [1. 0.5 inf]
0除算のみの警告を回避したかったのですが、冗長感ありますね。
numpy.errstate
のような便利メソッドを覚えて使いこなせるようになりたいです。