Pythonで〇×ゲームのAIを一から作成する　その129　デコレーターとデコレーター式によるラッパー関数の定義

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/18ad5c20101c96aaecae

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/129/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/129/ai.py)  |  AI に関する関数 |
| [test.py](https://github.com/ysgeso/marubatsu/blob/master/129/test.py)  |  テストに関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/129/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/129/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/129/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# デコレータ式によるラッパー関数の定義

前回の記事では、下記のプログラムのような、__ラップする関数を代入する仮引数__ を持ち、__ラッパー関数を定義して返す__ 処理を行う [`create_show_time`](https://qiita.com/ysgeso/items/18ad5c20101c96aaecae#%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E9%96%A2%E6%95%B0%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0) を定義しました。

```python
from datetime import datetime

def create_show_time(func):
    def show_time(*args, **kwargs):
        starttime = datetime.now()
        retval = func(*args, **kwargs)
        endtime = datetime.now()
        print(endtime - starttime)
        return retval
    
    return show_time
```

また、下記のプログラムのように、`create_show_time` を利用して `sum` 関数に処理時間を表示する機能を追加した __ラッパー関数を作成__ して __`sum_show_time` に代入__ しました。

```python
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
```

実行結果

```
0:00:00
30
```

## デコレータとは何か

`create_show_time` のような、ラップする関数を仮引数に代入し、ラッパー関数を定義して返すような高階関数の事を、Python では __デコレーター（decorator）__ と呼びます。ラップする関数の __機能を拡張__ して __飾り付けを行う（decorate）__ 処理を行うことから、デコレーターと名付けられたようです。


なお、デコレーターという、特別な種類の関数があるわけではありません。__デコレーター__ はそのような処理を行う __通常の関数の一種__ にすぎません。

:::note
上記では、デコレータのことを関数と説明しましたが（下記のリンク先にもそのように説明されています）、関数以外にもデコレーターの機能を持つクラスを定義する事もできます。その方法については次回の記事で説明します。

また、関数以外にも、クラスの機能を拡張した、ラッパークラスを定義するデコレーターを作成することもできます。そちらについては本記事で言及します。
:::

デコレーターの用語の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/glossary.html#term-decorator

## ラッパー関数の名前

上記の例では、`sum` 関数のラッパー関数に `sum_show_time` という名前を付けましたが、
`create_show_time` によって作成されるラッパー関数は、ラップする関数と __同じ仮引数__ と __同じ返り値__ を持ち、__元の機能を保持したまま機能を拡張__ するという処理を行います。

このような場合は、ラッパー関数に __ラップする関数と同じ名前を付ける__ ことが良くあります。具体的には、下記のプログラムの 1 行目のように、`sum_show_time` によって作成された __`sum` のラッパー関数__ の返り値を、__`sum` という名前の変数に代入__ します。

このような処理を行うことで、2 行目のように、ラップする関数を使ったプログラムの __記述を全く変えることなく__、機能を拡張したラッパー関数を使った処理が行われるようになるという利点が得られます。

```python
sum = create_show_time(sum)
print(sum([10, 20]))
```

実行結果

```
0:00:00
30
```

なお、この方法には __ラップする関数を利用できなくなる__ という欠点があります。上記の例の場合は、__元の組み込み関数 `sum`__ の処理を __利用することができなくなります__。ラップする関数を __引き続き利用したい場合__ は、ラッパー関数の名前をラップする関数と __異なる名前を付ける__ 必要があります。

## デコレーター式によるラッパー関数の定義

デコレータを利用して、ラップする関数と __同じ名前のラッパー関数を定義__ するという処理は、比較的よく行われます。そこで、そのようなラッパー関数を __デコレーター式__ という構文で __簡潔に定義__ する方法が用意されています。

デコレーター式は __@ で始まる構文__ で、デコレーター式を利用した __ラッパー関数の定義__ は下記のように記述します。これまでのプログラムで __クラスメソッド__ や __静的メソッド__ を定義する際に、__メソッドの定義の直前の行__ に記述した __`@classmethod`__ や __`@staticmethod`__ は Python で良く利用されるデコレータ式です。

```
@デコレータの処理を行う関数名
ラップする関数の定義  
```

@ の後ろには、以下のような性質を持つデコレータの処理を行う関数名を記述します。前回の記事で定義した `create_show_time` などが該当します。

* ラップする関数を代入する __仮引数を 1 つだけ持つ__[^1]
* 返り値として __関数を返す__[^2]

__デコレーター式のすぐ下__ には、__ラップする関数の定義をそのまま記述__ します。例えば、下記は、仮引数 `x` に代入された値に 1 を足した数を返す `add_1` という関数に対して、__デコレーター式__ に __`@create_show_time`__ を記述して、処理時間を計算して表示する機能を追加した __`add_1` という名前のラッパー関数を定義__ するプログラムです。

```python
@create_show_time
def add_1(x):
    return x + 1
```

上記を実行すると、__`add_1` のラッパー関数が作成__ され、__同じ `add_1` という名前の変数に代入__ されます。そのため、__`add_1` を呼び出す__ と、下記のプログラムのように __処理時間が表示される__ ようになります。

```python
print(add_1(1))
```

実行結果

```
0:00:00
2
```

## デコレーター式が行う処理

下記の 1 ~ 3 行目の __デコレーター式と関数の定義__ を実行した際に __行われる処理__ は、下記の __5 ~ 7 行目のプログラムで行われる処理と同じ__ です。

```python
1  @decorator
2  def func(*args, **kwargs): # 関数名や仮引数は何でもよい
3      pass                   # ここで行う処理は何でもよい
4
5  def func(*args, **kwargs): # デコレータ式の下に記述した関数の定義と同じ
6      pass                  
7  func = decorator(func)
```

そのため、下記の 1 ~ 3 行目の __デコレーター式と関数の定義を実行__ すると、__下記 5 ~ 7 行目のプログラムと同じ処理__ が行われされます。5 ~ 7 行目で行われる処理は、先程説明したデコレータである __`create_show_time`__ を呼び出して __`add_1` のラッパー関数を定義__ して、__`add_1` に代入__ する処理です。

```python
1  @create_show_time
2  def add_1(x):
3      return x + 1
4    
5  def add_1(x):
6      return x + 1
7  add_1 = create_show_time(add_1)
```

このように、デコレータ式は、__別の方法で記述__ できるプログラムを、__より簡潔に記述するため__ に __導入された構文__ です。このようなプログラムを __書きやすく__、__わかりやすく記述できるように導入__ された構文のことを __シンタックスシュガー（syntax sugar、糖衣構文）__ と呼びます。Python のシンタックスシュガーには、他にも [list 内包表記](https://qiita.com/ysgeso/items/2a436b3819bef9a6a2b4#%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E5%8C%85%E8%A1%A8%E8%A8%98)などがあります。

なお、シンタックスシュガーは慣れれば便利ですが、__プログラム言語に特有__ のものが多く、__他のプログラミング言語では利用できない場合が多い__ 点に注意する必要があります。そのため、例えば Python 以外のプログラミングを学んだ人にとっては list 内包表記や、デコレーター式を見てもすぐに意味が理解できない可能性が高いでしょう。また、Python で記述したプログラムを __別のプログラム言語に書き直す__ 必要がある場合では、__シンタックスシュガーの記述の書き直しの作業が問題となる__ 場合があります。

デコレーター式の詳細については下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/compound_stmts.html#function-definitions

### デコレーター式が行う処理の補足

厳密には下記のプログラムの 1 ~ 3 行目のデコレーター式が行う処理は、4 ~ 6 行目で行われる処理とは __ほんの少しだけ異なります__。そのことは、上記のリンク先では「だいたい次と等価です」と、「前者のコード（デコレーター式のプログラムの事です）では元々の関数を func という名前へ一時的に束縛することはない」のように説明されています。

```python
1  @create_show_time
2  def add_1(x):
3      return x + 1
4
5  def add_1(x):
6      return x + 1
7  add_1 = create_show_time(add_1)
```

__デコレーター式が行う処理を理解__ する際に、__その違いを意識する必要はありません__ が、その違いを知りたい人がいるかもしれないので以下に説明します。下記の説明の意味がわからない方は読み飛ばしても構いません。

「元々の関数を func という名前へ __一時的に束縛__ する」という説明は、上記の 5 ~ 7 行目で行われる下記の処理の事を意味しています。

* 5、6 行目の __関数の定義を実行__ することで __グローバル名前空間__ に、`add_1` という名前と作成された関数オブジェクトの対応づけが登録される。このような __名前とオブジェクトの対応づけの名前空間への登録__ のことを __オブジェクトを名前に束縛する（bound）と呼ぶ__ ので、この処理によって 5、6 行目に記述されている __元々の関数が `add_1` という名前に束縛__ されたことになる
* 7 行目の処理によって、`add_1` にラッパー関数が代入される。その結果、__元々の関数の `add_1` への束縛が解除__ される。このように、`add_1` への元々の関数の束縛が __すぐに解除される__ ことを、上記の説明では「__一時的に束縛する__」と表記している

5、6 行目で行われる元々の関数を `add_1` という名前に __一時的に束縛するという処理__ は、__`add_1` という名前のラッパー関数を定義__ する際に行う必要がない __無駄な処理__ です。上記の 1 ~ 3 行目の __デコレーター式が行う処理__ では、その __無駄な処理を行わない__ という点で、5 ~ 7 行目が行う処理と異なります。それが、「前者のコード（デコレーター式のプログラム）では元々の関数を func という名前へ一時的に束縛することはない」という説明の意味です。

なお、この無駄な処理が行われるかどうかによって、__最終的な処理の結果は変わらない__ ので、上記の __違いを意識する必要はありません__。

## デコレーター式に関するエラー

先程説明した、__下記の条件を満たさない関数__ をデコレーター式に記述すると __エラーが発生する__ ので、それらのエラーについて簡単に説明します。

* ラップする関数を代入する __仮引数を 1 つだけ持つ__
* 返り値として、__ラッパー関数を返す__

### 仮引数が存在しない場合

下記のプログラムの `decorator_error1` は、__仮引数を一つも持たない__ ので、デコレーター式を実行すると実行結果のように `decorator_error1` には位置引数がない（0 positional argument）にも関わらず実引数が記述されているという意味のエラーが発生します。

```python
def decorator_error1():
    pass

@decorator_error1
def add_1(x):
    return x + 1
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 4
      1 def decorator_error1():
      2     pass
----> 4 @decorator_error1
      5 def add_1(x):
      6     return x + 1

TypeError: decorator_error1() takes 0 positional arguments but 1 was given
```

これは、上記のプログラムは、下記のプログラムと同じ処理を行うからです。

```python
def decorator_error1():
    pass
    
def add_1(x):
    return x + 1
add_1 = decorator_error1(add_1)
```

### 仮引数が 2 つ以上存在する場合

下記のプログラムの `decorator_error2` は、__仮引数を 2 つ持つ__ ので、デコレーター式を実行すると実行結果のように、`decorator_error2` を呼び出す際に仮引数 `g` に対応する位置引数が記述されていないという意味のエラーが発生します。

```python
def decorator_error2(f, g):
    pass

@decorator_error2
def add_1(x):
    return x + 1
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 4
      1 def decorator_error2(f, g):
      2     pass
----> 4 @decorator_error2
      5 def add_1(x):
      6     return x + 1

TypeError: decorator_error2() missing 1 required positional argument: 'g'
```

### 返り値が関数でない場合

下記のプログラムの `decorator_error3` は、仮引数を 1 つ持つ点では問題はありませんが、__返り値が関数でない[^2]__ 点が問題です。ただし、下記のプログラムを実行しても __その時点ではエラーは発生しません__。

```python
def decorator_error3(func):
    return 1

@decorator_error3
def add_1(x):
    return x + 1
```

エラーが発生するのは、下記のプログラムのように  `add_1` を呼び出した場合です。

```python
print(add_1(1))
```

実行結果

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[8], line 1
----> 1 print(add_1(1))

TypeError: 'int' object is not callable
```

__`add_1`__ には、 __`decorator_error3(add_1)` の返り値が代入__ されますが、`decorator_error3` は __常に 1 を返す__ ので、__`add_1` には `1` が代入__ されています。そのため、整数型（int）である `1` は関数のように呼び出すことはできない（is not callable）というエラーが発生します。また、実際に下記のプログラムを実行すると、__`add_1` に `1` が代入__ されていることが確認できます。

```python
print(add_1)
```

実行結果

```
1
```

# デコレータ式の使用上の注意

デコレーターは便利ですが、使用する際には __いくつか気を付ける必要__ があるので、それらについて説明します。

## ラップする関数を利用できなくなる

デコレータ式は、ラップする関数と __同じ名前のラッパー関数を定義__ するので、__ラップする関数を利用できなくなってしまう__ という欠点があります。ラップする関数を __そのままの形で利用し続ける必要がある__ 場合は、__デコレーター式を利用することはできず__、下記のプログラムの 3 行目のような方法で、ラップする関数と __異なる名前の変数__ に作成したラッパー関数を代入する必要があります。

```python
def add_1(x):
    return x + 1
add_1_show_time = create_show_time(add_1)
```

## 定義済の関数に対して利用できない

デコレータ式は __直後の行__ に __ラップする関数の定義を記述__ する必要があるので、既に __定義済の関数__ に対して __利用することはできません__。例えば、`sum` のような __組み込み関数__ や、__他のモジュールで定義された関数__ に対してデコレーター式を利用することはできません。

## docstring などの表示がラッパー関数のものになる

デコレーター式によって定義された __ラッパー関数__ は、__名前はラップする関数と同じ__ になりますが、その __実態__ はデコレーターの関数の中で定義された __ラッパー関数__ です。

そのため、ラッパー関数の docstring を表示した場合に、__ラップする関数__ に記述した __docstring__ ではなく、__ラッパー関数の docstring が表示__ されるなどの __問題が生じます__。

### デコレーター式を実行する前の処理

具体例を挙げて説明します。

まず、__`add_1`__ を、下記のプログラムのように __docstring を記述したプログラム__ として __再定義__ します。

```python
def add_1(x):
    """1 を足して返す
    
    Args:
        x: 加算する値
    
    Returns:
        x + 1 の値
    """

    return x + 1
```

関数の定義に記述した __docstring__ は __組み込み関数 `help` を使って表示__ することができ、`add_1` の docstring は下記のプログラムで表示できます。

```python
help(add_1)
```

実行結果

```
Help on function add_1 in module __main__:

add_1(x)
    1 を足して返す
    
    Args:
        x: 加算する値
    
    Returns:
        x + 1 の値
```

また、__関数が定義された時__ に __def 文で付けられた名前__ は、関数オブジェクトの __`__name__`__ という __特殊属性に代入__ されています。そのため、下記のプログラムで `add_1` の `__name__` 属性を表示すると、実行結果のように add_1 が表示されます。

```python
print(add_1.__name__)
```

実行結果

```
add_1
```

組み込み関数 `help` の詳細については下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/functions.html#help

関数オブジェクトの特殊属性 `__name__` の詳細については下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/datamodel.html#special-writable-attributes

### デコレーター式を実行した後の処理

次に、__デコレーター式を実行した後__ で、上記と同様の処理を行うと、どのように結果が変わるかを説明します。まず、デコレーターである __`create_show_time`__ を、下記のプログラムのように __ラッパー関数 `show_time`__ に対して __docstring を記述__ したプログラムとして __再定義__ します。なお、デコレーターの中で定義する __ラッパー関数の名前__ は、__`wrapper`__ や __`_wrapper`__ という __名前にすることが多い__ ようですが、本記事ではこれまでと同様に `show_time` のままとしました。

```python
def create_show_time(func):
    def show_time(*args, **kwargs):
        """ラップする関数の処理時間を表示するラッパー関数"""
        
        starttime = datetime.now()
        retval = func(*args, **kwargs)
        endtime = datetime.now()
        print(endtime - starttime)
        return retval
    
    return show_time
```

上記を実行後に、下記のプログラムで `@create_show_time` のデコレーター式を使って `add_1` に対するラッパー関数を定義します。

```python
@create_show_time
def add_1(x):
    """1 を足して返す
    
    Args:
        x: 加算する値
    
    Returns:
        x + 1 の値
    """
    
    return x + 1
```

上記のプログラムを実行すると、__`add_1` という名前の変数__ に、`create_show_time` で定義された __ラッパー関数 `show_time` が代入__ されます。その結果、`add_1` には上記のプログラムに記述した __ラップする `add_1` の定義とは異なる関数が代入__ されることになります。

そのため、下記のプログラムで __`help`__ を使って `add_1` の docstring を表示すると、実行結果のように __`show_time` の docstring が表示__ されてしまいます。

```python
help(add_1)
```

実行結果

```
Help on function show_time in module __main__:

show_time(*args, **kwargs)
    ラップする関数の処理時間を表示するラッパー関数
```

同様に `add_1` の `__name__` 属性を表示すると、実行結果のように show_time が表示されます。

```python
print(add_1.__name__)
```

実行結果

```
show_time
```

## `functools.wrap` による問題の修正

`create_show_time` のように、__ラップする関数__ の仮引数、返り値、処理に __変更を加えず__、__ちょっとした機能の追加__ を行うようなラッパー関数を定義する場合は、__docstring や、`__name__` 属性の値__ は、ラッパー関数ではなく、__ラップする関数の情報が表示された方が便利__ です。実際に上記の `help(add_1)` の実行で、`show_time` の docstring が表示されても `add_1` の __使い方を知ることはできません__。

この問題は、__ラッパー関数__ に対して functools モジュールで定義された __`wraps` によるデコレーター式を記述__ することで修正することできます。

`wraps` をデコレーター式に記述する際は、下記のプログラムの 4 行目のように __ラップする関数を実引数に記述して呼び出す__ という形で記述します。

__`@wraps(func)`__ は、その下に定義した関数の docstring や `__name__` 属性の値などを、実引数 __`func` で指定した関数の情報に置き換える__ という機能を持つデコレーター式です。

```python
 1  from functools import wraps
 2
 3  def create_show_time(func):
 4      @wraps(func)
 5      def show_time(*args, **kwargs):
 6          """ラップする関数の処理時間を表示するラッパー関数"""
 7       
 8          starttime = datetime.now()
 9          retval = func(*args, **kwargs)
10          endtime = datetime.now()
11          print(endtime - starttime)
12          return retval
13    
14      return show_time
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from functools import wraps

def create_show_time(func):
    @wraps(func)
    def show_time(*args, **kwargs):
        """ラップする関数の処理時間を表示するラッパー関数"""
        
        starttime = datetime.now()
        retval = func(*args, **kwargs)
        endtime = datetime.now()
        print(endtime - starttime)
        return retval
    
    return show_time
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
+from functools import wrap

def create_show_time(func):
+   @wrap(func)
    def show_time(*args, **kwargs):
        """ラップする関数の処理時間を表示するラッパー関数"""
        
        starttime = datetime.now()
        retval = func(*args, **kwargs)
        endtime = datetime.now()
        print(endtime - starttime)
        return retval
    
    return show_time
```
</details>

:::note
functools は、高階関数に対する様々な処理を行うためのモジュールです。functools の `wraps` の詳細については、下記のリンク先を参照して下さい。
:::

https://docs.python.org/ja/3/library/functools.html#functools.wraps


上記の修正後に、改めて下記のプログラムで `add_1` に対して修正した `create_show_time` を利用してラッパー関数を作成します。

```python
@create_show_time
def add_1(x):
    """1 を足して返す
    
    Args:
        x: 加算する値
    
    Returns:
        x + 1 の値
    """
    
    return x + 1
```

上記の実行後に下記のプログラムで `help` を使って `add_1` の docstring を表示すると、実行結果のように、__再び `add_1` の docstring が表示される__ ようになります。

```python
help(add_1)
```

実行結果

```
Help on function add_1 in module __main__:

add_1(x)
    1 を足して返す
    
    Args:
        x: 加算する値
    
    Returns:
        x + 1 の値
```

同様に `add_1` の `__name__` 属性を表示すると、実行結果のように __再び add_1 が表示される__ ようになります。

```python
print(add_1.__name__)
```

実行結果

```
add_1
```

:::note warn
ラッパー関数が、ラップする関数と __同じような機能を持つ場合__ は、functools モジュールの `wraps` をインポートし、デコレーターの中で定義されたラッパー関数に対して __`@wraps(func)` のデコレータ式を記述すると良い__。
:::

## ラップする関数と無関係な処理を行うデコレーターをデコレーター式に記述できてしまう

デコレーター式は __一般的__ には __ラップする関数の機能を拡張__ したラッパー関数を定義するために利用されますが、下記のプログラムの 1 ~ 3 行目のデコレーター式によるラッパー関数を作成する処理は、5 ~ 7 行目の処理を __別の方法で記述しているにすぎません__。

そのため、下記の __5 ~ 7 行目__ のプログラムを実行した際に __エラーが発生しなければ__、__`decorator`__ が __どのような処理を行う関数であっても__、1 ~ 3 行目のプログラムを実行することで 2、3 行目の `func` のラッパー関数を定義できてしまいます。

```python
1  @decorator
2  def func(*args, **kwargs): # 関数名や仮引数は何でもよい
3      pass                   # ここで行う処理は何でもよい
4
5  def func(*args, **kwargs): # デコレータ式の下に記述した関数の定義と同じ
6      pass                  
7  func = decorator(func)
```

具体的には、先程説明したように、以下の条件を満たす関数であれば、デコレーター式とその下の関数の定義を実行しても __エラーは発生しません__。

* ラップする関数を代入する __仮引数を 1 つだけ持つ__[^1]
* 返り値として __関数を返す__[^2]

例えば、下記の `decorator_sum` は、仮引数 `func` を 1 つだけ持ち、__組み込み関数 `sum` を返す__ ので、その中で __ラッパー関数を定義していません__ が、上記の条件を満たします。

```python
def decorator_sum(func):
    return sum
```

上記の定義後に、下記のプログラムのように __`decorator_sum`__ をデコレーター式に記述して、__`add_1` のラッパー関数を定義__ する処理を実行してもエラーは発生しません。

```python
@decorator_sum
def add_1(x):
    return x + 1
```

上記のプログラムは、下記のプログラムと同じ処理を行います。

```python
add_1 = decorator_sum(add_1)
```

`decorator_sum` は、__仮引数に代入された関数__ とは __全く無関係__ に、__常に `sum` という組み込み関数を返す__ という処理を行うので、__`decorator_sum(add_1)` の返り値は `sum` になります__。従って、上記のプログラムは、下記のプログラムと同じ処理が行われます。

```python
add_1 = sum
```

上記のことから、デコレータ式に __`@decorator_sum` を記述__ すると、その下に定義した関数にどのような処理が記述されていても、__必ず `sum` と同じ処理を行うラッパー関数が定義される__ ことになります。実際に、下記のプログラムのように、`add_1([10, 20])` を実行すると、`sum([10, 20])` と同じ処理が行われ、実行結果のように 30 が表示されます。

```python
add_1([10, 20])
```

実行結果

```
30
```

__一般的__ には、上記のような __ラップする関数と完全に無関係な処理を行う__ ような関数をデコレーター式に __記述することはありません__ が、上記のようなことができてしまうということは頭の片隅にでも入れておいたほうが良いでしょう。

## 実引数と返り値がそのまま使われない場合がある

ラッパー関数の中で、ラッパー関数の __仮引数に代入された値を全く利用せず__ に __ラップする関数を呼び出す__ ようなデコレーターを定義する事ができます。

例えば、下記のプログラムの __`decorator_arg10`__ は、下記のような処理を行うラッパー関数を返します。

* ラップする関数 `func` の __実引数に必ず `10` を記述して呼び出し__、その返り値を返す処理を行う
* その際に、ラッパー関数 `wrapper` の __仮引数 `args` と `kwargs` の値を一切利用しない__

```python
def decorator_arg10(func):
    def wrapper(*args, **kwargs):
        return func(10)
    return wrapper
```

その結果、下記のプログラムのように、__`decorator_10`__ をデコレーター式に記述して `add_1` の __ラッパー関数を定義__ すると、ラッパー関数の __実引数__ にどのような値を記述して呼び出してもその __値は無視され__、ラップする元の関数の `add_1` の __実引数に常に 10 が記述__ して呼び出された場合の処理が行われます。

```python
@decorator_arg10
def add_1(x):
    return x + 1
```

実際に、下記のプログラムを実行すると、実行結果では `add_1` の __実引数の値に関わらず常に 11 が表示__ されます。

```python
print(add_1(1))
print(add_1(100))
```

実行結果

```
11
11
```

同様に、下記のプログラムのようなデコレータを定義してデコレーター式に記述すると、実引数にどのような値を記述しても __必ず 10 が返る__ ようなラッパー関数が作成されます。


```python
def decorator_ret10(func):
    def wrapper(*args, **kwargs):
        return 10
    return wrapper

@decorator_ret10
def add_1(x):
    return x + 1

print(add_1(1))
print(add_1(100))
```

実行結果

```
10
10
```

上記のような、__実引数を完全に無視__ したり、__返り値を特定の値にしてしまう__ ようなデコレータを __定義して利用することはほぼない__ と思いますが、こちらもこのようなことができてしまうことは頭の片隅にでも入れておいたほうが良いでしょう。

なお、以前の記事で紹介した[関数呼び出しの記述の簡略化](https://qiita.com/ysgeso/items/1ce7ecc51ae323b64bee#%E9%96%A2%E6%95%B0%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%AE%E8%A8%98%E8%BF%B0%E3%81%AE%E7%B0%A1%E7%95%A5%E5%8C%96)や、[データー構造の変更](https://qiita.com/ysgeso/items/1ce7ecc51ae323b64bee#%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0%E3%81%AE%E5%A4%89%E6%9B%B4)のように、ラップする関数の __仮引数の一部に特定の値を代入__ にしたり、__返り値に対して何らかの計算を行って返す__ ようなラッパー関数を定義する場合は __実際にあります__。先ほど `functools.wraps` を紹介しましたが、そのような場合は、ラッパー関数の定義の前に  `functools.wraps` を記述しても、__ラップする関数の docstring の内容__ が、__ラッパー関数で行う処理と大きく異なってしまう__ ことになるので、__あまり有用ではないかもしれません__。

なお、この点に関する筆者のデコレーターに対する知識がまだ曖昧なので、詳しい方はコメントで補足して頂ければ助かります。

ここまでの内容をまとめると以下のようになります。

:::note warn
__デコレーター式__ によるラッパー関数の定義の多くは以下のようなものが多い。そのような場合は、__デコレーターの中でラッパー関数を定義__ する際に、__`@wraps(func)`__ のデコレータ式を記述すると良い。

* ラップする関数の仮引数をそのまま利用する
* ラップする関数の返り値をそのまま返す
* 行う機能の拡張はラップする関数の処理を大きく変えるものではない

ただし、上記の条件を満たさないようなデコレーター式を記述してラッパー関数を定義する場合もある点に注意が必要である。
:::

## 複数のデコレーター式の記述

__一つのラップする関数の定義__ に対して、下記のプログラムのように、__複数のデコレーター式を記述__ することができます。このような場合は、__下に記述されたデコレーター式から順番__ に処理が行われます。

```python
@decoratorA
@decoratorB
def func():
    pass
```

具体的には、上記のプログラムでは、下記の処理が行われます。

```python
func = decoratorA(decoratorB(func))
```

詳細を説明すると長くなるので今回の記事ではでは具体的な説明を省略します。

# デコレーター式の種類

__デコレーター式にはいくつかの種類がある__ のでそれらについて簡単に説明します。

## 関数呼び出しを記述するデコレーター式

先程紹介した functools モジュールの `wraps` をデコレーター式に記述する際に、__`@wraps(func)`__ のように、__関数呼び出しをデコレーター式に記述__ しました。

このような記述を行うことができるようにするためには、__デコレーターの関数の定義__ を今回の記事で説明した方法とは __若干異なる方法で行う__ 必要があります。ただし、その方法を説明すると記事がかなり長くなってしまうので、今回の記事では説明しません。また、`wraps` のようなデコレーターを __利用するだけ__ であれば、__どのように定義するかを知る必要はありません__。そのようなデコレータの定義の方法については、必要が生じた場合に説明したいと思います。

なお、そのようにデコレータ式を記述した場合でも、__行われる処理が特に変わるわけではありません__。例えば、下記の 1 ~ 3 行目のプログラムは、5 ~ 7 行目のプログラムと同じ処理を行います。

```python
1  @decorator(arg)
2  def func():
3      pass
4
5  def func():
6      pass
7  func = decorator(arg)(func)
```

<details><summary><strong>先程との違い</strong></summary>

```diff_python
-@decorator
+@decorator(arg)
def func():
    pass

def func():
    pass
-func = decorator(func)
+func = decorator(arg)(func)
```
</details>

先程は 1 行目が __`@decorator`__、7 行目が __`decorator(func)`__ でしたが、上記では 1 行目が __`@decorator(arg)`__、7 行目が __`decorator(arg)(func)`__ のように変わっており、具体的には __`decorator`__ を __`decorator(arg)`__ に __置き換えているだけ__ です。従って、デコレータ式が行う __処理の性質は全く同じ__ です。

## メソッドに対するデコレーター式

これまでは関数の定義に対してデコレーター式を記述しましたが、__メソッドに対しても同様の方法__ でデコレーター式を記述できます。

下記は `A` というクラスの __`add_1`__ というメソッドに対して __`@create_show_time` のデコレータ式を記述__ したプログラムです。

* __2、3 行目__：インスタンスの作成時に、属性 `x` に 1 を代入する
* __6 ~ 8 行目__：属性 `x` の値を 1 加算し、その値を表示する


```python
1  class A:
2      def __init__(self):
3          self.x = 1
4        
5      @create_show_time
6      def add_1(self):
7          self.x += 1
8          print(self.x)
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
class A:
    def __init__(self):
        self.x = 1
        
    @create_show_time
    def add_1(self):
        self.x += 1
        print(self.x)
```
</details>

上記のプログラムを実行後に、下記のプログラムを実行して `add_1` を呼び出すと、実行結果に処理時間が表示されることから、`add_1` が __処理時間を表示__ する機能が追加された __ラッパーメソッドになっている__ ことが確認できます。

```python
a = A()
a.add_1()
```

実行結果

```
2
0:00:00
```

## クラスに対するデコレーター式

クラスに対してもデコレーター式を利用して、クラスの機能を拡張したラッパークラスを定義する事ができます。ただし、下記の公式ドキュメントに「同じ概念がクラスにも存在しますが、あまり使われません」と記述されているように、クラスに対するデコレーター式はあまり使われないようなので、本記事では説明を省略します。

https://docs.python.org/ja/3/glossary.html#term-decorator

クラスに対するデコレーター式の処理については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/compound_stmts.html#class

## クラスによるデコレータの定義

これまでの記事では、デコレーターを関数として定義しましたが、デコレーターをクラスとして定義する事もできます。その説明は次回の記事で行います。

# 今回の記事のまとめ

今回の記事では、デコレーターとデコレーター式を利用したラッパー関数の定義について説明しました。

# 本記事で入力したプログラム

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/129/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |

# 次回の記事

https://qiita.com/ysgeso/items/7e1d948c4b9208ab44fb

[^1]:今回の記事で紹介する `@wraps(func)` のように、デコレーター式に関数呼び出しを記述する場合は、1 つ以上の仮引数を持つ関数を定義する必要があります。その定義の方法については説明が長くなるので今回の記事では省略します
[^2]:厳密には、__関数のように呼び出す__ ことができる __呼び出し可能オブジェクト__（callable object）を返せば良いことになっています。呼び出し可能オブジェクトについては、次回の記事で説明する予定です