7.6. Decorator Arguments¶
7.6.1. Rationale¶
- Decorator:
@mydecorator(a, b) def myfunction(*args, **kwargs): pass
- Is equivalent to:
myfunction = mydecorator(a, b)(myfunction)
7.6.2. Syntax¶
Definition:
def mydecorator(a=1, b=2):
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return mydecorator
Decoration:
@mydecorator(a=0)
def myfunction():
...
Usage:
myfunction()
7.6.3. Example¶
def run(lang='en'):
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
@run(lang='en')
def hello(name):
return f'My name... {name}'
hello('José Jiménez')
# 'My name... José Jiménez'
7.6.4. Use Cases¶
Deprecated:
import warnings
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}'
warnings.warn(message, DeprecationWarning)
return func(*args, **kwargs)
return wrapper
return decorator
@deprecated(removed_in_version=2.0)
def myfunction():
pass
myfunction()
# /home/python/myscript.py:11: DeprecationWarning: Call to deprecated function myfunction in /home/python/myscript.py at line 19
# It will be removed in 2.0
Timeout using signal(SIGALRM)
:
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:
return func(*args, **kwargs)
except TimeoutError:
print(error_message)
finally:
alarm(0)
return wrapper
return decorator
@timeout(seconds=3.0)
def countdown(n):
for i in reversed(range(n)):
print(i)
sleep(1)
print('countdown finished')
countdown(5)
# 4
# 3
# 2
# Sorry, timeout
Timeout using threading.Timer
:
from _thread import interrupt_main
from threading import Timer
from time import sleep
def timeout(seconds=2.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)
def countdown(n):
for i in reversed(range(n)):
print(i)
sleep(1)
print('countdown finished')
countdown(5)
# 4
# 3
# 2
# Traceback (most recent call last):
# TimeoutError: Timeout
7.6.5. Assignments¶
"""
* Assignment: Decorator Arguments Syntax
* Filename: decorator_arguments_syntax.py
* Complexity: easy
* Lines of code: 5 lines
* Time: 5 min
English:
1. Define decorator `mydecorator`
2. Decorator should take `a` and `b` as arguments
2. Define `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. Compare result with "Tests" section (see below)
Polish:
1. Zdefiniuj dekorator `mydecorator`
2. Dekorator powinien przyjmować `a` i `b` jako argumenty
2. Zdefiniuj `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ć funckję `wrapper`
5. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> from inspect import isfunction
>>> assert isfunction(mydecorator)
>>> assert isfunction(mydecorator(a=1, b=2))
>>> assert isfunction(mydecorator(a=1, b=2)(lambda: None))
>>> @mydecorator(a=1, b=2)
... def echo(text):
... return text
>>> echo('hello')
'hello'
"""
"""
* Assignment: Decorator Arguments Astronauts
* Filename: decorator_arguments_astronauts.py
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 min
English:
1. Use data from "Given" 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ą "Tests" (patrz poniżej)
Polish:
1. Użyj kodu z sekcji "Given" (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ą "Tests" (patrz poniżej)
Tests:
>>> 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_astronauts(field='is_astronaut', value=True)
... def launch(crew):
... crew = ', '.join(astro['name'] for astro in crew)
... return f'Launching: {crew}'
>>> 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
>>> @check_astronauts(field='name', value='Melissa Lewis')
... def launch(crew):
... crew = ', '.join(astro['name'] for astro in crew)
... return f'Launching: {crew}'
>>> launch(CREW_PRIMARY)
Traceback (most recent call last):
PermissionError: Jan Twardowski is not an astronaut
>>> launch(CREW_BACKUP)
Traceback (most recent call last):
PermissionError: Mark Watney is not an astronaut
"""
# Given
def check_astronauts(field, value):
def decorator(func):
def wrapper(crew):
return func(crew)
return wrapper
return decorator
"""
* Assignment: Decorator Arguments Type Check
* Filename: decorator_arguments_typecheck.py
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min
English:
1. Use data from "Given" section (see below)
2. Create decorator function `typecheck`
3. Decorator checks return type only if `check_return` is `True`
4. Compare result with "Tests" section (see below)
Polish:
1. Użyj danych z sekcji "Given" (patrz poniżej)
2. Stwórz dekorator funkcję `typecheck`
3. Dekorator sprawdza typ zwracany tylko gdy `check_return` jest `True`
4. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Hints:
* `echo.__annotations__`
Tests:
>>> @typecheck(check_return=True)
... def echo(a: str, b: int, c: float = 0.0) -> bool:
... return bool(a * b)
>>> echo('one', 1)
True
>>> echo('one', 1, 1.1)
True
>>> echo('one', b=1)
True
>>> echo('one', 1, c=1.1)
True
>>> echo('one', b=1, c=1.1)
True
>>> echo(a='one', b=1, c=1.1)
True
>>> echo(c=1.1, b=1, a='one')
True
>>> echo(b=1, c=1.1, a='one')
True
>>> echo('one', c=1.1, b=1)
True
>>> echo(1, 1)
Traceback (most recent call last):
TypeError: "a" is <class 'int'>, but <class 'str'> was expected
>>> echo('one', 'two')
Traceback (most recent call last):
TypeError: "b" is <class 'str'>, but <class 'int'> was expected
>>> echo('one', 1, 'two')
Traceback (most recent call last):
TypeError: "c" is <class 'str'>, but <class 'float'> was expected
>>> echo(b='one', a='two')
Traceback (most recent call last):
TypeError: "b" is <class 'str'>, but <class 'int'> was expected
>>> echo('one', c=1.1, b=1.1)
Traceback (most recent call last):
TypeError: "b" is <class 'float'>, but <class 'int'> was expected
"""
# Given
def decorator(func):
def validate(argname, argval):
argtype = type(argval)
expected = func.__annotations__[argname]
if argtype is not expected:
raise TypeError(f'"{argname}" is {argtype}, but {expected} was expected')
def merge(*args, **kwargs):
args = dict(zip(func.__annotations__.keys(), args))
return kwargs | args # Python 3.9
# return {**args, **kwargs)} # Python 3.7, 3.8
def wrapper(*args, **kwargs):
for argname, argval in merge(*args, **kwargs).items():
validate(argname, argval)
result = func(*args, **kwargs)
validate('return', result)
return result
return wrapper