3.1. Decorator About

  • Decorator is an object, which takes another object as it's argument

  • Since Python 2.4: PEP 318 -- Decorators for Functions and Methods

  • Since Python 3.9: PEP 614 -- Relaxing Grammar Restrictions On Decorators

  • Decorator can do things before call

  • Decorator can do things after call

  • Decorator can modify arguments

  • Decorator can modify returned value

  • Decorator can avoid calling

  • Decorator can modify globals

  • Decorator can add or change metadata

../../_images/decorator-about-call.png

Decorator is an object (such as function, method or class) that modifies the behavior of another object. It takes a decorated object as input and returns a modified version of that object. Decorators are a powerful feature that allows you to add functionality to an object without modifying its source code.

3.1.1. Syntax

  • func is a reference to function which is being decorated

  • args arbitrary number of positional arguments

  • kwargs arbitrary number of keyword arguments

  • By calling func(*args, **kwargs) you actually run original (wrapped) function with it's original arguments

Decorators are defined using the "@" symbol followed by the name of the decorator. When a function is decorated, the decorator is called with the original object as its argument. The decorator can then modify the behavior of the original object by adding new functionality or modifying its existing behavior.

Here is an example of a simple decorator that adds an additional behavior to a function:

>>> def mydecorator(func):
...     def wrapper():
...         print('before')
...         result = func()  # call the original function
...         print('after')
...         return result
...     return wrapper
>>>
>>> @mydecorator
... def myfunction():
...     print('This is my function.')
>>>
>>> myfunction()
before
This is my function.
after

In this example, the mydecorator function takes a function as input and returns a new function wrapper that prints a trext before and after calling the original function. The @mydecorator syntax is used to apply the decorator to the myfunction function. When myfunction is called, it will now print "before", execute its original code, and then print "after".

3.1.2. Decoration

Syntax:

>>> @mydecorator
... def myfunction(*args, **kwargs):
...     ...

Is equivalent to:

>>> myfunction = mydecorator(myfunction)

3.1.3. Assignments

# %% About
# - Name: Decorator About Syntax
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3

# %% 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

# %% English
# 1. Create decorator `mydecorator`
# 2. Decorator should have `wrapper` with `*args` and `**kwargs` parameters
# 3. Wrapper should call original function with it's original parameters,
#    and return its value
# 4. Decorator should return `wrapper` function
# 5. Run doctests - all must succeed

# %% Polish
# 1. Stwórz dekorator `mydecorator`
# 2. Dekorator powinien mieć `wrapper` z parametrami `*args` i `**kwargs`
# 3. Wrapper powinien wywoływać oryginalną funkcję z jej oryginalnymi
#    parametrami i zwracać jej wartość
# 4. Decorator powinien zwracać funkcję `wrapper`
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Doctests
"""
>>> 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 echo(text):
...     return text

>>> echo('hello')
'hello'
"""

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

# %% Imports

# %% Types
from typing import Callable
mydecorator: Callable[[Callable], Callable]

# %% Data

# %% Result

# %% About
# - Name: Decorator About Usecase
# - Difficulty: easy
# - Lines: 1
# - Minutes: 3

# %% 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

# %% English
# 1. Modify decorator `disable`
# 2. Decorator does not execute function, but raises an exception
#    `PermissionError` with message "Function is disabled"
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zmodyfikuj dekorator `disable`
# 2. Dekorator nie wywołuje funkcji, ale podnosi wyjątek
#    `PermissionError` z komunikatem "Function is disabled"
# 3. Uruchom doctesty - wszystkie muszą się powieść

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

>>> from inspect import isfunction

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

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

>>> @disable
... def echo(text):
...     print(text)

>>> echo('hello')
Traceback (most recent call last):
PermissionError: Function is disabled
"""

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

# %% Imports

# %% Types
from typing import Callable
disable: Callable[[Callable], Callable]

# %% Data

# %% Result
def disable(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper