今回はだいぶダーティーな手法に関する話。 未処理の例外が上がったときに走るデフォルトの処理をオーバーライドしてしまう方法について。 あらかじめ断っておくと、どうしても必要でない限り、こんなことはやらない方が望ましい。 とはいえ、これによって助けられることもあるかも。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G1012 $ python -V Python 3.7.5
もくじ
下準備
下準備として、Python のインタプリタを起動しておく。
$ python
デフォルトの挙動をオーバーライドする
try ~ except
で捕捉されない例外があると、次のように例外の詳細とトレースバックが出力される。
>>> raise Exception('Oops!') Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: Oops!
このときの挙動は sys.excepthook
でフックされているので、このオブジェクトを上書きすることでオーバーライドできる。
例えば、実用性は皆無だけどただメッセージを出力するだけの処理に置き換えてみよう。
>>> import sys >>> def myhook(type, value, traceback): ... print('Hello, World!', file=sys.stderr) ... >>> sys.excepthook = myhook
例外を上げてみると、次のようにメッセージが表示されるだけになる。
>>> raise Exception('Oops!') Hello, World!
関数のシグネチャについて
フックの関数のシグネチャについて、もうちょっと詳しく見てみよう。 以下のようにデバッグ用の関数をフックに指定する。
>>> def debughook(type, value, traceback): ... print(type, value, traceback, file=sys.stderr) ... >>> sys.excepthook = debughook
試しに例外を上げてみると、次のようになった。 例外クラスの型、引数、トレースバックのオブジェクトが渡されるようだ。
>>> raise Exception('Oops!') <class 'Exception'> Oops! <traceback object at 0x1024a6910>
スレッドを使うときの問題点について
なお、このフックはスレッドを使っているときに有効にならないという問題がある。
実際に試してみよう。 先ほどのデバッグ用のフックが有効な状態で、別のスレッドを起動する。 そして、スレッドの中で例外を上げるように細工してやろう。 すると、次のように普通のトレースバックが表示されてしまう。
>>> import threading >>> def f(): ... raise Exception('Oops!') ... >>> threading.Thread(target=f).start() >>> Exception in thread Thread-1: Traceback (most recent call last): File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 926, in _bootstrap_inner self.run() File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 870, in run self._target(*self._args, **self._kwargs) File "<stdin>", line 2, in f Exception: Oops!
上記のように、スレッドを使った場合にはフックが有効にならない。 この問題は Python 3.8 で追加された API によって解決できる。
$ python -V Python 3.8.0
Python 3.8 では threading モジュールに excepthook というオブジェクトが追加されている。 このオブジェクトを上書きすることで処理をオーバーライドできるようになった。
>>> def threading_hook(args): ... print('Hello, World!', args) ... >>> threading.excepthook = threading_hook >>> >>> threading.Thread(target=f).start() Hello, World! _thread.ExceptHookArgs(exc_type=<class 'Exception'>, exc_value=Exception('Oops!'), exc_traceback=<traceback object at 0x1033f4900>, thread=<Thread(Thread-2, started 123145518649344)>)
デフォルトの挙動に戻す
デフォルトのフックへの参照は sys.__excepthook__
にあるため、これを使えば挙動を元に戻せる。
なお、sys.__excepthook__
の方は絶対に変更しないこと。
>>> sys.excepthook = sys.__excepthook__ >>> raise Exception('Oops!') Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: Oops!
試してないけど Jupyter とかでエラーになったときチャットに通知を送る、なんて用途に使えるかもね。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る