pythonのデコレータ


pythonのデコレータについて調べてみました。すぐに内容を忘れてしまうので備忘録として書いておきます

# デコレータの分類

デコレータには２つの関数が使われます。一般的に書くと以下のようになります。**実引数**は、具体的な値が入ります。すなわち、関数を呼び出す場合と考えればよいです。**仮引数**は、関数の内容を定義される際に使われるパラメータの意味です。

```
@デコレータ関数(実引数)
def 参照関数(仮引数)
```
ここで、それぞれの関数は引数を取ることができますが、引数の有無により４種類のパターンが考えられます。

- パターン1
```
@デコレータ関数()
def 参照関数()
```

- パターン2
```
@デコレータ関数()
def 参照関数(仮引数)
```

- パターン3
```
@デコレータ関数(実引数)
def 参照関数()
```

- パターン4
```
@デコレータ関数(実引数)
def 参照関数(仮引数)
```

ここでは形式的に引数の有無で4パターンに分類しましたが、 実際にはパターン1,および2 のデコレータ関数に()を追加するとエラーが発生するので、()を省略して書く必要があるようです。すなわち以下のようになります。


- パターン1(改)
```
@デコレータ関数
def 参照関数()
```

- パターン2(改)
```
@デコレータ関数
def 参照関数(仮引数)
```


これらの４パターンについて具体的に関数の記述例を示します。


# パターン1

- デコレータを使った関数の記述
```python
def decorator(func):
    def wrapper():
        print('--start--')
        func()
        print('--end--')
    return wrapper

@decorator
def test():
    print("test was called")

test()
```

- 実行結果
```
--start--
test was called
--end--
```

なお、デコレータは既存の関数を再定義するためのシンタックスシュガーであり、上記のプログラムは以下と同等になります。


- デコレータを使わない関数の記述

```python
def decorator(func):
    def wrapper():
        print('--start--')
        func()
        print('--end--')
    return wrapper

def test():
    print("test was called")

test = (decorator)(test)  # 再定義された関数

test()  # 関数の実行
```

- 実行結果
```
--start--
test was called
--end--
```

# パターン2

- デコレータを使った関数の記述
```python
def decorator(func):
    def wrapper(*args,**kwargs):
        print('--start--')
        func(*args,**kwargs)
        print('--end--')
    return wrapper

@decorator
def test(*args,**kwargs):
    no   = args[0]
    name = kwargs["name"]
    print('no=',no)
    print('name=',name)

test(3,name="tokyo")
```

- 実行結果
```
--start--
no= 3
name= tokyo
--end--
```

この例の場合、デコレータを使わない関数の実行は以下のようになります

```python
(decorator)(test)(3,name="tokyo")
```


# パターン3


- デコレータを使った関数の記述
```python
def decorator(*args,**kwargs):
    no   = args[0]
    name = kwargs['country']
    def wrapper(func):
        print('no=',no)
        print('country=',name)
        print('--start--')
        func()
        print('--end--')
        return func
    return wrapper

no=3
name='japan'

@decorator(no,country=name)
def test():
    print("test was called")
```

- 実行結果
```
no= 3
country= japan
--start--
test was called
--end--
```

この例の場合、デコレータを使わない関数の実行は以下のようになります

```python
(decorator(3,country='japan'))(test)
```


# パターン4

このパターンではデコレータ関数内で**入れ子の関数定義**を１段増やします。

- デコレータを使った関数の記述
```python
def decodecorator(*para,**kwpara):
    no   = para[0]
    name = kwpara['city']
    def decorator(func):
        def wrapper(*args,**kwargs):
            print('no=',no)
            print('city=',name)
            print('--start--')
            func(*args,**kwargs)
            print('--end--')
            return
        return wrapper
    return decorator

no=3
name="tokyo"

@decodecorator(no,city=name)
def test(*para,**kwpara):
    numbr = para[0]
    label = kwpara['food']
    print('numbr=',numbr)
    print('food=',label)

num=7
eatable="sushi"

test(num,food=eatable)
```

- 実行結果
```
no= 3
city= tokyo
--start--
numbr= 7
food= sushi
--end--
```

この例の場合、デコレータを使わない関数の実行は以下のようになります

```python
no=3
name="tokyo"
num=7
eatable="sushi"
(decodecorator(no,city=name))(test)(num,food=eatable)

```

# デコレータ関数の入れ子呼出

上記のパターン4をさらに発展させた例として、デコレータを入れ子にして呼び出す例を示します。この関数は、初期値$n$を与えて、$B*(n+A)$を求めます。足し算をdeco_add、掛け算をdeco_multiplyで行っています。この例では足し算$\rightarrow$掛け算の順に実行されますが、deco_addとdeco_multiplyの順番を逆にすると掛け算$\rightarrow$足し算の順となり $B+(n*A)$が計算されます。

```python
def deco_add(para):
        print("para_a=",para)
        def add(func):
                def wrapper(*args,**kwargs):
                        val = para+func(*args,**kwargs)
                        return val
                return wrapper
        return add

def deco_multiply(para):
        print("para_m=",para)
        def multiply(func):
                def wrapper(*args,**kwargs):
                        val = para*func(*args,**kwargs)
                        return val
                return wrapper
        return multiply

@deco_multiply(3)
@deco_add(5)
def init(para):
        num = para
        return num

n=1
print(init(n))
```

- 実行結果


```
para_m= 3
para_a= 5
18   注： 3*(1+5)が計算されている
```

# まとめ

pythonのデコレータについて記述方法を調べました。誰かの役に立てれば幸いです。


参考資料：https://www.geeksforgeeks.org/decorators-with-parameters-in-python