Pythonのsys.exit()についてのメモ

先日、Pythonのsys.exit()について、いろいろ調べたので、そのことについてまとめておく。

なにはともあれ、公式ドキュメント sys --- システムパラメータと関数 — Python 3.8.5 ドキュメント を読む。

Python を終了します。 exit() は SystemExit を送出するので、 try ステートメントの finally 節に終了処理を記述したり、上位レベルで例外を捕捉して exit 処理を中断したりすることができます。

オプション引数 arg には、終了ステータスとして整数 (デフォルトは0)や他の型のオブジェクトを指定することができます。整数を指定した場合、シェル等は 0 は "正常終了"、0 以外の整数を "異常終了" として扱います。多くのシステムでは、有効な終了ステータスは 0--127 で、これ以外の値を返した場合の動作は未定義です。システムによっては特定の終了コードに個別の意味を持たせている場合がありますが、このような定義は僅かしかありません。Unix プログラムでは構文エラーの場合には 2 を、それ以外のエラーならば 1 を返します。arg に None を指定した場合は、数値の 0 を指定した場合と同じです。それ以外の型のオブジェクトを指定すると、そのオブェクトが stderr に出力され、終了コードとして 1 を返します。エラー発生時には sys.exit("エラーメッセージ") と書くと、簡単にプログラムを終了することができます。

究極には、 exit() は例外を送出する "だけ" なので、これがメインスレッドから呼び出されたときは、プロセスを終了するだけで、例外は遮断されません。

バージョン 3.6 で変更: Python インタープリタが (バッファされたデータを標準ストリームに吐き出すときのエラーなどの) SystemExit を捕捉した後の後始末でエラーが起きた場合、終了ステータスは 120 に変更されます。

まず、基本的な使い方は、きっと

import sys

print("Hello!")
sys.exit()
print("Hello!!")

(二回目のprintは実行されない)です。

知りたかったこと1. sys.exitのリターンコードは一体どうなるのか

ドキュメントでは、オプション引数argが、整数か他の型で異なると書いてあります。整数とNone以外のオプジェクトを渡すと、stderrにオブジェクトを表示させて、プログラムを終了すると書かれていますが、
その時のリターンコードが1だと書かれています。
整数を渡したときは、どうでしょうか。例えば、-1や300を渡したら、どうなるかを調べてみました。(環境依存?)

$ python3.7 rtn_code.py 1; echo $?
1
1
$ python3.7 rtn_code.py 255; echo $?
255
255
$ python3.7 rtn_code.py 256; echo $?
256
0
$ python3.7 rtn_code.py -1; echo $?
-1
255

となり、循環した整数になるようです。

知りたかったこと2 sys.exitするのと、raise SystemExitとの違い

これは、ほぼほぼ違いがないようです。raise SystemExit(99)とか書けば良いっぽい。

docs.python.org

知りたくなったこと SystemExitとはなんぞや。

通常の例外は、Exceptionを継承していますが、SystemExitは、BaseExceptionを継承しているようです。この違いっていまいち不明だが・・・。

話は変わり、flake8のE722

flake8で、

try:
    f = open("hoge.txt", "r")
except:
    print("except")

のようなコードを書くと、flake8で怒られます。

E722 do not use bare 'except'

exceptのままで使うなと。(この例だと、ちゃんとFileNotFoundErrorをExceptしてあげれば良い。)

で、実験

以下のようなコードがあったとき、何が出力されるのか。

import sys

try:
    sys.exit(10)
except Exception:
    print("sys.exit E")
except BaseException:
    print("sys.exit BE")

try:
    raise SystemExit(10)
except Exception:
    print("SystemExit E")
except SystemExit:
    print("SystemExit BE")


try:
    raise
except Exception:
    print("raise E")
except BaseException:
    print("raise BE")

答えは、

sys.exit BE
SystemExit BE
raise E

sys.exit()しても、raise SystemExitしても、BaseExceptionで補足されている。つまり、Exceptionでは補足されないです。(ドキュメント通り)

で、次は、bare exceptを使った場合。

import sys

try:
    raise
except:
    print("raise bare except")

try:
    raise SystemExit(10)
except:
    print("SystemExit bare except")

結果は、

raise bare except
SystemExit bare except

となる。つまり、bare exceptを使うと、SystemExitも、Exceptionの例外も両方握り潰す処理することになる。

(昔知ったTips)

Jupyter notebookで、処理の途中で、exit相当のことをしたいなと思って、sys.exitを書くと、カーネルごとお亡くなりになります。
そこで、raiseをかませるというTipsを、昔StackOverflowか何かで読んだ記憶があります。(けど、最近のjupyter labとか使うと、カーネル落ちなくなってて、賢い!と思ったり)

まとめ

E722 do not use bare 'except'