【python】今度こそ理解できるデコレータの動き①引数を受け取らないデコレータ

# はじめに
僕は全然デコレータの動きを覚えることができませんでした。
しかし、ようやく他の人に説明できると思えるようになったので、簡単な例を使って段階的に動きを見ていきます。

この記事では以下の主な3種類のデコレータの動きのうち①について書きます。
（1つの記事で全部紹介しようと思いましたが、体力が尽きました。）

**①デコレータ使用時に引数を受け取らないパターン**

```py
def deco1(func):
  # 処理
  return decooo1

@deco1
def foo():
  # 処理
  return foooo

foo()
```

**②デコレータ使用時に引数を受け取るパターン**

```py
def deco2(boo):
  # 処理
  return decooo2

@deco2(val, arg1=val2)
def foo2():
  # 処理
  return foooo2

foo2()
```

**③デコレータがクラスで実装されているパターン**

```py
class deco3:
  # 処理

@deco3(arg=val)
def foo3():
  # 処理
  return foooo3

foo3()
```

# デコレータの基本的な動き
「はじめに」で書いたようにいくつかの書き方がありますが、基本的な動きは同じです。

1. @の直後に置かれたcallableオブジェクト[^1]に次の行で定義している関数を引数として渡して呼び出す
1. @の次の行で定義している関数名に1で呼び出した返り値を代入する

文字だけだと訳がわからないので、段階的に説明を進めていきます。
なお、以降`deco`は関数で定義されたデコレータとします。

###ステップ1:`@deco`の次の行に関数を定義すると、その時点で`deco`関数が実行される

```py
def deco(func):
    print('deco excecuted')

@deco
def test():
    print('test')
```
[paiza.ioで実行してみる](https://paiza.io/en/projects/new?language=python3)(↑のコードをコピーして遷移先で貼り付けて実行してください)
以下のように出力されました。
`deco excecuted`
つまり、`deco`関数が実行されました。

ここまでで以下のことがわかりました。
`@deco`の次の行に関数を定義すると

* その時点で`deco`関数が実行される

簡単ですね。


### ステップ2:`@deco`の次の行に定義した関数名には`deco`関数の返り値が代入される


```py
def deco(func):
    return 1

@deco
def test():
    print('test')

print(test)
```
[paiza.ioで実行してみる](https://paiza.io/en/projects/new?language=python3)(↑のコードをコピーして遷移先で貼り付けて実行してください)
`1`が出力されました。つまりデコレータのせいで変数testは関数ではなく、整数`1`を保持するようになりました。

ここまでで以下のことがわかりました。
`@deco`の次の行に関数を定義すると

* その時点で`deco`関数が実行される
* `@deco`の次の行で定義された関数名には`deco`関数の返り値が格納される


###ステップ3:`deco`関数の引数には`@deco`直後で定義された関数が渡される

```py
def deco(func):
    func()
    return 1

@deco
def test():
    print('test')
```
[paiza.ioで実行してみる](https://paiza.io/en/projects/new?language=python3)(↑のコードをコピーして遷移先で貼り付けて実行してください)
以下のように出力されました。
`test`
`test()`とは書いていないのに、testが出力されました。これは`deco`関数の引数`func`に`test`関数が代入され、`func()`により`test`関数が実行されているためです。

ここまでで以下のことがわかりました。
`@deco`の次の行に関数を定義すると

* `deco`関数が実行される
* `deco`関数の返り値は`@deco`の次の行で定義された関数名に代入される
* `deco`関数の引数には`@deco`の次の行で定義された関数が入っている

### ステップ4：ステップ１〜３を組み合わせてデコレータを活用してみる
ここまで理解できればもう何も怖くありません。①のパターンのデコレータに関する必要な知識はこれだけです。
もしここでつまずく場合は、`*args`と`**kwargs`などを復習してみてください。

ここでは名前を引数として受け取るとその人を紹介する`introduce`関数を定義します。
そして、もしその人が兄弟であればその人を紹介する前に「This is my brother.」を出力するデコレータ`brother_deco`を定義します。

```py
brother_list = ("John", "Bob")

def deco(func):
    def replacing_func(*args, **kwargs):
        if kwargs.get("name") in brother_list:
            print(f'{kwargs.get("name")} is my brother.')
        func(*args, **kwargs)
    return replacing_func

@deco
def introduce(name: str):
    print(f"This is {name}.\n")

introduce(name="Bob")
introduce(name="Taro")
```
[paiza.ioで実行してみる](https://paiza.io/en/projects/new?language=python3)
以下のように出力されました。
`Bob is my brother.`
`This is Bob.`

`This is Taro.`

つまり、`@deco`の次の行に`introduce`が定義されているので、ステップ1より、この時点で`deco`関数が実行されます。
ステップ2より`deco`関数は引数として`introduce`を受け取るので、`deco`関数内の`func`を`introduce`に置き換えて考えることができます。
`deco`関数は`replacing_func`関数を返すので、ステップ3より`introduce`という関数名には`replacing_func`が代入されます。

そして最後から2行目で`introduce(name=="Bob")`が実行されています。
`replacing_func`関数のkwargs={'name': 'Bob'}より、`kwargs.get("name") in brother_list `はTrueです。
したがって、`Bob is my brother`が出力されます。
最後に`func(*args, **kwargs)`により、`This is Bob`が出力されました。

# 最後に
もし理解できなかった、間違っているなどありましたらご指摘していただけると助かります。
ステップ1~3までは簡単かと思いますが、ステップ4になると\*args, \*\*kwargsも相まってややこしくなりますが、一つずつ処理を追っていけば理解できると思います。

今度は②、③のパターンの記事も作ろうと思います。


[^1]: [callableの説明](https://docs.python.org/ja/3/library/functions.html#callable)