<jupyter_start><jupyter_text>Decoradores ¿Quién podría explicar lo que está pasando aquí?:  Funciones (first-class object)<jupyter_code>def say_hello(name):
    return f"Hello {name}"

def be_awesome(name):
    return f"Yo {name}, together we are the awesomest!"

def greet_bob(funcion):
    return funcion("Bob")
greet_bob(say_hello)
greet_bob(be_awesome)
def parent():
    print("Printing from the parent() function")

    def first_child():
        print("Printing from the first_child() function")

    def second_child():
        print("Printing from the second_child() function")

    second_child()
    first_child()
def parent(num):
    def first_child(n):
        return "Hi, I am Emma", n

    def second_child(n):
        return "Call me Liam", n

    if num == 1:
        return first_child
    else:
        return second_child
first = parent(1)
second = parent(2)


# descomentar las siguientes líneas y mirad que va pasando
# first
# second

# first()
# second()
second(2)<jupyter_output><empty_output><jupyter_text>Un decorador básico<jupyter_code>def my_decorator_printantesdespues(func):
    def wrapper():
        print("Ocurre algo ANTES de llamar a la función.")
        print(f"el valor de retorno es: {func()}")
        print("Ocurre algo DESPUÉS de llamar a la función.")

    return wrapper
def say_whee():
    print("Whee!")
say_whee()
say_whee_decorada = my_decorator_printantesdespues(say_whee)
say_whee_decorada()
@my_decorator_printantesdespues
def say_whee():
    print("Whee!")
@my_decorator_printantesdespues
def funcion():
    
    return "cadena"
funcion()
@my_decorator_printantesdespues
def decir_hola():
    print("Holaaa")
decir_hola()
permitido = True


def pedir_permiso(func):
    def wrapper():
        if permitido:
            print("Adelante!")
            return func()
        else:
            print("Pues va a ser que no...")
            pass  # No se puede :(

    return wrapper


@pedir_permiso
def hacer_algo():
    return "Voy a bañarme!"
permitido = True


resultado = hacer_algo()
resultado
# decorado que permite argumentos
def pedir_permiso_arg(permitido=True):
    def decorador_real(func):
        def wrapper(*args, **kwargs):
            if permitido:
                print("Adelante!")
                func(*args, **kwargs)
            else:
                print("Pues va a ser que no...")
                pass  # No se puede :(

        return wrapper
    
    return decorador_real


@pedir_permiso_arg()
def hacer_algo():
    print("Voy a bañarme!")
hacer_algo()
def f(*args, **kwargs):
    print(args)
    print(kwargs)
f(1, 2, 3, nombre="ricardo")
d = {"a": 1, "v": 2}
def f2(a, v):
    print(a)
    print(v)
f2(**d)<jupyter_output><empty_output><jupyter_text>\*args y \*\*kwargs<jupyter_code>def f(*args, **kwargs):
    print(args)
    print(kwargs)
f(1,2,3,4, ultimo=5)<jupyter_output><empty_output><jupyter_text>Como hacer correctamente un decorador que acepte argumentos pero que funcione también si no le pasamos ningun argumento. Es decir, los argumentos son opcionales.<jupyter_code># source: https://florimond.dev/blog/articles/2019/09/python-optionally-parametrized-decorators/
# also Python Cookbook: Recipes for Mastering Python 3
# Book by <NAME> and <NAME>
# https://stackoverflow.com/questions/48098569/use-of-functools-partial-in-a-decorator-that-attaches-function-as-attribute-of-o

import functools


def logged(func=None, decimals: int = None):
    if func is None:
        print(f"Called with decimals={decimals}")
        return functools.partial(logged, decimals=decimals)

    print(f"Called without parameters, func={func}.")

    @functools.wraps(func)
    def decorated(*args, **kwargs):
        print(f"{func.__name__} called with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        logged_result = result if decimals is None else round(result, decimals)
        print(f"Result: {logged_result}")
        return result

    return decorated
from datetime import datetime
import functools

def not_during_the_night(func):
    @functools.wraps(func)
    def wrapper():
        if 20 <= datetime.now().hour < 11:
            func()
        else:
            print("es de noche")
            pass
    return wrapper


def say_whee():
    """Dice whee."""
    
    print("Whee!")

# no permite ejecutar la función a ciertas horas
say_whee = not_during_the_night(say_whee)
say_whee()
say_whee()<jupyter_output><empty_output><jupyter_text>`*args` y `**kwargs`  Ejercicio 01Crear un decorador llamado `@timer`.El decorados me dice el tiempo que ha tardado en ejecutarse está funcion (usando `print()`).<jupyter_code>@timer
def hola():
    print("jnkjn")
hola()<jupyter_output><empty_output><jupyter_text>¿Qué es eso de @functools.wrap? `@functools.wraps` es un decorador que preserva la información de la función original (metadatos: docstring, etc)  Usando clases para crear decoradores La mayoría de las veces no será necesario 🙅🏻‍♀️ ¿Cómo haríais un decorador que almacena el número de veces que la función ha sido llamada?<jupyter_code>import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)

@CountCalls
def haz_algo():
    print("Whee!")
import time
haz_algo()<jupyter_output><empty_output><jupyter_text>El método `.__ init __ ()` debe almacenar una referencia a la función y puede hacer cualquier otra inicialización necesaria. Se llamará al método `.__ call __ ()` en lugar de la función decorada. Hace lo mismo que la función `wrapper()` en nuestros ejemplos anteriores. Ten en cuenta que se usa la función `functools.update_wrapper()` en lugar de `@functools.wraps`.<jupyter_code># source: https://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html#what-can-you-do-with-decorators
# PythonDecorators/decorator_with_arguments.py

class decorador_con_argumentos(object):
    def __init__(self, arg1, arg2, arg3):
        """
        If there are decorator arguments, the function
        to be decorated is not passed to the constructor!
        """
        print("Inside __init__()")
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, f):
        """
        If there are decorator arguments, __call__() is only called
        once, as part of the decoration process! You can only give
        it a single argument, which is the function object.
        """
        print("Inside __call__()")

        def wrapped_f(*args):
            print("Inside wrapped_f()")
            print("Decorator arguments:", self.arg1, self.arg2, self.arg3)
            f(*args)
            print("After f(*args)")

        return wrapped_f


@decorador_con_argumentos("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print("sayHello arguments:", a1, a2, a3, a4)


print("After decoration")

print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")<jupyter_output><empty_output><jupyter_text>Decorador básico:<jupyter_code>def decor(func):
    def wrapper(*args, **kwargs):
        
        result = func(*args, **kwargs)

        return result
        
    return wrapper
@decor
def suma(a, b):
    return a + b
res = suma(1,2)
res<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup