6.15. Decorator Wraps

  • from functools import wraps

  • @wraps(func)

6.15.1. Problem

  • Decorator will replace function with wrapper

  • All function attributes, such as __doc__ and __name__ will be from decorator

>>> def mydecorator(func):
...     def wrapper(*args, **kwargs):
...         """wrapper docstring"""
...         return func(*args, **kwargs)
...     return wrapper
>>>
>>>
>>> @mydecorator
... def myfunction(x):
...     """myfunction docstring"""
...     print(x)
>>>
>>>
>>> print(myfunction.__name__)
wrapper
>>>
>>> print(myfunction.__doc__)
wrapper docstring

6.15.2. Solution: Manual

>>> def mydecorator(func):
...     def wrapper(*args, **kwargs):
...         """wrapper docstring"""
...         return func(*args, **kwargs)
...     wrapper.__name__ = func.__name__
...     wrapper.__doc__ = func.__doc__
...     return wrapper
>>>
>>>
>>> @mydecorator
... def myfunction(x):
...     """myfunction docstring"""
...     print(x)
>>>
>>>
>>> print(myfunction.__name__)
myfunction
>>>
>>> print(myfunction.__doc__)
myfunction docstring

6.15.3. Solution: Wraps

>>> from functools import wraps
>>>
>>>
>>> def mydecorator(func):
...     @wraps(func)
...     def wrapper(*args, **kwargs):
...         """wrapper docstring"""
...         return func(*args, **kwargs)
...     return wrapper
>>>
>>>
>>> @mydecorator
... def myfunction(x):
...     """myfunction docstring"""
...     print(x)
>>>
>>>
>>> print(myfunction.__name__)
myfunction
>>>
>>> print(myfunction.__doc__)
myfunction docstring

6.15.4. Class Decorators

>>> def debug(cls):
...     class Wrapper(cls):
...         def __init__(self):
...             print(f'debug: {cls.__name__}')
...     Wrapper.__name__ = cls.__name__
...     Wrapper.__doc__ = cls.__doc__
...     return Wrapper
>>> @debug  # User = debug(User)
... class User:
...     pass
>>> mark = User()
debug: User
>>> print(User.__name__)
User

6.15.5. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Decorator Functools Func
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% English
# 1. Use `functools.wraps` in correct place
# 2. Run doctests - all must succeed

# %% Polish
# 1. Użyj `functools.wraps` w odpowiednim miejscu
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert isfunction(mydecorator), \
'Create mydecorator() function'

>>> assert isfunction(mydecorator(lambda: ...)), \
'mydecorator() should take function as an argument'

>>> @mydecorator
... def hello():
...     '''Hello Docstring'''

>>> hello.__name__
'hello'
>>> hello.__doc__
'Hello Docstring'
"""

from functools import wraps


# type: Callable[[Callable], Callable]
def mydecorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Decorator Functools Args
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% English
# 1. Use `functools.wraps` in correct place
# 2. Run doctests - all must succeed

# %% Polish
# 1. Użyj `functools.wraps` w odpowiednim miejscu
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert isfunction(mydecorator), \
'Create mydecorator() function'

>>> assert isfunction(mydecorator(True)), \
'mydecorator() should take one positional argument'

>>> assert isfunction(mydecorator(happy=True)), \
'mydecorator() should take one keyword argument'

>>> assert isfunction(mydecorator(happy=True)(lambda: ...)), \
'The result of mydecorator() should take function as an argument'

>>> @mydecorator(happy=False)
... def hello():
...     '''Hello Docstring'''
>>> hello.__name__
'hello'
>>> hello.__doc__
'Hello Docstring'
"""

from functools import wraps


# type: Callable[[bool], Callable]
def mydecorator(happy=True):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper

    return decorator


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Decorator Functools Cls
# - Difficulty: easy
# - Lines: 2
# - Minutes: 3

# %% English
# 1. Modify code to restore docstring and name from decorated class
# 2. Run doctests - all must succeed

# %% Polish
# 1. Zmodyfikuj kod aby przywrócić docstring oraz nazwę z dekorowanej klasy
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction, isclass

>>> assert isfunction(mydecorator), \
'Create mydecorator() function'

>>> assert mydecorator(object), \
'mydecorator() should take class as an argument'

>>> assert isclass(mydecorator(object)), \
'The result of mydecorator() should be a class'

>>> @mydecorator
... class Hello:
...     '''Hello Docstring'''
>>> hello = Hello()
>>> hello.__name__
'Hello'
>>> hello.__doc__
'Hello Docstring'
"""


# type: Callable[[Type], Type]
def mydecorator(cls):
    class Wrapper(cls):
        pass

    return Wrapper