# 17.4. `signal` --- 非同期イベントにハンドラを設定する

このモジュールでは Python でシグナルハンドラを使うための機構を提供します。シグナルとハンドラを扱う上で一般的なルールがいくつかあります:

- 特定のシグナルに対するハンドラが一度設定されると、明示的にリセットしないかぎり設定されたままになります (Python は背後の実装系に関係なく BSD 形式のインタフェースをエミュレートします)。例外は `SIGCHLD` のハンドラで、この場合は背後の実装系の仕様に従います。
- クリティカルセクションから一時的にシグナルを "ブロック" することはできません (この機能をサポートしない Unix 系システムも存在するためです)。
- Python のシグナルハンドラは Python のユーザが望む限り非同期で呼び出されますが、呼び出されるのは Python インタプリタの "原子的な (atomic)" 命令実行単位の間です。したがって、 (巨大なサイズのテキストに対する正規表現の一致検索のような) 純粋に C 言語のレベルで実現されている時間のかかる処理中に到着したシグナルは、不定期間遅延する可能性があります。
- シグナルが I/O 操作中に到着すると、シグナルハンドラが処理を返した後に I/O 操作が例外を送出する可能性があります。これは背後にある Unix システムが割り込みシステムコールにどういう意味付けをしているかに依存します。
- C 言語のシグナルハンドラは常に処理を返すので、 `SIGFPE` や `SIGSEGV` のような同期エラーの捕捉はほとんど意味がありません。
- Python は標準でごく少数のシグナルハンドラをインストールしています: `SIGPIPE` は無視されます (したがって、パイプやソケットに対する書き込みで生じたエラーは通常の Python 例外として報告されます) `SIGINT` は `KeyboardInterrupt` 例外に変換されます。これらはどれも上書きすることができます。
- シグナルとスレッドの両方を同じプログラムで使用する場合にはいくつか注意が必要です。シグナルとスレッドを同時に利用する上で基本的に注意すべきことは、 `signal()` 命令は常に主スレッド (main thread) の処理中で実行するということです。どのスレッドも `alarm()` 、 `getsignal()` 、 `pause()` 、 `setitimer()` 、 `getitimer()` を実行することができます。しかし、主スレッドだけが新たなシグナルハンドラを設定することができ、したがってシグナルを受け取ることができるのは主スレッドだけです (これは、背後のスレッド実装が個々のスレッドに対するシグナル送信をサポートしているかに関わらず、 Python `signal` モジュールが強制している仕様です)。したがって、シグナルをスレッド間通信の手段として使うことはできません。代わりにロック機構を使ってください。

以下に `signal` モジュールで定義されている変数を示します:

`signal.``SIG_DFL`

  二つある標準シグナル処理オプションのうちの一つです; 単純にシグナルに対する標準の関数を実行します。例えば、ほとんどのシステムでは、 `SIGQUIT` に対する標準の動作はコアダンプと終了で、 `SIGCHLD` に対する標準の動作は単にシグナルの無視です。

`signal.``SIG_IGN`

  もう一つの標準シグナル処理オプションで、受け取ったシグナルを単に無視します。

`SIG*`

  全てのシグナル番号はシンボル定義されています。例えば、ハングアップシグナルは `signal.SIGHUP` で定義されています; 変数名は C 言語のプログラムで使われているのと同じ名前で、 `<signal.h>` にあります。 '`signal()`' に関する Unix マニュアルページでは、システムで定義されているシグナルを列挙しています (あるシステムではリストは signal(2) に、別のシステムでは signal(7) に列挙されています)。全てのシステムで同じシグナル名のセットを定義しているわけではないので注意してください; このモジュールでは、システムで定義されているシグナル名だけを定義しています。

`signal.``CTRL_C_EVENT`

  `CTRL+C` キーストロークに該当するシグナル。このシグナルは `os.kill()` でだけ利用できます。

  利用出来る環境: Windows.

  バージョン 2.7 で追加.

`signal.``CTRL_BREAK_EVENT`

  `CTRL+BREAK` キーストロークに該当するシグナル。このシグナルは `os.kill()` でだけ利用できます。

  利用出来る環境: Windows.

  バージョン 2.7 で追加.

`signal.``NSIG`

  最も大きいシグナル番号に 1 を足した値です。

`signal.``ITIMER_REAL`

  実時間でデクリメントするインターバルタイマーです。タイマーが発火したときに `SIGALRM` を送ります。

`signal.``ITIMER_VIRTUAL`

  プロセスの実行時間だけデクリメントするインターバルタイマーです。タイマーが発火したときに `SIGVTALRM` を送ります。

`signal.``ITIMER_PROF`

  プロセスの実行中と、システムがそのプロセスのために実行している時間だけデクリメントするインターバルタイマーです。ITIMER_VIRTUAL と組み合わせて、このタイマーはよくアプリケーションがユーザー空間とカーネル空間で消費した時間のプロファイリングに利用されます。タイマーが発火したときに `SIGPROF` を送ります。

`signal` モジュールは1つの例外を定義しています:

exception `signal.``ItimerError`

  背後の `setitimer()` または `getitimer()` 実装からエラーを通知するために送出されます。無効なインタバルタイマーや負の時間が `setitimer()` に渡された場合、このエラーを予期してください。このエラーは `IOError` を継承しています。

`signal` モジュールでは以下の関数を定義しています:

`signal.``alarm`(time)

  time がゼロでない値の場合、この関数は time 秒後頃に `SIGALRM` をプロセスに送るように要求します。それ以前にスケジュールしたアラームはキャンセルされます (常に一つのアラームしかスケジュールできません)。この場合、戻り値は以前に設定されたアラームシグナルが通知されるまであと何秒だったかを示す値です。 time がゼロの場合、アラームは一切スケジュールされず、現在スケジュールされているアラームがキャンセルされます。戻り値がゼロの場合、現在アラームがスケジュールされていないことを示します。(Unix マニュアルページ alarm(2) を参照してください)。利用できる環境: Unix。

`signal.``getsignal`(signalnum)

  シグナル signalnum に対する現在のシグナルハンドラを返します。戻り値は呼び出し可能な Python オブジェクトか、 `signal.SIG_IGN`、 `signal.SIG_DFL`、および `None` といった特殊な値のいずれかです。ここで `signal.SIG_IGN` は以前そのシグナルが無視されていたことを示し、 `signal.SIG_DFL` は以前そのシグナルの標準の処理方法が使われていたことを示し、 `None` はシグナルハンドラがまだ Python によってインストールされていないことを示します。

`signal.``pause`()

  シグナルを受け取るまでプロセスを一時停止します; その後、適切なハンドラが呼び出されます。戻り値はありません。Windows では利用できません。(Unix マニュアルページ signal(2) を参照してください。)

`signal.``setitimer`(which, seconds[, interval])

  which で指定されたタイマー (`signal.ITIMER_REAL`, `signal.ITIMER_VIRTUAL`, `signal.ITIMER_PROF` のどれか) を、 seconds 秒後と (`alarm()` と異なり、floatを指定できます)、それから interval 秒間隔で起動するように設定します。 seconds に0を指定すると、which で指定されたタイマーをクリアすることができます。

  インターバルタイマーが起動したとき、シグナルがプロセスに送られます。送られるシグナルは利用されたタイマーの種類に依存します。 `signal.ITIMER_REAL` の場合は `SIGALRM` が、 `signal.ITIMER_VIRTUAL` の場合は `SIGVTALRM` が、 `signal.ITIMER_PROF` の場合は `SIGPROF` が送られます。

  以前の値が (delay, interval) のタプルとして返されます。

  無効なインターバルタイマーを渡すと `ItimerError` 例外が発生します。利用できる環境: Unix。

  バージョン 2.6 で追加.

`signal.``getitimer`(which)

  which で指定されたインターバルタイマーの現在の値を返します。利用できる環境: Unix。

  バージョン 2.6 で追加.

`signal.``set_wakeup_fd`(fd)

  wakeup fd を fd に設定します。シグナルを受信したときに、 `'\0'` バイトがそのfd に書き込まれます。これは、poll や select をしているライブラリを起こして、シグナルの処理をさせるのに利用できます。

  The old wakeup fd is returned (or -1 if file descriptor wakeup was not enabled). If fd is -1, file descriptor wakeup is disabled. If not -1, fd must be non-blocking. It is up to the library to remove any bytes from fd before calling poll or select again.

  スレッドが有効な場合、この関数はメインスレッドからしか実行できません。それ以外のスレッドからこの関数を実行しようとすると `ValueError` 例外が発生します。

  バージョン 2.6 で追加.

`signal.``siginterrupt`(signalnum, flag)

  システムコールのリスタートの動作を変更します。 flag が `False` の場合、 signalnum シグナルに中断されたシステムコールは再実行されます。それ以外の場合、システムコールは中断されます。戻り値はありません。利用できる環境: Unix (詳しい情報についてはマニュアルページ siginterrupt(3) を参照してください)。

  `signal()` を使ってシグナルハンドラを設定したときに、暗黙のうちに flag に true を指定して `siginterrupt()` が実行されるため、中断に対するリスタートの動作がリセットされることに注意してください。

  バージョン 2.6 で追加.

`signal.``signal`(signalnum, handler)

  シグナル signalnum に対するハンドラを関数 handler にします。 handler は二つの引数 (下記参照) を取る呼び出し可能な Python オブジェクトか、 `signal.SIG_IGN` あるいは `signal.SIG_DFL` といった特殊な値にすることができます。以前に使われていたシグナルハンドラが返されます (上記の `getsignal()` の記述を参照してください)。 (Unix マニュアルページ signal(2) を参照してください。)

  スレッドが有効な場合、この関数はメインスレッドからしか実行できません。それ以外のスレッドからこの関数を実行しようとすると `ValueError` 例外が発生します。

  handler は二つの引数とともに呼び出されます: シグナル番号、および現在のスタックフレーム (`None` またはフレームオブジェクト; フレームオブジェクトについての記述は 標準型の階層における説明 か、 `inspect` モジュールの属性の説明を参照してください)。

  Windows では、 `signal()` は `SIGABRT`, `SIGFPE`, `SIGILL`, `SIGINT`, `SIGSEGV`, `SIGTERM` でのみ利用できます。それ以外の場合は `ValueError` を発生させます。

## 17.4.1. 例

以下は最小限のプログラム例です。この例では `alarm()` を使ってファイルを開く処理を待つのに費やす時間を制限します; 例えば、電源の入っていないシリアルデバイスを開こうとすると、通常 `os.open()` は未定義の期間ハングアップしてしまいますが、この方法はそうした場合に便利です。ここではファイルを開くまで 5 秒間のアラームを設定することで解決しています; ファイルを開く処理が長くかかりすぎると、アラームシグナルが送信され、ハンドラが例外を送出するようになっています。

    import signal, os

    def handler(signum, frame):
        print 'Signal handler called with signal', signum
        raise IOError("Couldn't open device!")

    # Set the signal handler and a 5-second alarm
    signal.signal(signal.SIGALRM, handler)
    signal.alarm(5)

    # This open() may hang indefinitely
    fd = os.open('/dev/ttyS0', os.O_RDWR)

    signal.alarm(0)          # Disable the alarm