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

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がデコレータになっちゃうよ**
ということかな
## 実際に動きを見る
テストコード

```py3
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()
```
**結果**

```
デコレータだよ
これがデコってる関数だ！
```
よく見るやつ。
動きは特に問題ない。

だが・・・

```py3

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**の出番です。
早速使ってみましょう。

```py3
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無し

```py3
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有り

```py3
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.
```

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