6.10. Decorator Arguments
Used for passing extra configuration to decorators
Use more one level of nesting
>>> 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 myfunction():
... ...
>>>
>>>
>>> myfunction()
6.10.1. Example
>>> def translate(lang='en'):
... TRANSLATION = {
... 'Hello': {'en': 'Hello', 'pl': 'Cześć', 'ru': 'Привет'},
... 'Goodbye': {'en': 'Goodbye', 'pl': 'Pa', 'ru': 'Пока'},
... }
... def decorator(func):
... def wrapper(*args, **kwargs):
... result = func(*args, **kwargs)
... i18n = TRANSLATION.get(result, result)
... return i18n.get(lang, result) if type(i18n) else i18n
... return wrapper
... return decorator
>>>
>>>
>>> @translate(lang='en')
... def say_hello():
... return 'Hello'
>>>
>>> say_hello()
'Hello'
>>>
>>>
>>> @translate(lang='pl')
... def say_hello():
... return 'Hello'
>>>
>>> say_hello()
'Cześć'
6.10.2. Use Case - 1
>>>
... @setup(...)
... @teardown(...)
... def test():
... ...
6.10.3. Use Case - 2
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
6.10.4. Use Case - 3
Timeout (SIGALRM)
>>> from signal import signal, alarm, SIGALRM
>>> from time import sleep
>>>
>>>
>>> def timeout(seconds=1, 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)
... def countdown(n):
... for i in reversed(range(n)):
... print(i)
... sleep(1)
... print('countdown finished')
>>>
>>>
>>> countdown(10)
9
8
7
Timeout
Note
Note to Windows users.
Implementation of subprocess.Popen._wait()
>>>
... def _wait(self, timeout):
... """Internal implementation of wait() on Windows."""
... if timeout is None:
... timeout_millis = _winapi.INFINITE
... else:
... timeout_millis = int(timeout * 1000)
... if self.returncode is None:
... # API note: Returns immediately if timeout_millis == 0.
... result = _winapi.WaitForSingleObject(self._handle,
... timeout_millis)
... if result == _winapi.WAIT_TIMEOUT:
... raise TimeoutExpired(self.args, timeout)
... self.returncode = _winapi.GetExitCodeProcess(self._handle)
... return self.returncode
6.10.5. Use Case - 4
Timeout (Timer)
>>> from _thread import interrupt_main
>>> from threading import Timer
>>> from time import sleep
>>>
>>>
>>> def timeout(seconds=1.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.0)
... print('countdown finished')
>>>
>>>
>>> countdown(10)
9
8
7
Traceback (most recent call last):
TimeoutError: Timeout
6.10.6. Use Case - 5
File settings.py
:
>>> BASE_URL = 'https://python3.info'
File utils.py
:
>>> from http import HTTPStatus
>>> import httpx
>>>
>>>
>>> def _request(url, method='GET'):
... url = BASE_URL + url
... resp = httpx.request(url, method)
... if resp.staus_code != HTTPStatus.OK:
... raise ConnectionError
... return resp
>>>
>>>
>>> def get(url):
... def decorator(func):
... def wrapper():
... resp = _request(url)
... return func(resp.json())
... return wrapper
... return decorator
File main.py
:
>>> @get('/users/')
... def get_users(data: list[dict] = None) -> list['User']:
... ...
>>>
>>>
>>> users = get_users()
6.10.7. 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 Arguments Syntax
# - Difficulty: easy
# - Lines: 5
# - Minutes: 3
# %% English
# 1. Define decorator `result`
# 2. Decorator should take `a` and `b` as arguments
# 2. Define `wrapper` with `*args` and `**kwargs` parameters
# 3. Wrapper should call original function with its original parameters,
# and return its value
# 4. Decorator should return `wrapper` function
# 5. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj dekorator `result`
# 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. 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(result), \
'Create result() function'
>>> assert isfunction(result(1, 2)), \
'result() should take two positional arguments'
>>> assert isfunction(result(a=1, b=2)), \
'result() should take two keyword arguments: a and b'
>>> assert isfunction(result(a=1, b=2)(lambda: ...)), \
'result() should return decorator which can take a function as arg'
>>> @result(a=1, b=2)
... def echo(text):
... return text
>>> echo('hello')
'hello'
"""
# type: Callable[[int,int], Callable]
def result():
...
# %% 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 Arguments Staff
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Create decorator `can_login`
# 2. To answer if person is staff check field:
# `is_staff` in `users: list[dict]`
# 3. Decorator will call decorated function, only if all users
# has field with specified value
# 4. Field name and value are given as keyword arguments to decorator
# 5. If user is not a staff:
# raise `PermissionError` with message `USERNAME is not a staff`,
# where USERNAME is user's username
# 6. Run doctests - all must succeed
# %% Polish
# 1. Stwórz dekorator `can_login`
# 2. Aby odpowiedzieć czy osoba jest staffem sprawdź pole:
# `is_staff` in `users: list[dict]`
# 3. Dekorator wywoła dekorowaną funkcję, tylko gdy każdy członek
# grupy ma pole o podanej wartości
# 4. Nazwa pola i wartość są podawane jako argumenty nazwane do dekoratora
# 5. Jeżeli użytkownik nie jest staffem:
# podnieś `PermissionError` z komunikatem `USERNAME is not a staff`,
# gdzie USERNAME to username użytkownika
# 6. 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(can_login), \
'Create can_login() function'
>>> assert isfunction(can_login('field', 'value')), \
'can_login() should take two positional arguments'
>>> assert isfunction(can_login(field='field', value='value')), \
'can_login() should take two keyword arguments: field and value'
>>> assert isfunction(can_login('field', 'value')(lambda: ...)), \
'can_login() should return decorator which can take a function'
>>> group1 = [
... {'is_staff': True, 'username': 'mwatney'},
... {'is_staff': True, 'username': 'mlewis'},
... {'is_staff': True, 'username': 'rmartinez'},
... ]
...
>>> group2 = [
... {'is_staff': False, 'username': 'avogel'},
... {'is_staff': True, 'username': 'bjohanssen'},
... {'is_staff': True, 'username': 'cbeck'},
... ]
>>> @can_login(field='is_staff', value=True)
... def login(users):
... users = ', '.join(user['username'] for user in users)
... return f'Logging-in: {users}'
>>> login(group1)
'Logging-in: mwatney, mlewis, rmartinez'
>>> login(group2)
Traceback (most recent call last):
PermissionError: avogel is not a staff
>>> @can_login(field='is_staff', value='yes')
... def login(users):
... users = ', '.join(user['username'] for user in users)
... return f'Logging-in: {users}'
>>> login(group1)
Traceback (most recent call last):
PermissionError: mwatney is not a staff
>>> login(group2)
Traceback (most recent call last):
PermissionError: avogel is not a staff
"""
# type: Callable[[str,str], Callable]
def can_login(field, value):
def decorator(func):
def wrapper(users):
return func(users)
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 Arguments TypeCheck
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% English
# 1. Create decorator function `typecheck`
# 2. Decorator checks return type only if `check_return` is `True`
# 3. Run doctests - all must succeed
# %% Polish
# 1. Stwórz dekorator funkcję `typecheck`
# 2. Dekorator sprawdza typ zwracany tylko gdy `check_return` jest `True`
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - https://docs.python.org/3/howto/annotations.html
# - `inspect.get_annotations()`
# - `function.__code__.co_varnames`
# - `dict(zip(...))`
# - `dict.items()`
# - `dict1 | dict2` - merging dicts
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> from inspect import isfunction, isclass
>>> assert isfunction(typecheck), \
'Create typecheck() function'
>>> assert isfunction(typecheck(True)), \
'typecheck() should take one positional arguments'
>>> assert isfunction(typecheck(check_return=True)), \
'typecheck() should take one keyword arguments: check_return'
>>> assert isfunction(typecheck(check_return=True)(lambda: ...)), \
'typecheck() should return decorator which can take a function'
>>> @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
>>> @typecheck(check_return=True)
... def echo(a: str, b: int, c: float = 0.0) -> bool:
... return str(a * b)
>>>
>>> echo('one', 1, 1.1)
Traceback (most recent call last):
TypeError: "return" is <class 'str'>, but <class 'bool'> was expected
>>> @typecheck(check_return=False)
... def echo(a: str, b: int, c: float = 0.0) -> bool:
... return str(a * b)
>>>
>>> echo('one', 1, 1.1)
'one'
"""
from inspect import get_annotations
# type: Callable[[Callable], Callable]
def typecheck(func):
annotations = get_annotations(func)
def merge(args, kwargs) -> dict:
args = dict(zip(annotations, args))
return args | kwargs
def check(argname, argvalue):
argtype = type(argvalue)
expected = annotations[argname]
if argtype is not expected:
raise TypeError(f'"{argname}" is {argtype}, '
f'but {expected} was expected')
def wrapper(*args, **kwargs):
arguments = merge(args, kwargs).items()
for argname, argvalue in arguments:
check(argname, argvalue)
result = func(*args, **kwargs)
check('return', result)
return result
return wrapper