Note that Python decorators should have wraps

Python decorators are difficult to understand at first. And even if you think you understand it, it's often not a best-practice implementation. Meanwhile, what is functools.wraps that I didn't know until now?

Official documentation

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

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

This is update when defining the wrapper function_wrapper()Is a convenience function that calls as a function decorator.
This is partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)Is equivalent to

Why? If you look at this, it's a mess, but ** Function names and Docstrings will be decorators ** I wonder if that means

Actually see the movement

Test code

def hoge_decorator(f):
    def hoge_wrapper(*args, **kwargs):
        """It's the decorator Docstring"""
        print("It's a decorator")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """It's the Docstring of the decorating function"""
    print("This is the deco function!")

if __name__ == '__main__':
    hoge_function()

result

It's a decorator
This is the deco function!

The one you often see. There is no particular problem with the movement.

But ...


def hoge_decorator(f):
    def hoge_wrapper(*args, **kwargs):
        """It's the decorator Docstring"""
        print("It's a decorator")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """It's the Docstring of the decorating function"""
    print("This is the deco function!")

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

result

It's a decorator
This is the deco function!
hoge_wrapper
It's the decorator Docstring

That? It's funny, isn't it? The hidden attribute output by print is a decorator.

The problem with this is that doctest doesn't work. (I think there are many unit test people ...) Besides, normal operation may not be a problem, but the name is not actually the name of the function that is running. I don't know what you're talking about, but ... it's a development.

functools.wraps

Therefore This is where ** functools.wraps ** comes into play. Let's use it immediately.

import functools

def hoge_decorator(f):
    @functools.wraps(f)
    def hoge_wrapper(*args, **kwargs):
        """It's the decorator Docstring"""
        print("It's a decorator")
        return f(*args, **kwargs)
    return hoge_wrapper

@hoge_decorator
def hoge_function():
    """It's the Docstring of the decorating function"""
    print("This is the deco function!")

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

result

It's a decorator
This is the deco function!
hoge_function
It's the Docstring of the decorating function

Sounds good.

doctest

The last is doctest. By the way, if you do not normally test and if you pass the test, there will be no log, so add -v as a parameter. Also, the decorator test is run, so the decorator Docstring has been removed.

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):
    """It's the Docstring of the decorating function
    >>> hoge_function(2)
    4
    """
    return n ** 2
if __name__ == '__main__':
    doctest.testmod()

result

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

After all the test is not running.

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):
    """It's the Docstring of the decorating function
    >>> hoge_function(2)
    4
    """
    return n ** 2
if __name__ == '__main__':
    doctest.testmod()

result

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.

The test passed properly.

Recommended Posts

Note that Python decorators should have wraps
The attitude that programmers should have (The Zen of Python)
Note that it supports Python 3
[python] Script that (should) update pwsh
Note: Python
Python note
Note: Python Decorator
Python programming note
[Python] Learning Note 1
Python study note_004
Note that the Pandas loc specifications have changed
Python study note_003
Note that Python list comprehensions are always confusing
About python decorators
[Note] openCV + python
About Python decorators
Python beginner's note
Dynamically import / reload modules that have changed in Python
Note that writing like this with ruby is writing like this with python
Python3 comprehension (List, dictionary) that I have seen somewhere
[Note] future sentence ~ Python ~
[Note] File reading ~ Python ~
Note to daemonize python
Note: python Skeleton Nya
Python basic grammar note (4)
Python basic grammar note (3)
Python Tkinter Primer Note
Consideration for Python decorators of the type that passes variables
Note links that may be useful when using Python, Selenium2