3.8. Decorator with Arguments

3.8.1. Syntax

Decorator:
@mydecorator(a, b)
def my_function(*args, **kwargs):
    pass
Is equivalent to:
mydecorator = mydecorator(a, b)(my_function)

3.8.2. Definition

def mydecorator(a=1, b=2):
    def decorator(func):

        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

    return decorator


@mydecorator(a=0)
def echo(name):
    print(name)


echo('Mark Watney')
# Mark Watney

3.8.3. Examples

Listing 3.113. Deprecated
def deprecated(removed_in_version=None):
    def decorator(func):
        def wrapper(*args, **kwargs):
            name = func.__name__
            file = func.__code__.co_filename
            line = func.__code__.co_firstlineno + 1
            message = f"Call to deprecated function {name} in {file} at line {line}"
            message += f'\nIt will be removed in {removed_in_version}'

            import warnings
            warnings.warn(message, DeprecationWarning)
            return func(*args, **kwargs)

        return wrapper
    return decorator


@deprecated(removed_in_version=2.0)
def my_function():
    pass


my_function()
# /tmp/my_script.py:11: DeprecationWarning: Call to deprecated function my_function in /tmp/my_script.py at line 19
# It will be removed in 2.0
Listing 3.114. Timeout
from signal import signal, alarm, SIGALRM
from time import sleep


def timeout(seconds=2.0, error_message='Timeout'):
    def on_timeout(signum, frame):
        raise TimeoutError

    def decorator(func):
        def wrapper(*args, **kwargs):
            signal(SIGALRM, on_timeout)
            alarm(int(seconds))

            try:
                func(*args, **kwargs)
            except TimeoutError:
                print(error_message)
            finally:
                alarm(0)

        return wrapper
    return decorator


@timeout(seconds=3.0, error_message='Sorry, timeout')
def countdown(n):
    for i in reversed(range(n)):
        print(i)
        sleep(1)
    print('countdown finished')


if __name__ == '__main__':
    countdown(5)
# 4
# 3
# 2
# Sorry, timeout
Listing 3.115. Timeout
from _thread import interrupt_main
from threading import Timer
from time import sleep


def timeout(seconds=3.0, error_message='Timeout'):
    def decorator(func):
        def wrapper(*args, **kwargs):
            timer = Timer(seconds, interrupt_main)
            timer.start()
            try:
                result = func(*args, **kwargs)
            except KeyboardInterrupt:
                raise TimeoutError(error_message)
            finally:
                timer.cancel()
            return result
        return wrapper
    return decorator


@timeout(seconds=3.0, error_message='Sorry, timeout')
def countdown(n):
    for i in reversed(range(n)):
        print(i)
        sleep(1)
    print('countdown finished')


if __name__ == '__main__':
    countdown(5)
# 4
# 3
# 2
# TimeoutError: Timeout

3.8.4. Assignments

3.8.4.1. Decorator Arguments Astronauts

English
  1. Use data from "Input" section (see below)

  2. Create decorator check_astronauts

  3. To answer if person is an astronaut check field is_astronaut in crew: List[dict]

  4. Decorator will call decorated function, only if all crew members has field with specified value

  5. Both field name and value are given as keyword arguments to decorator

  6. If any member is not an astronaut raise PermissionError and print his first name and last name

  7. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Polish
  1. Użyj kodu z sekcji "Input" (patrz poniżej)

  2. Stwórz dekorator check_astronauts

  3. Aby odpowiedzieć czy osoba jest astronautą sprawdź pole is_astronaut in crew: List[dict]

  4. Dekorator wywoła dekorowaną funkcję tylko wtedy, gdy każdy członek załogi ma pole o podanej wartości

  5. Zarówno nazwa pola jak i wartość są podawane jako argumenty nazwane do dekoratora

  6. Jeżeli, jakikolwiek członek nie jest astronautą, podnieś wyjątek PermissionError i wypisz jego imię i nazwisko

  7. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
CREW_PRIMARY = [
    {'is_astronaut': True, 'name': 'Jan Twardowski'},
    {'is_astronaut': True, 'name': 'Mark Watney'},
    {'is_astronaut': True, 'name': 'Melissa Lewis'}]

CREW_BACKUP = [
    {'is_astronaut': True, 'name': 'Melissa Lewis'},
    {'is_astronaut': True, 'name': 'Mark Watney'},
    {'is_astronaut': False, 'name': 'Alex Vogel'}]


@check(field='is_astronaut', value=True)
def launch(crew):
    print('Launch')


launch(CREW_PRIMARY)
launch(CREW_BACKUP)
Output
"""
>>> launch(CREW_PRIMARY)
Launching Jan Twardowski, Mark Watney, Melissa Lewis

>>> launch(CREW_BACKUP)
Traceback (most recent call last):
    ...
PermissionError: Alex Vogel is not an astronaut
"""

3.8.4.2. Decorator Arguments Type Check

English

Todo

English translation

Polish
  1. Użyj danych z sekcji "Input" (patrz poniżej)

  2. Stwórz dekorator do sprawdzania typów

  3. Dekorator ma sprawdzać typy danych, wszystkich parametrów wchodzących do funkcji

  4. Jeżeli, którykolwiek się nie zgadza, wyrzuć wyjątek TypeError

  5. Dekorator może przyjmować argument check_return: bool

  6. Jeżeli argument jest True to sprawdź również poprawność typu danych zwracanych przez funkcję

  7. Wyjątek ma wypisywać:

    • nazwę parametru

    • typ, który parametr ma (nieprawidłowy)

    • typ, który był oczekiwany

Input
@check_types(check_return=True)
def echo(a: str, b: int, c: float = 0) -> bool:
    print('Function run as expected')
    return a * b


echo('a', 2)
echo('a', 2)
echo('b', 2)
echo(a='b', b=2)
echo(b=2, a='b')
echo('b', b=2)
Hint
echo.__annotations__
# {'a': <class 'str'>, 'b': <class 'int'>, 'c': <class 'float'>, 'return': <class 'bool'>}