【Python】デコレータで失われる関数情報を守ろう：`functools.wraps`

Pythonのデコレータについて少し調べていたら、メタデータが失われる問題に気づきました🤔 functools.wrapsを使うと関数名やdocstringが保持されるみたい。自分用の備忘録として記事にまとめてみました。デバッグやドキュメント生成時に役立つかもしれません。型注釈も守られるのは知らなかった…。誰かの参考になれば嬉しいです🙏
## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b8cd9ae5-438c-43fa-a7cf-0df3be2a882d.png)

Pythonを使って開発をしていると、「デコレータ」という便利な機能を目にすることがあるかと思います。デコレータは関数に新しい機能を追加（修飾）できる強力なツールですが、そのまま使うと思わぬ落とし穴にはまることがあります。

例えば、
- デコレータのせいで元の関数名やドキュメント文字列（docstring）が上書きされてしまう  
- デバッグ時に「どの関数を呼び出しているか分からない」状態に陥る

といった問題です。そこで、本記事では「`functools.wraps`」を使った解決方法を紹介します。これを知っておくと、デバッグやドキュメント作成がぐっと楽になりますよ。

---

## デコレータって何？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/833a8e61-53ce-49c7-a0e1-1619c65e6b41.png)

まずはデコレータの基本を簡単におさらいします。

- **デコレータとは**  
  関数やメソッドを「ラップ」して、新たな機能を追加する仕組みです。引数の前後でログを出したり、実行時間を計測したり、何か特別な処理を挟みたいときなどにとても便利です。

- **基本的な書き方**  
  ```python
  def デコレータ(func):
      def wrapper(*args, **kwargs):
          # 何らかの前処理
          result = func(*args, **kwargs)
          # 何らかの後処理
          return result
      return wrapper
  ```

- **使い方**  
  ```python
  @デコレータ
  def 任意の関数(...):
      ...
  ```

### 例：挨拶を拡張する
簡単な例を示します。関数が呼び出される前後に「励ましのメッセージ」を付与してくれるデコレータを作成してみましょう。

```python
def 挨拶を飾る(func):
    def wrapper(名前):
        print("素敵な一日になりますように！")
        func(名前)
        print("今日も頑張りましょう！")
    return wrapper

@挨拶を飾る
def こんにちは(名前):
    """この関数は名前を受け取って挨拶します"""
    print(f"こんにちは、{名前}さん！")

こんにちは("田中")
```

実行結果:
```
素敵な一日になりますように！
こんにちは、田中さん！
今日も頑張りましょう！
```

---

## デコレータの落とし穴：元の関数情報が失われる
便利なデコレータですが、注意しないと元の関数のメタ情報（関数名やdocstring）が書き換えられてしまいます。先ほどの例で検証してみましょう。

```python
print(こんにちは.__name__)  # 出力: wrapper
print(こんにちは.__doc__)   # 出力: None
```

- `こんにちは.__name__` が本来は `こんにちは` であるはずなのに `wrapper` になってしまう  
- `こんにちは.__doc__`（本来のdocstring）も消えて `None` になっている

ドキュメント生成ツールで関数の説明を自動的に拾えなくなったり、デバッグ時のログで正しい関数名が出なくなったりして困ります。

---

## 解決策：`functools.wraps`の使用
この問題は標準ライブラリの `functools` に用意されている `wraps` デコレータを使うだけで、あっさりと解決できます。

### 使い方
```python
from functools import wraps

def 挨拶を飾る(func):
    @wraps(func)  # ← これを付けるだけでOK！
    def wrapper(名前):
        print("素敵な一日になりますように！")
        func(名前)
        print("今日も頑張りましょう！")
    return wrapper

@挨拶を飾る
def こんにちは(名前):
    """この関数は名前を受け取って挨拶します"""
    print(f"こんにちは、{名前}さん！")

print(こんにちは.__name__)  # こんにちは
print(こんにちは.__doc__)   # この関数は名前を受け取って挨拶します
```

ご覧の通り、`こんにちは.__name__`も`こんにちは.__doc__`も正しく元の関数の情報を保持できるようになりました。

---

## さらに一歩進んだ使い方：引数や戻り値の型情報を守る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/db8427b3-8a92-4bfd-80d1-662c0b2f56f4.png)

最近ではPythonで型注釈（type hint）を使う人も多いと思います。`wraps`を使うと、以下のような情報もきちんとラップしてくれます。

- 引数や戻り値に付与した型アノテーション
- 関数のモジュール情報など

ドキュメント生成ツール（Sphinxやpdocなど）でも活用されることが多いので、型をきちんと書いている人は特に`wraps`の利用が大切になってきます。

---

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/47463f0e-3799-4a1c-af8c-562620dec0db.png)

- デコレータは、既存の関数に機能を追加するための便利な仕組み
- ただしそのまま使うと、元の関数の名前やdocstringなどの大事なメタ情報が失われる  
- `functools.wraps`を使うだけで、この問題を簡単に解消できる  
- デバッグやドキュメント生成時の混乱を防ぐためにも、デコレータを書くときは `@wraps(func)` を忘れないようにしよう

---

## 参考資料
- [Python公式ドキュメント: functools.wraps](https://docs.python.org/ja/3/library/functools.html#functools.wraps)  
- [Qiita: デコレータの使い方まとめ](https://qiita.com/tags/decorator)  
- [Sphinxやpdocでのdocstring自動生成](https://pdoc.dev/)  

---

以上が、デコレータで失われがちなメタ情報を守りながら活用する方法でした。ちょっとしたテクニックですが、コードの可読性やメンテナンス性を格段に上げてくれるので、ぜひ活用してみてください。