More on Decorators
A Summary of lecture "Writing functions in Python", via datacamp
- Real-world examples
- Decorators and metadata
- Decorators that takes arguments
- timeout(): a real world example
def print_return_type(func):
# Define wrapper(), the decorated function
def wrapper(*args, **kwargs):
# Call the function being decorated
result = func(*args, **kwargs)
print('{}() returned type {}'.format(func.__name__, type(result)))
return result
# Return the decorated function
return wrapper
@print_return_type
def foo(value):
return value
print(foo(42))
print(foo([1, 2, 3]))
print(foo({'a': 42}))
def counter(func):
def wrapper(*args, **kwargs):
wrapper.count += 1
# Call the function being decorated and return the result
return func()
wrapper.count = 0
# Return the new decorated function
return wrapper
# Decorate foo() with the counter() decorator
@counter
def foo():
print('calling foo()')
foo()
foo()
print('foo() was called {} times.'.format(foo.count))
def add_hello(func):
def wrapper(*args, **kwargs):
"""Print 'hello' and then call the decorated function."""
print('hello')
return func(*args, **kwargs)
return wrapper
# Decorate print_sum() with the add_hello() decorator
@add_hello
def print_sum(a, b):
"""Adds two numbers and prints the sum"""
print(a + b)
print_sum(10, 20)
print(print_sum.__doc__)
from functools import wraps
def add_hello(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Print 'hello' and then call the decorated function."""
print('hello')
return func(*args, **kwargs)
return wrapper
# Decorate print_sum() with the add_hello() decorator
@add_hello
def print_sum(a, b):
"""Adds two numbers and prints the sum"""
print(a + b)
print_sum(10, 20)
print(print_sum.__doc__)
import numpy as np
def check_inputs(a, *args, **kwargs):
for value in a:
time.sleep(0.01)
print('Finished checking inputs')
def check_outputs(a, *args, **kwargs):
for value in a:
time.sleep(0.01)
print('Finished checking outputs')
def check_everything(func):
@wraps(func)
def wrapper(*args, **kwargs):
check_inputs(*args, **kwargs)
result = func(*args, **kwargs)
check_outputs(result)
return result
return wrapper
import time
@check_everything
def duplicate(my_list):
"""Return a new list that repeats the input twice"""
return my_list + my_list
t_start = time.time()
duplicated_list = duplicate(list(range(50)))
t_end = time.time()
decorated_time = t_end - t_start
t_start = time.time()
# Call the original function instead of the decorated one
duplicated_list = duplicate.__wrapped__(list(range(50)))
t_end = time.time()
undecorated_time = t_end - t_start
print('Decorated time: {:.5f}s'.format(decorated_time))
print('Undecorated time: {:.5f}s'.format(undecorated_time))
def run_n_times(n):
"""Define and return a decorator"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@run_n_times(3)
def print_sum(a, b):
print(a + b)
print_sum(10, 5)
run_three_times = run_n_times(3)
@run_three_times
def print_sum(a, b):
print(a + b)
print_sum(20, 30)
def run_n_times(n):
"""Define and return a decorator"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@run_n_times(10)
def print_sum(a, b):
print(a + b)
print_sum(15, 20)
run_five_times = run_n_times(5)
@run_five_times
def print_sum(a, b):
print(a + b)
print_sum(4, 100)
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
msg = func(*args, **kwargs)
return '<b>{}</b>'.format(msg)
return wrapper
def italics(func):
@wraps(func)
def wrapper(*args, **kwargs):
msg = func(*args, **kwargs)
return '<i>{}</i>'.format(msg)
return wrapper
def html(open_tag, close_tag):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
msg = func(*args, **kwargs)
return '{}{}{}'.format(open_tag, msg, close_tag)
# Return the decorated function
return wrapper
# Return the decorator
return decorator
@html('<b>', '</b>')
def hello(name):
return 'Hello {}!'.format(name)
print(hello('Alice'))
@html('<i>', '</i>')
def goodbye(name):
return 'Goodbye {}.'.format(name)
print(goodbye('Alice'))
@html('<div>', '</div>')
def hello_goodbye(name):
return '\n{}\n{}\n'.format(hello(name), goodbye(name))
print(hello_goodbye('Alice'))
import signal
def raise_timeout(*args, **kwargs):
raise TimeoutError()
# When an 'alarm' signal goes off, call raise_timeout()
signal.signal(signalnum=signal.SIGALRM, handler=raise_timeout)
# Set off an alarm in 5 seconds
signal.alarm(5)
# Cancel the alarm
signal.alarm(0)
def timeout_in_5s(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Set an alarm for 5 seconds
signal.alarm(5)
try:
# Call the decorated func
return func(*args, **kwargs)
finally:
# Cancel alarm
signal.alarm(0)
return wrapper
@timeout_in_5s
def foo():
time.sleep(10)
print('foo!')
foo()
def timeout(n_seconds):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Set an alarm for n seconds
signal.alarm(n_seconds)
try:
return func(*args, **kwargs)
finally:
signal.alarm(0)
return wrapper
return decorator
@timeout(20)
def bar():
time.sleep(10)
print('bar!')
bar()
def tag(*tags):
# Define a new decorator, named "decorator", to return
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Call the function being decorated and return the result
return func(*args, **kwargs)
wrapper.tags = tags
return wrapper
# Return the new decorator
return decorator
@tag('test', 'this is a tag')
def foo():
pass
print(foo.tags)
def returns_dict(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
assert(type(result) == dict)
return result
return wrapper
@returns_dict
def foo(value):
return value
try:
print(foo([1, 2, 3]))
except AssertionError:
print('foo() did not return a dict')
def returns(return_type):
# Complete the returns() decorator
def decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
assert(type(result) == return_type)
return result
return wrapper
return decorator
@returns(dict)
def foo(value):
return value
try:
print(foo([1,2,3]))
except AssertionError:
print('foo() did not return a dict!')