Pythonのデコレータについて少し調べていたら、メタデータが失われる問題に気づきました🤔 functools.wrapsを使うと関数名やdocstringが保持されるみたい。自分用の備忘録として記事にまとめてみました。デバッグやドキュメント生成時に役立つかもしれません。型注釈も守られるのは知らなかった…。誰かの参考になれば嬉しいです🙏
はじめに
Pythonを使って開発をしていると、「デコレータ」という便利な機能を目にすることがあるかと思います。デコレータは関数に新しい機能を追加(修飾)できる強力なツールですが、そのまま使うと思わぬ落とし穴にはまることがあります。
例えば、
- デコレータのせいで元の関数名やドキュメント文字列(docstring)が上書きされてしまう
- デバッグ時に「どの関数を呼び出しているか分からない」状態に陥る
といった問題です。そこで、本記事では「functools.wraps
」を使った解決方法を紹介します。これを知っておくと、デバッグやドキュメント作成がぐっと楽になりますよ。
デコレータって何?
まずはデコレータの基本を簡単におさらいします。
-
デコレータとは
関数やメソッドを「ラップ」して、新たな機能を追加する仕組みです。引数の前後でログを出したり、実行時間を計測したり、何か特別な処理を挟みたいときなどにとても便利です。 -
基本的な書き方
def デコレータ(func): def wrapper(*args, **kwargs): # 何らかの前処理 result = func(*args, **kwargs) # 何らかの後処理 return result return wrapper
-
使い方
@デコレータ def 任意の関数(...): ...
例:挨拶を拡張する
簡単な例を示します。関数が呼び出される前後に「励ましのメッセージ」を付与してくれるデコレータを作成してみましょう。
def 挨拶を飾る(func):
def wrapper(名前):
print("素敵な一日になりますように!")
func(名前)
print("今日も頑張りましょう!")
return wrapper
@挨拶を飾る
def こんにちは(名前):
"""この関数は名前を受け取って挨拶します"""
print(f"こんにちは、{名前}さん!")
こんにちは("田中")
実行結果:
素敵な一日になりますように!
こんにちは、田中さん!
今日も頑張りましょう!
デコレータの落とし穴:元の関数情報が失われる
便利なデコレータですが、注意しないと元の関数のメタ情報(関数名やdocstring)が書き換えられてしまいます。先ほどの例で検証してみましょう。
print(こんにちは.__name__) # 出力: wrapper
print(こんにちは.__doc__) # 出力: None
-
こんにちは.__name__
が本来はこんにちは
であるはずなのにwrapper
になってしまう -
こんにちは.__doc__
(本来のdocstring)も消えてNone
になっている
ドキュメント生成ツールで関数の説明を自動的に拾えなくなったり、デバッグ時のログで正しい関数名が出なくなったりして困ります。
解決策:functools.wraps
の使用
この問題は標準ライブラリの functools
に用意されている wraps
デコレータを使うだけで、あっさりと解決できます。
使い方
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__
も正しく元の関数の情報を保持できるようになりました。
さらに一歩進んだ使い方:引数や戻り値の型情報を守る
最近ではPythonで型注釈(type hint)を使う人も多いと思います。wraps
を使うと、以下のような情報もきちんとラップしてくれます。
- 引数や戻り値に付与した型アノテーション
- 関数のモジュール情報など
ドキュメント生成ツール(Sphinxやpdocなど)でも活用されることが多いので、型をきちんと書いている人は特にwraps
の利用が大切になってきます。
まとめ
- デコレータは、既存の関数に機能を追加するための便利な仕組み
- ただしそのまま使うと、元の関数の名前やdocstringなどの大事なメタ情報が失われる
-
functools.wraps
を使うだけで、この問題を簡単に解消できる - デバッグやドキュメント生成時の混乱を防ぐためにも、デコレータを書くときは
@wraps(func)
を忘れないようにしよう
参考資料
以上が、デコレータで失われがちなメタ情報を守りながら活用する方法でした。ちょっとしたテクニックですが、コードの可読性やメンテナンス性を格段に上げてくれるので、ぜひ活用してみてください。