【Python】ざっくり見てわかるデコレータの作り方と使い方

## デコレータとは（ざっくり）
これです
`@` がついている子のことをデコレータといいます
デコレーション（装飾）してくれる子です
別名ラッパーと言ったり言わなかったりします
（今回，呼び名自体はなんだっていいので真偽は割愛です）
```Python
@handmade_decorator
def just_func(just_hensu):
    print(just_hensu)
```

## 何ができるの？
関数の前後に他の処理を入れることができます

![やりたいこと.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/0daa2436-c168-ea13-af47-b652ddeeceb4.png)

## どう嬉しいの？
共通の処理を簡潔に適用できます
例えばこんな感じです

- どの関数も 5 回はリトライするようにしたい
- 関数実行後に共通したログを出力したい
- 関数実行前に共通のチェックを入れたい

このようなお悩みを，
関数定義の前に `@` を書いてあげるだけで解決するのがデコレータです


## 今回デコレートする関数の紹介
引数を出力するだけのシンプルな関数です
```Python
# デコレートされる関数
def just_func(just_hensu):
    print(just_hensu)

# 通常通り関数を呼び出す
just_func("通常通りの関数")
```
出力結果はこちらです
```
通常通りの関数
```

## まずは @ を使わない方法
最初は `@` を使いません
後で `@` は出てくるので少々辛抱です

ということでまずはこちらをご覧ください

```Python
# デコレータ
def handmade_decorator(target_func, hensu):
    print("前処理")
    target_func(hensu)
    print("後処理")


# デコレートされる関数
def just_func(just_hensu):
    print(just_hensu)


# デコレーション
handmade_decorator(just_func, "デコレーション")
```
出力結果はこうです
「デコレーション」の前に「前処理」，
後に「後処理」を出力するようにしました
```
前処理
デコレーション
後処理
```

何が起きたかというと，こうです
![簡単な例の説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/12498076-1248-6e47-8bcf-6479d1635163.png)

今回の例ではこうですね
![簡単な例の説明2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/42f518bd-64d9-24d1-519f-4517427bd023.png)
このように，関数の前後に何かしらの処理をいれてくれる
それがデコレータです

## 関数を返すデコレータ
デコレータの中で関数を定義して，その関数を返すパターンです
ポイントは**関数名を変えず**に，**前後に処理を追加**できるところです

```Python
# デコレータ
def handmade_decorator(target_func):
    """関数を定義してその関数を返す"""

    def wrap_func(hensu):
        print("前処理")
        target_func(hensu)
        print("後処理")

    return wrap_func

# デコレートされる関数
def just_func(just_hensu):
    print(just_hensu)

# デコレートされた後
just_func = handmade_decorator(just_func)
just_func("デコレーション")
```

出力例は先ほどと同様です
変わったのは下記です
- デコレータの中で関数を定義してそれを返す
- デコレータから返ってきた関数を変数(`just_func`)に入れる
- 返ってきた関数(`just_func`)を呼び出す

デコレータの中で関数を定義してそれを返す？
何を言ってるのかしら...ということでこちら解説です
![関数を返す例1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/fa792be2-db90-2070-3255-3fa6de01decd.png)

デコレータから返ってきた関数を変数(`just_func`)に入れる？  
返ってきた関数(`just_func`)を呼び出す？
何を言っちゃているの...？ということで解説です

![関数を返す例2_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/df45edb0-6946-fc9c-5af1-7e4c229cb5e9.png)

イメージでいうとこんな感じですね
![関数を返す例3_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294646/d0c3f9c3-0c68-c8a6-fc93-158f07723b7c.png)

ということで元々の `just_func` 関数を，
デコレータ `handmade_decorator` によって装飾し，
変数 `just_func` に返したわけです

ここ大事なのは，
元々の `just_func` と変数 `just_func` は**同姓同名の違う子**ということです


デコレートされる前後で `id` と正体を確認してみます
丸っきり違う子だということがわかりますね
```Python
# デコレートされる前
print(id(just_func)) # 2121628872320
print(just_func) # <function just_func at 0x000001EDFAEF6E80>

# デコレートされた後
just_func = handmade_decorator(just_func)

print(id(just_func)) # 2121628867840
print(just_func) # <function handmade_decorator.<locals>.wrap_func at 0x000001EDFAEF5D00>
```

## @を使ってデコレーション
お待たせしました
`@`の登場です

この`@`を使った書き方は糖衣構文といい，
かっこよく言うとシンタックスシュガーです
先ほどまでの書き方だと書くのが大変ということで，
書きやすくしたのが糖衣構文ってやつですね
飲みやすくするために甘いもので包んだお薬も糖衣っていいますよね
（某xx丸みたいな）

ということで見ていきましょう

```Python
"""シンタックスシュガー"""

# デコレータ
def handmade_decorator(target_func):

    def wrap_func(hensu):
        print("前処理")
        target_func(hensu)
        print("後処理")

    return wrap_func

# デコレートされる関数
@handmade_decorator
def just_func(just_hensu):
    print(just_hensu)

# デコレートされた関数を呼び出す
just_func("デコレーション")
```

出力結果は変わらないので割愛です

これまでと変わったのは以下です
- `just_func` の上に `@handmade_decorator` がついている
- `just_func = handmade_decorator(just_func)` が無くなっている

そしてポイントはまさにここです
```Python
# @をつけるだけでデコレーションできる
@handmade_decorator
def just_func(just_hensu):
    print(just_hensu)

# 既にデコレーションされてるので後はそのまま呼び出すだけ
just_func("デコレーション")
```

## デコレータに引数を渡したい
`@handmade_decorator(arg)` みたいな感じで，
デコレータに引数を渡したい場合です

そのためには，デコレータを返す関数をつくります
下記の例だと `create_decorator` がそれです

`crete_decorator` で `handmade_decorator` を定義して返し，
`handmade_decorator` で `wrap_func` を定義して返す
という流れです

```Python
"""デコレータに引数を渡したい"""

# デコレータを返す関数
def create_decorator(arg):
    # デコレータ
    def handmade_decorator(target_func):

        def wrap_func(hensu):
            print(arg) # 引数でもらってきた arg をここで出力
            print("前処理")
            target_func(hensu)
            print("後処理")

        return wrap_func
    return handmade_decorator


# デコレートされる関数
# デコレータに arg をわたしてます
@create_decorator(arg="デコレータに渡す引数")
def just_func(just_hensu):
    print(just_hensu)

# デコレートされた関数を呼び出す
just_func("デコレーション")
```

出力結果はこうです
```
デコレータに渡す引数
前処理
デコレーション
後処理
```

## 参考
https://www.lifewithpython.com/2016/09/python-decorator-with-arguments.html

https://gihyo.jp/book/2022/978-4-297-12576-9