pythonのクラスをデコレータ(@関数)で使用する

# はじめに
[前回の関数をデコレータ化](https://qiita.com/mink0212/items/9b93414a823cc8b7944d)の続きです。クラスをデコレータ化してさらに色々なことを試してみます。関数のデコレータとは違い、クラス内の変数や関数を保持できるためできることが増えます。

# 環境
 - python:3.6.5  

# シンプルなクラスのデコレータ
## シンプルなクラスのデコレータの例
文言を標準出力をするだけのクラスをデコレータにします。


``` python:main.py
class myDecorator(object):
    def my_decorator(self, my_func):
        def decorator_wrapper(*args, **kwargs):
            print('--- decorator before my_func ---')
            my_func(*args, **kwargs)
            print('--- decorator after my_func ---\n')

        return decorator_wrapper

decorator = myDecorator()

@decorator.my_decorator
def print_hello():
    print('hello world')

print_hello()
```

結果

```
# python3 main.py
--- decorator before my_func ---
hello world
--- decorator after my_func ---
```

## シンプルなクラスのデコレータの説明
クラスの中に関数のデコレータ```def my_decorator(self, my_func):```を入れ、さらにその中に実処理用の```decorator_wrapper```を入れるとデコレータ用のクラスが作成できます。適用するときは普通にクラスを生成して関数のデコレータ```@decorator.my_decorator```を適用するだけです。


# クラスの変数を保持するデコレータ
## クラスの変数を保持するデコレータの例
クラスなので初期化やクラス変数を保持することができます。
例としてカウントアップの加算数を初期化で与えて関数を実行するたびにカウントアップします。


``` python:main.py
class myDecorator(object):
    def __init__(self, interval):
        self.interval = interval
        self.count = 0
    def my_decorator(self, my_func):
        def decorator_wrapper(*args, **kwargs):
            print('decorator count:{}'.format(self.count))
            self.count = self.count + self.interval
            my_func(*args, **kwargs)
        return decorator_wrapper

decorator = myDecorator(2)

@decorator.my_decorator
def print_hello():
    print('hello world')

print_hello()
print_hello()
print_hello()
```

結果

```
# python3 main.py
decorator count:0
hello world
decorator count:2
hello world
decorator count:4
hello world
```
## クラスの変数を保持するデコレータの説明
クラスの生成時に呼び出される初期化関数```___init___```内でカウントとインターバルを初期化して、関数を実行するデコレータ関数```decorator_wrapper```でカウントアップするという処理をしています。

# 関数を保持するデコレータ
## 関数を保持するデコレータの例
クラスに自分の関数を持たせて任意のタイミングと順番で実行することができます。
例では、デコレータに関数を実行する順番を渡して後で実行させています。


``` python:main.py
class myDecorator(object):
    def __init__(self):
        self.func_list=[]
    def my_decorator(self, index):
        def func_decorator(my_func):
            self.func_list.insert(index, my_func)
            return my_func
        return func_decorator
    def print_exec(self):
        for func in self.func_list:
            func()        

decorator = myDecorator()

@decorator.my_decorator(1)
def print_hello_world():
    print('hello world')

@decorator.my_decorator(0)
def print_hello_python():
    print('hello python')

if __name__ == "__main__":
    decorator.print_exec()
```

結果

```
# python3 main.py
hello python
hello world
```

## 関数を保持するデコレータの説明
クラスの中に関数を格納するリストを生成しています。デコレータ自身に指定した値は```def my_decorator(self, index):```のindexに入っており、デコレータを適用した関数は```def func_decorator(my_func):```のmy_funcに入っているため、生成したリストの指定したインデックスに入れています。
最後にリストに入れた関数をクラス関数内で呼び出して順番に標準出力をしています。


# 色々な使い方
## 指定した時間から順番にインターバルを入れて関数を実行する
毎時～分に動作開始して、・・・の関数を×××分おきに実行するなどの処理もデコレータとスケジュールを使えば簡単に使用できます。
例では21:08に実行開始して、まずprint_hello_pythonを実行しています。その後、2秒のインターバル後にprint_hello_worldを実行して、1秒のインターバル後に21:08まで待つ処理をしています。

``` python:main.py
import time
import schedule

class myDecorator(object):
    def __init__(self, start_time):
        self.func_list=[]
        self.start_time = start_time
    def my_decorator(self, index, interval):
        def func_decorator(my_func):
            func_data = [interval, my_func]
            self.func_list.insert(index, func_data)
            return my_func
        return func_decorator
    def print_exec(self):
        for func_data in self.func_list:
            func_data[1]()
            print('sleep time:{}'.format(func_data[0]))
            time.sleep(func_data[0])
    def do_schedule(self):
        schedule.every().day.at(self.start_time).do(self.print_exec)
        while True:
            print("----schedule pending----")
            schedule.run_pending()
            time.sleep(10)

decorator = myDecorator("21:08")

@decorator.my_decorator(1, 1)
def print_hello_world():
    print('hello world')

@decorator.my_decorator(0, 2)
def print_hello_python():
    print('hello python')

if __name__ == "__main__":
    decorator.do_schedule()
```

結果

```
# python3 main.py
----schedule pending----
----schedule pending----
----schedule pending----
hello python
sleep time:2
hello world
sleep time:1
----schedule pending----
```

# おわりに
クラスのデコレータを色々見ていきました。関数のデコレータ以上にぱっと見は何をしているのか分かり難いため取っつきにくいです。
しかし、関数のデコレータ以上に応用すれば色々なことができました。
自分だけで開発する分には特に必要なものではないかもしれませんが、複数人で開発する場合や見やすいソースを作るために重宝できると思いました。