IPython環境で呼び出したスクリプト内でquit()
やexit()
を使うとNameErrorが発生する。
NameError: name 'quit' is not defined
or
NameError: name 'exit' is not defined
この場合はimport sys
をしてからsys.exit()
を使うと想定通りの動作になる。quit()
を使っているということはそこでスクリプトを終わらせたいわけで、例外でコケて止まるので一応目的は達成されているといえるがIPythonの詳細すぎるエラーメッセージがうるさい。
終了系の組み込み関数/例外が使えない
quit()
exit()
IPythonそのものがPythonプログラムであるのでスクリプトからこれらの関数や例外をそのまま使われるとIPythonのシェルそのものが終わってしまう。なのでこれらはスクリプトの名前空間からは取り除かれている。
簡単な検証
まず次のスクリプトを普通のpythonコマンドで動かす。
print(type(quit),str(quit))
print(type(exit),str(exit))
print(type(SystemExit),str(SystemExit))
結果は次のようになる。
<class '_sitebuiltins.Quitter'> Use quit() or Ctrl-Z plus Return to exit
<class '_sitebuiltins.Quitter'> Use exit() or Ctrl-Z plus Return to exit
<class 'type'> <class 'SystemExit'>
Quitter
というビルトインによって終了機能が実現されていることがわかる。このQuitter
のソースコードを調べてみると中では最終的に組み込み例外のSytemExitによってプログラムを終了していることがわかる。pythonコマンドからの実行ではこの例外に直接アクセスしてプログラムを止めることも可能だ。
普通のpythonコマンドもインタラクティブシェルを備えておりそこから同じプログラムを動かしても結果は同じである。
>>> type(quit), str(quit)
(<class '_sitebuiltins.Quitter'>, 'Use quit() or Ctrl-Z plus Return to exit')
>>> type(exit), str(exit)
(<class '_sitebuiltins.Quitter'>, 'Use exit() or Ctrl-Z plus Return to exit')
>>> type(SystemExit), str(SystemExit)
(<class 'type'>, "<class 'SystemExit'>")
>>> raise SystemExit(0)
# ここで正しく終了
以上をIPython内のrun
コマンドから行ってみる。順番は異なるがまずインタラクティブシェルから同じコードを実行すると次の結果が返ってくる。
In [1]: type(quit), str(quit)
Out[1]:
(IPython.core.autocall.ExitAutocall,
'<IPython.core.autocall.ExitAutocall object at 0x00000199738E7700>')
In [2]: type(exit), str(exit)
Out[2]:
(IPython.core.autocall.ExitAutocall,
'<IPython.core.autocall.ExitAutocall object at 0x00000199738E7700>')
In [3]: type(SystemExit), str(SystemExit)
Out[3]: (type, "<class 'SystemExit'>")
In [4]: raise SystemExit(0)
An exception has occurred, use %tb to see the full traceback.
SystemExit: 0
c:\users\xxx\appdata\local\programs\python\python38\lib\site-packages\IPython\core\interactiveshell.py:3339: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.
warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
これらの名前は存在しているもののタイプが異なっている。quit
、exit
はIPython内のディレクティブとして上書きされていることが読み取れるがSystemExit
はそのまま残っているように見える。しかしこれを直接呼び出そうとすると警告がでてごらんのようにquit
かexit
かCtrl-D
でシェルを終了させなさいと怒られる。
これをrun script.py
によって実行させるとこのようになる。
In [1]: run script.py
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
~\Documents\Python\20200401labexp\e01mmpatern.py in <module>
----> 1 print(type(quit),str(quit))
2 print(type(exit),str(exit))
3 print(type(SystemExit),str(SystemExit))
NameError: name 'quit' is not defined
名前が定義されていないといわれる。run
から実行したスクリプトの中では名前空間からquit
やexit
が取り除かれているようだ。最後にスクリプト内でraise SystemExit(0)
で例外を送出してみるとどうなるだろうか。エラーなしで終了するのである。
sys.exit() かraise SystemExit
以上で結論は出た。IPython内から実行したスクリプトをquit()
やexit()
と根本的に同じ方法で中断させるには以下の2種類のいずれかを行えばよい。
import sys
sys.exit()
# or
raise SystemExit
インポート文が邪魔だがすっきりするのはsys.exit()
だろう。この中ではここにある通り結局SystemExit
が送出されるので上記はプログラム中断の方法としては根本的に同じである。
感想
IPythonのシェルから直接raise SystemExit
をされることまで想定してwarningが用意されているのはやられた。