Help us understand the problem. What is going on with this article?

Pythonのデコレータにはwrapsをつけるべきという覚え書き

More than 1 year has passed since last update.

Pythonのデコレータは最初理解するのが難しい。
そして理解したと思っていても実はベストプラクティスな実装ではないという事がしばしばある。
そんな中私が今まで知らなかった functools.wraps とは。

公式ドキュメント

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

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)(原文)

これはラッパー関数を定義するときに update_wrapper() を関数デコレータとして呼び出す便宜関数です。
これは partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) と等価です。

Why?
これだけみるとなんのこっちゃですが、ようするに
関数の名前やDocstringがデコレータになっちゃうよ
ということかな

実際に動きを見る

テストコード

def hoge_decorator(f):
    def hoge_wrapper(*args, **kwargs):
        """デコレータのDocstringだよ"""
        print("デコレータだよ")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """デコってる関数のDocstringだよ"""
    print("これがデコってる関数だ!")

if __name__ == '__main__':
    hoge_function()

結果

デコレータだよ
これがデコってる関数だ!

よく見るやつ。
動きは特に問題ない。

だが・・・

def hoge_decorator(f):
    def hoge_wrapper(*args, **kwargs):
        """デコレータのDocstringだよ"""
        print("デコレータだよ")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """デコってる関数のDocstringだよ"""
    print("これがデコってる関数だ!")

if __name__ == '__main__':
    hoge_function()
    print(hoge_function.__name__)
    print(hoge_function.__doc__)

結果

デコレータだよ
これがデコってる関数だ!
hoge_wrapper
デコレータのDocstringだよ

あれれー?おっかしいぞー?
printで出力された隠し属性がデコレータになっていますね。

これで何が問題かというと、doctestが動かなくなります。(unittestの人も多いと思いますが…)
他にも、通常動作は問題ないかもだけど名称とかが実は動かしている関数の名前じゃないという、
何を言ってるのかわからないが…的な展開になります。

functools.wraps

そこで
functools.wrapsの出番です。
早速使ってみましょう。

import functools

def hoge_decorator(f):
    @functools.wraps(f)
    def hoge_wrapper(*args, **kwargs):
        """デコレータのDocstringだよ"""
        print("デコレータだよ")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """デコってる関数のDocstringだよ"""
    print("これがデコってる関数だ!")

if __name__ == '__main__':
    hoge_function()
    print(hoge_function.__name__)
    print(hoge_function.__doc__)

結果

デコレータだよ
これがデコってる関数だ!
hoge_function
デコってる関数のDocstringだよ

いいですね。

doctest

最後はdoctest。
ちなみに通常だとテストをしない場合とテストにパスした場合はなにもログがでないので -v をパラメータとしてつけます。
また、デコレータのテストも走るのでデコレータのDocstringは削除してあります。

  • wraps無し
import functools
import doctest

def hoge_decorator(f):
    def hoge_wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function(n):
    """デコってる関数のDocstringだよ
    >>> hoge_function(2)
    4
    """
    return n ** 2
if __name__ == '__main__':
    doctest.testmod()

結果

3 items had no tests:
    __main__
    __main__.hoge_decorator
    __main__.hoge_function
0 tests in 3 items.
0 passed and 0 failed.
Test passed.

やはりテストが走ってないですね。

  • wraps有り
import functools
import doctest

def hoge_decorator(f):
    @functools.wraps(f)
    def hoge_wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function(n):
    """デコってる関数のDocstringだよ
    >>> hoge_function(2)
    4
    """
    return n ** 2
if __name__ == '__main__':
    doctest.testmod()

結果

Trying:
    hoge_function(2)
Expecting:
    4
ok
2 items had no tests:
    __main__
    __main__.hoge_decorator
1 items passed all tests:
   1 tests in __main__.hoge_function
1 tests in 3 items.
1 passed and 0 failed.
Test passed.

ちゃんとテストが通りました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした