Python デコレータ再入門　 ~デコレータは種類別に覚えよう~

Pythonのアットマーク(@)を使った記法であるデコレータについてまとめる。難しいと思われているかもしれないデコレータだが、デコレータの種類(パターン)を意識することが理解の近道だと思ったので今回は種類別にデコレータの簡単な実装例をあげて解説をしていこうと思う。

# 対象者
- Python のデコレータを学んだことがあるがたまに分からなくなる
- デコレータを自作しろと言われたらスラスラかけるか不安

# 知っていると良いこと
- 関数のスコープ
- 第一級関数
- `*args` , `**kwargs` のような記法

# デコレータ関数の種類分け
以下2つの要因でデコレータ関数の種類分けが可能だと思われる。

- 引数を取るか否か
- ラッパー関数を返すか否か

よって組み合わせから計4つのパターンが考えられるが、今回は "引数を取らずラッパー関数を返さないデコレータ"を除いた３つを紹介する。1つ除外する理由は簡単すぎて意味がなさそうだから。

用語の定義として、デコレータを乗せている関数( `@decorator` の下に定義する関数)を **元の関数** と呼ぶことにする(良い名前ありますか？)

# 1. 引数無しデコレータでラッパー関数を返す場合

```python
def args_logger(f):
    def wrapper(*args, **kwargs):
        f(*args, **kwargs)
        print('args: {}, kwargs: {}'.format(args, kwargs))
    return wrapper

@args_logger
def print_message(msg):
    print(msg)

# 以下と等価
'''
def print_message(msg):
    print(msg)
print_message = args_logger(print_message)
'''

print_message('hello')
```

実行結果

```
hello
args: ('hello',), kwargs: {}
```

まず1番理解しやすいものから。 `args_logger` 関数は元の関数の引数情報をprint()するラッパー関数を返す。
つまり `@args_logger` を乗っけた関数は実行の度に引数情報を吐くようになる。
この例のように、返すラッパー関数は `wrapper` という関数名にするのが慣例である。

# 2. 引数ありデコレータでラッパー関数を返さない場合

```python
funcs = []
def appender(*args, **kwargs):
    def decorator(f):
        # args や kwargsの内容によって処理内容を変えたり変えなかったり
        print(args)
        if kwargs.get('option1'):
            print('option1 is True')

        # 元の関数をfuncsに追加
        funcs.append(f)
    return decorator


@appender('arg1', option1=True)
def hoge():
    print('hoge')

@appender('arg2', option2=False)
def fuga():
    print('fuga')

# 以下と等価
'''
def hoge():
    print('hoge')
appender('arg1', option1=True)(hoge)

def fuga():
    print('fuga')
appender('arg2', option2=False)(fuga)
'''

for f in funcs:
    f()
```

実行結果

```
('arg1',)
option1 is True
('arg2',)
hoge
fuga
```
`appender` デコレータは元の関数をfuncsリストにappendする。デコレータ関数に渡された引数によって処理内容を変えたりできる（今回は良い例を思いつかなかったので適当にprintしているだけ)。
注意する点は、デコレータに引数を保つ場合、デコレータ関数内では直接元の関数 `ｆ` を扱えないということだ。代わりに「元の関数を扱う関数」 を定義する。この関数名は慣例として `decorator` という名前を付ける(多分)。
このパターンのデコレータとしてはflaskの `app/Flask.route` 関数が当てはまる。
https://github.com/pallets/flask/blob/3a0ea726bd45280de3eb3042784613a676f68200/flask/app.py#L1222

flaskのように単にコールバック関数を定義するためにこのパターンは使われると思われる。

# 3. 引数ありデコレータでラッパー関数を返す場合

```python
def args_joiner(*dargs, **dkwargs):
    def decorator(f):
        def wrapper(*args, **kwargs):
            newargs = dargs + args  # リストの結合
            newkwargs = {**kwargs, **dkwargs}  # 辞書の結合 (python3.5以上で動く)
            f(*newargs, **newkwargs)
        return wrapper
    return decorator


@args_joiner('darg', dkwarg=True)
def print_args(*args, **kwargs):
    print('args: {}, kwargs: {}'.format(args, kwargs))


# 以下と等価
'''
def print_args(*args, **kwargs):
    print('args: {}, kwargs: {}'.format(args, kwargs))
print_args = args_joiner('darg', dkwarg=True)(print_args)
'''

print_args('arg', kwarg=False)
```

実行結果

```
args: ('darg', 'arg'), kwargs: {'kwarg': False, 'dkwarg': True}
```

1番目の例と2番目の例の合わせ技のような感じでさらに複雑になった。
`args_joiner` デコレータは、元の関数の引数とデコレータの引数を連結させた引数を取る関数を返す関数を返す(引数を連結させるという処理に深い意味はない、ただの例)。「関数を返す関数を返す」と一段回ネストが深くなったことに注意。処理をがんばって追えば何をしているのか分かると思う。

# どの種類のデコレータなのか見抜けるようになろう
3つの例を通してまとめ。
デコレータが何をしているか理解するために、どの種類のデコレータなのかを見抜ければ後は楽である。

例えば一番目の例のように

```python
def hoge_deco(func):
    def wrapper(...):
        ...
    return wrapper
```

このように書かれていたら、 `hoge_deco` デコレータは **引数を取らない** デコレータで **ラッパー関数を返す** やつだと一瞬でわかると良い。判断材料は `hoge_deco` の引数に `func` を取っていることと、 `wrapper` 関数を定義してそれを最後にreturn していることである。

2番目の例のように

```python
def fuga_deco(*args, **kwargs):
    def decorator(f):
        # args, kwargs, fを使って何かする(fをあるリストに加えるなど)
        ...
    return decorator
```

このように書かれていたら、 `fuga_deco` デコレータは **引数を取る** デコレータで **ラッパー関数を返さない** やつだと思おう。判断材料は `fuga_deco` の引数に関数以外の何かを取っていることと、 `wrapper` という名前ではなく、 関数 `f` を引数に取る `decorator` という名前の関数が定義されていてそれを返していることである。このパターンのデコレータはラッパー関数を返さないので元の関数自体はなんの変化もないことに注意。

3番目の例のように

```python
def piyo_deco(*dargs, **dkwargs):
    def decorator(f):
        ...
        def wrapper(*args, **kwargs):
            ...
        return wrapper
    return decorator
```

と書かれていたら、 `piyo_deco` デコレータは **引数を取る** デコレータで **ラッパー関数を返す** やつだと思おう。判断材料は、、、もういいか。
この例のデコレータは例えばどのような物がありますかね。ちょっと思いつきませんがけっこうあると思います。

# デコレータをスラスラ実装できるようになろう

デコレータのコードが理解できれば、逆にデコレータを自分で作ることも楽になってくるはず。今回の3つのどれかのパターンで殆ど書けると思うので、まずはどのパターンを必要としているか考えて、後は機械的に `*arg, **args` とか `def wrapper` とか `def decorator` とか書けるようになればあなたはデコレータマスターと言えましょう。
僕自身デコレータを自分で作ったことはありませんが、この記事を書くことで良い復習になりました。これからバンバンデコレートしていきたい所存！