<jupyter_start><jupyter_text>More on Decorators> A Summary of lecture "Writing functions in Python", via datacamp- toc: true - badges: true- comments: true- author: <NAME>- categories: [Python, Datacamp]- image: images/logo.png  Real-world examples  Print the return type<jupyter_code>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}))<jupyter_output>foo() returned type <class 'int'>
42
foo() returned type <class 'list'>
[1, 2, 3]
foo() returned type <class 'dict'>
{'a': 42}<jupyter_text>Counter<jupyter_code>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))<jupyter_output>calling foo()
calling foo()
foo() was called 2 times.<jupyter_text>Decorators and metadata  Preserving docstrings when decorating functions<jupyter_code>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__)
# Import the function to read docstring of decorator, not wrapper
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__)<jupyter_output>hello
30
Adds two numbers and prints the sum<jupyter_text>Measure decorator overhead<jupyter_code>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))<jupyter_output>Finished checking inputs
Finished checking outputs
Decorated time: 1.51825s
Undecorated time: 0.00025s<jupyter_text>Decorators that takes arguments  Example<jupyter_code>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)<jupyter_output>50
50
50<jupyter_text>run_n_times()<jupyter_code>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
# Make print_sum() run 10 times with the run_n_times() decorator
@run_n_times(10)
def print_sum(a, b):
    print(a + b)
    
print_sum(15, 20)
# Use run_n_times() to create the run_five_times() decorator
run_five_times = run_n_times(5)

@run_five_times
def print_sum(a, b):
    print(a + b)
    
print_sum(4, 100)<jupyter_output>104
104
104
104
104<jupyter_text>HTML Generator<jupyter_code>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
# Make hello() return bolded text
@html('<b>', '</b>')
def hello(name):
    return 'Hello {}!'.format(name)

print(hello('Alice'))
# Make goodbye() return italicized text
@html('<i>', '</i>')
def goodbye(name):
    return 'Goodbye {}.'.format(name)

print(goodbye('Alice'))
# Wrap the result of hello_goodbye() in <div> and </div>
@html('<div>', '</div>')
def hello_goodbye(name):
    return '\n{}\n{}\n'.format(hello(name), goodbye(name))

print(hello_goodbye('Alice'))<jupyter_output><div>
<b>Hello Alice!</b>
<i>Goodbye Alice.</i>
</div><jupyter_text>timeout(): a real world example  Example<jupyter_code>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()<jupyter_output>bar!<jupyter_text>Tag your functions<jupyter_code>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)<jupyter_output>('test', 'this is a tag')<jupyter_text>Check the return type<jupyter_code>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!')<jupyter_output>foo() did not return a dict!
{'fraction_non_alphanumeric': 0.1295372840078859, 'fraction_numerical': 0.013336425837875449, 'mean_word_length': 3.438497169325785, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '299201', 'license': 'Apache-2.0', 'n_tokens_mistral': 2880, 'n_tokens_neox': 2789, 'n_words': 790, 'path': '_notebooks/2020-05-22-02-More-On-Decorators.ipynb', 'repo_name': 'AntonovMikhail/chans_jupyter', 'size': '8630'}