timeoutデコレータで、動的にタイムアウト時間を設定したかった

## Pythonのタイムアウトデコレータ実行値を動的に変えたい
この記事はpythonほぼわからん人間が描いた試行錯誤の軌跡である

以下の要件があった。
 - 新規の機能を追加したい → 新規関数追加すればいいや
 - タイムアウト機能をつけたい → 新規でtimeoutのデコレータ作ってつければいいや
 - タイムアウト時間は呼び出し元に応じて変化させたい → デコレータでやるならこれは実装どうしよう・・・・

デコレータは以下の通り、printはデバッグ用

```python:timeout.py
from functools import wraps
import errno
import os
import signal

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):

            print("decorator.second: %s" % seconds)

            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.setitimer(signal.ITIMER_REAL, seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result
        return wraps(func)(wrapper)
    return decorator
```

#### ダメだった試行錯誤
- パターン①：instance変数の利用

```python:
class Test:
  def __init__(self, ...):
    self.TimeoutSec = 10
  ...

  @timeout(self.TimeoutSec)
  def execHoge():
    ...
```
実行結果

```sh:
Traceback (most recent call last):
  File "./CallTest.py", line 7, in <module>
    import Test
  File "~/Test.py", line 18, in <module>
    class Test:
  File "~/Test.py", line 111, in Test
    @timeout(self.TimeoutSec)
NameError: name 'self' is not defined
```
デコレータはinstance変数を読み取ってくれなかった。

- パターン②：class変数の利用<br>
複数の端末から同時に実行される想定ではあるが、int型でimmutableだから、ヨシ！ <br>
\# class変数はバグの温床になりやすいから余り使いたくはないが四の五の言う暇はない

```python:
class Test:
  TimeoutSec=1.5
  def __init__(self, ...):
    self.TimeoutSec = 0.05
    print("class変数: %s " % self.TimeoutSec)
  ...

  @timeout(TimeoutSec)
  def execHoge():
    ...
```
実行結果

```sh:
class変数: 0.05
decorator.second: 1.5
```
class作成時に定義しているclass変数の値でdecoretorを呼び出していることが分かった<br>

- パターン③：global変数の利用
class変数で書いた理由と同様の理由で、ヨシ！

```python:
TIMEOUT_SEC=1.5
class Test:
  def __init__(self, ...):
    global TIMEOUT_SEC
    TIMEOUT_SEC = 0.05
    print("global変数: %s " % TIMEOUT_SEC)
  ...

  @timeout(TIMEOUT_SEC)
  def execHoge():
    ...
```
実行結果

```sh:
global変数: 0.05
decorator.second: 1.5
```
globalでもclassでも定義時の値がデコレータに渡されていることがわかる<br>

ここら辺でお手上げになったのでpython分かる先輩にきいたところ、ヘボエンジニアの私が理解できるように説明してくれた。<br>
\# あくまで私が理解できるようにしてくれたものなので、正確な仕様とは微妙に違うかも・・・

 - pythonの読込み時に、デコレータは付与してる関数に処理を追加した関数を内部的にこっそり作ってるよ
 - Instanceは読込みが終わって、コンピュータが実行するタイミングで作られるものだから、読込み時にinstance使おうとしてもエラーが出るよ <br>
  - だからパターン①は実行したタイミングでtraceback流れてるんだよ

<br> \# python勉強しとこう‥‥

#### つまり治すべきは実装ではなくデコレータだった
上述しているデコレータを下記のように修正した。
修正概要は以下
 - 引数でタイムアウトの秒数指定をやめた
 - Instance変数で、timeout_secondを用意しなければならないようにした

 ```python:timeout.py
 from functools import wraps
 import errno
 import os
 import signal

 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
     def decorator(func):
         def _handle_timeout(signum, frame):
             raise TimeoutError(error_message)

         def wrapper(*args, **kwargs):
             seconds = args[0].timeout_seconds
             print("decorator.second: %s" % seconds)
             signal.signal(signal.SIGALRM, _handle_timeout)
             signal.setitimer(signal.ITIMER_REAL, seconds)
             try:
                 result = func(*args, **kwargs)
             finally:
                 signal.alarm(0)
             return result
         return wraps(func)(wrapper)
     return decorator
 ```

 ```python:
class Test:
  def __init__(self, ...):
    self.timeout_seconds = 0.05
    print("instance変数: %s " % self.timeout_seconds)
    ...

    @timeout()
    def execHoge():
      ...
 ```
実行結果

```sh:
instance変数: 0.05
decorator.second: 0.05
```

これにて漸くタイムアウト時間を柔軟に設定できるようになった。

#### 反省点
納期が短かったとはいえ、無理にデコレータで実装せずに、別途なにか方法考えても良かったかもしれないなあ。<br>
汎用的じゃない不細工な実装に仕上がったものがmasterに乗るのはとても悔しい