3.11. Decorate Function
Decorator must return reference to
wrapper
wrapper
is a closure functionwrapper
name is a convention, but you can name it anyhowwrapper
gets arguments passed tofunction
Definition:
>>> def mydecorator(func):
... def wrapper(*args, **kwargs):
... return func(*args, **kwargs)
... return wrapper
Decoration:
>>> @mydecorator
... def myfunction():
... ...
Usage:
>>> myfunction()
3.11.1. Example
>>> def run(func):
... def wrapper(*args, **kwargs):
... return func(*args, **kwargs)
... return wrapper
>>>
>>>
>>> @run
... def hello(name):
... return f'My name... {name}'
>>>
>>>
>>> hello('José Jiménez')
'My name... José Jiménez'
3.11.2. Use Case - 1
Check if file exists, before executing function:
>>> import os
>>>
>>>
>>> def ifexists(func):
... def wrapper(file):
... if os.path.exists(file):
... return func(file)
... else:
... print(f'File {file} does not exist')
... return wrapper
>>>
>>>
>>> @ifexists
... def display(file):
... print(f'Printing file {file}')
>>>
>>>
>>> display('/etc/passwd')
Printing file /etc/passwd
>>>
>>> display('/tmp/passwd')
File /tmp/passwd does not exist
3.11.3. Use Case - 2
Timeit
>>> from time import time
>>>
>>>
>>> def timeit(func):
... def wrapper(*args, **kwargs):
... start = time()
... result = func(*args, **kwargs)
... end = time()
... duration = end - start
... print(f'Duration: {duration}')
... return result
... return wrapper
>>>
>>>
>>> @timeit
... def add(a, b):
... return a + b
>>>
>>>
>>> add(1, 2)
Duration: 0:00:00.000006
3
>>>
>>> add(1, b=2)
Duration: 0:00:00.000007
3
>>>
>>> add(a=1, b=2)
Duration: 0:00:00.000008
3
3.11.4. Use Case - 3
Debug
>>> def debug(func):
... def wrapper(*args, **kwargs):
... function = func.__name__
... print(f'Calling: {function=}, {args=}, {kwargs=}')
... result = func(*args, **kwargs)
... print(f'Result: {result}')
... return result
... return wrapper
>>>
>>>
>>> @debug
... def add(a, b):
... return a + b
>>>
>>>
>>> add(1, 2)
Calling: function='add', args=(1, 2), kwargs={}
Result: 3
3
>>>
>>> add(1, b=2)
Calling: function='add', args=(1,), kwargs={'b': 2}
Result: 3
3
>>>
>>> add(a=1, b=2)
Calling: function='add', args=(), kwargs={'a': 1, 'b': 2}
Result: 3
3
3.11.5. Use Case - 4
Deprecated
>>> import warnings
>>>
>>>
>>> def deprecated(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}'
... warnings.warn(message, DeprecationWarning)
... return func(*args, **kwargs)
... return wrapper
>>>
>>>
>>> @deprecated
... def add(a, b):
... return a + b
>>>
>>>
>>> add(1, 2)
/home/python/myscript.py:11: DeprecationWarning: Call to deprecated function `add` in /home/python/myscript.py at line 19
3.11.6. Use Case - 5
Stacked Decorators
>>> from datetime import datetime
>>> import logging
>>>
>>> logging.basicConfig(
... level='DEBUG',
... format='{asctime}, "{levelname}", "{message}"',
... datefmt='"%Y-%m-%d", "%H:%M:%S"',
... style='{')
>>>
>>> log = logging.getLogger(__name__)
>>>
>>>
>>> def timeit(func):
... def wrapper(*args, **kwargs):
... start = datetime.now()
... result = func(*args, **kwargs)
... end = datetime.now()
... log.info(f'Duration: {end - start}')
... return result
... return wrapper
>>>
>>>
>>> def debug(func):
... def wrapper(*args, **kwargs):
... function = func.__name__
... log.debug(f'Calling: {function=}, {args=}, {kwargs=}')
... result = func(*args, **kwargs)
... log.debug(f'Result: {result}')
... return result
... return wrapper
>>>
>>>
>>> @timeit
... @debug
... def add(a, b):
... return a + b
>>>
>>>
>>> add(1, 2)
"1969-07-21", "02:56:15", "DEBUG", "Calling: function='add', args=(1, 2), kwargs={}"
"1969-07-21", "02:56:15", "DEBUG", "Result: 3"
"1969-07-21", "02:56:15", "INFO", "Duration: 0:00:00.000209"
>>>
>>> add(1, b=2)
"1969-07-21", "02:56:15", "DEBUG", "Calling: function='add', args=(1,), kwargs={'b': 2}"
"1969-07-21", "02:56:15", "DEBUG", "Result: 3"
"1969-07-21", "02:56:15", "INFO", "Duration: 0:00:00.000154"
>>>
>>> add(a=1, b=2)
"1969-07-21", "02:56:15", "DEBUG", "Calling: function='add', args=(), kwargs={'a': 1, 'b': 2}"
"1969-07-21", "02:56:15", "DEBUG", "Result: 3"
"1969-07-21", "02:56:15", "INFO", "Duration: 0:00:00.000083"
3.11.7. Use Case - 6
Global Scope Cache
Recap information about factorial (n!
):
5! = 5 * 4!
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1!
1! = 1 * 0!
0! = 1
n! = n * (n-1)! # 1 for n=0
>>> def factorial(n):
... if n == 0:
... return 1
... else:
... return n * factorial(n-1)
3.11.8. Use Case - 7
Cache with global scope:
>>> _cache = {}
>>>
>>> def cache(func):
... def wrapper(n):
... if n not in _cache:
... _cache[n] = func(n)
... return _cache[n]
... return wrapper
>>>
>>>
>>> @cache
... def factorial(n):
... if n == 0:
... return 1
... else:
... return n * factorial(n-1)
>>>
>>>
>>> factorial(5)
120
>>>
>>> print(_cache)
{0: 1, 1: 1, 2: 2, 3: 6, 4: 24, 5: 120}
3.11.9. Use Case - 8
Local Scope Cache
Cache with local scope:
>>> def cache(func):
... _cache = {}
... def wrapper(n):
... if n not in _cache:
... _cache[n] = func(n)
... return _cache[n]
... return wrapper
>>>
>>>
>>> @cache
... def factorial(n):
... if n == 0:
... return 1
... else:
... return n * factorial(n-1)
>>>
>>>
>>> factorial(5)
120
3.11.10. Use Case - 9
Cache with embedded scope:
>>> def cache(func):
... def wrapper(n):
... if n not in wrapper._cache:
... wrapper._cache[n] = func(n)
... return wrapper._cache[n]
... if not hasattr(wrapper, '_cache'):
... setattr(wrapper, '_cache', {})
... return wrapper
>>>
>>>
>>> @cache
... def factorial(n: int) -> int:
... if n == 0:
... return 1
... else:
... return n * factorial(n-1)
>>>
>>>
>>> print(factorial(4))
24
>>>
>>> print(factorial._cache)
{0: 1, 1: 1, 2: 2, 3: 6, 4: 24}
>>>
>>> print(factorial(6))
720
>>>
>>> print(factorial._cache)
{0: 1, 1: 1, 2: 2, 3: 6, 4: 24, 5: 120, 6: 720}
>>>
>>> print(factorial(6))
720
>>>
>>> print(factorial(3))
6
>>>
>>> print(factorial._cache)
{0: 1, 1: 1, 2: 2, 3: 6, 4: 24, 5: 120, 6: 720}
3.11.11. Use Case - 10
Database Cache
>>> DATABASE = {
... 'mlewis': {'name': 'Melissa Lewis', 'email': 'melissa.lewis@nasa.gov'},
... 'mwatney': {'name': 'Mark Watney', 'email': 'mark.watney@nasa.gov'},
... 'avogel': {'name': 'Alex Vogel', 'email': 'alex.vogel@nasa.gov'},
... 'rmartinez': {'name': 'Rick Martinez', 'email': 'rick.martinez@nasa.gov'},
... 'bjohanssen': {'name': 'Beth Johanssen', 'email': 'beth.johanssen@nasa.gov'},
... 'cbeck': {'name': 'Chris Beck', 'email': 'chris.beck@nasa.gov'},
... }
>>>
>>> _cache = {}
>>>
>>> def cache(func):
... def wrapper(username):
... if username not in _cache:
... _cache[username] = func(username)
... return _cache[username]
... return wrapper
>>>
>>>
>>> @cache
... def db_search(username):
... return DATABASE[username]['name']
>>>
>>>
>>>
>>> db_search('mwatney') # not in cache, searches database and updates cache with result
'Mark Watney'
>>>
>>> db_search('mwatney') # found in cache and returns from it, no database search
'Mark Watney'
>>>
>>> print(_cache)
{'mwatney': 'Mark Watney'}
3.11.12. Use Case - 11
Django Login Required
Decorator checks whether user is_authenticated. If not, user will be redirected to login page:
>>>
... from django.shortcuts import render
...
...
... def edit_profile(request):
... if not request.user.is_authenticated:
... return render(request, 'templates/login_error.html')
... else:
... return render(request, 'templates/edit-profile.html')
...
...
... def delete_profile(request):
... if not request.user.is_authenticated:
... return render(request, 'templates/login_error.html')
... else:
... return render(request, 'templates/delete-profile.html')
>>>
... from django.shortcuts import render
... from django.contrib.auth.decorators import login_required
...
...
... @login_required
... def edit_profile(request):
... return render(request, 'templates/edit-profile.html')
...
...
... @login_required
... def delete_profile(request):
... return render(request, 'templates/delete-profile.html')