Decoradores en Python

Día 12

Repaso

1. Objetos de función: nombre de la función
def funcion(): pass
1) f = funcion
2) def fun(funcion): pass
3) def func(): return funcion
4) dic = {'función': funcion} | lista = [funcion]

2. Espacios de nombres y ámbito de aplicación
LEGB
len("abc")
longitud = 10
del longitud
len("abc")
global palabra_clave L => G

numero = 100
def funcion():
    global numero
    numero = 10
def funcion1():
    global
    numero = 20
funcion1()
funcion()
print(numero)

3. Anidamiento de funciones y closures
def funcion1():
    num = 10
    def funcion2():
        print(num)

def funcion1():
    def funcion2():
        def funcion3():
            def funcion4():
                pass
¿Qué es un closure: funcion2, función definida dentro de otra función

# Hoy
# Decoradores: una aplicación de los closures

Palabra clave nonlocal

# Propósito: Unificar los nombres del ámbito Local y Encerrado (el nombre en el ámbito Encerrado debe estar definido previamente)

# Caso de uso: Si se desea modificar el valor de una variable de función externa dentro de una función anidada

# Ejemplo:

def externa():
    valor = 10
    print(valor) # 10
    def interna():
        nonlocal valor
        valor = 20
        print(valor) # 20
    interna()
    print(valor) # 20

'''
# num = 0
# def fn():
#     global num # L => G, unifica el nombre local con el nombre global
#     num = 20
# fn()
# print(num)

def externa():
    # global num
    num = 0
    def interna():
        # Si se desea modificar el valor de la variable de función externa dentro de una función anidada
        nonlocal num # Unifica el nombre del ámbito Local con el Encerrado (el nombre debe estar definido previamente en el ámbito Encerrado)
        num = 10
        print(num)
    interna()
    print(num)
externa()
# print(num)
'''

Principio de abierto-cerrado: no cambiar la forma de llamada ni aumentar funcionalidad en el código fuente

'''
1. No se puede modificar el código fuente del objeto (función) a decorar (cerrado)
2. No se puede cambiar la forma de llamar al objeto (función) decorado, y se debe lograr el efecto de aumentar la funcionalidad (abierto)
'''

# Versión 1.0
def funcion():
    print('función ejecutada')
# funcion()

# Versión 2.0
# def funcion():
#     print('función ejecutada0')
#     print('función ejecutada1')
#     print('función ejecutada2')
# funcion()

# Se modificó el código fuente, no se cambió la forma de llamada, la forma de llamada externa sigue siendo la misma, pero la funcionalidad debe aumentar (abierto)
# def funcion():
#     print('función ejecutada0')
#     print('función ejecutada')
#     print('función ejecutada2')
# funcion()

# Se cambió la forma de llamada, no se modificó el código de la funcionalidad original (cerrado)
def contenedor(funcion):
    print('función ejecutada0')
    funcion()
    print('función ejecutada2')
contenedor(funcion)

'''
1. No se puede modificar el código fuente del objeto (función) a decorar (cerrado)
2. No se puede cambiar la forma de llamar al objeto (función) decorado, y se debe lograr el efecto de aumentar la funcionalidad (abierto)
'''

Decroadores

# Pasa la función a decorar como parámetro a la función externa, luego a través de operaciones de closure devuelve una función de reemplazo
# Función a decorar: fn
# Función externa: exterior(func) exterior(fn) => func = fn
# Función de reemplazo: return interna: funcionalidad original + nueva funcionalidad

def funcion():
    print("Funcionalidad original")

# Decorador
def decorador(etiqueta):
    def interna():
        etiqueta()
        print("Funcionalidad agregada")
    return interna
funcion = decorador(funcion)

funcion()

# Jarrón

# def jarron():
#     print('Aflorar flores')
# jarron()

# Agregar una funcionalidad de pintar y luego admirar: no cumple con el principio abierto-cerrado, se modificó el código fuente
# def jarron():
#     print('Aflorar flores')
#     print('Pintar: para admirar')
# jarron()

# Agregar una funcionalidad de pintar y luego admirar: no cumple con el principio abierto-cerrado, se cambió la forma de llamada
# def contenedor(funcion):
#     jarron()
#     print('Pintar: para admirar')
# contenedor(jarron)

# Aunque se cumple con el principio abierto-cerrado, aparece un bucle infinito en la llamada a la función
# def funcion():
#     jarron()
#     print('Pintar: para admirar')
# jarron = funcion
# jarron()

# Conocimiento: cumple con el principio abierto-cerrado y puede lograr el efecto de decorador: expandir funcionalidad
# def jarron():
#     print('Aflorar flores')
#     etiqueta = jarron # Expuesto globalmente: fácil de modificar
#     def funcion():
#         etiqueta()
#         print('Pintar: para admirar')
#     jarron = funcion
#     jarron()

def decorador(etiqueta):
    def funcion():
        etiqueta()  # jarrón original
        print('Pintar: para admirar')
    return funcion  # jarrón con funcionalidad expandida
jarron = decorador(jarron)

jarron()  # Funcionalidad expandida, y la forma de llamada no cambia

Sintaxis de azúcar: @función_externa

def exterior(f):
    def interna():
        f()
        print("Funcionalidad agregada1")
    return interna

def contenedor(f):
    def interna():
        f()
        print("Funcionalidad agregada2")
    return interna

@contenedor # El orden de decoración determina el orden de ejecución de la nueva funcionalidad
@exterior # <=> fn = exterior(fn): interna
def fn():
    print("Funcionalidad original")

def exterior(fn):
    def interna():
        fn()
        print("Pintar: para admirar")
    return interna

def contenedor(fn):
    def interna():
        fn()
        print('Funcionalidad de exhibición')
    return interna

# Sintaxis de azúcar | Sintaxis dulce
@contenedor
@exterior # <=> jarron = exterior(jarron)
def jarron():
    print('Aflorar flores')
# jarron = exterior(jarron)

jarron()

# Resumen: una función puede ser decorada por cualquier decorador relacionado, o por varios decoradores
# Nota: El orden de decoración afecta el orden de ejecución de la nueva funcionalidad

Funciones con parámetros y retornno decoradas

def verificar_usuario(fn): # fn, login, interna: login en diferentes estados, por lo que los parámetros son uniformes
    def interna(usuario, contrasena):
        # Agregar nueva funcionalidad sobre la original
        if not (len(usuario) >= 3 y usuario.isalpha()):
            print('Validación de cuenta fallida')
            return False
        
        # Funcionalidad original
        resultado = fn(usuario, contrasena)
        
        # Agregar nueva funcionalidad debajo de la original
        # ...
        
        return resultado
    return interna

@verificar_usuario
def login(usuario, contrasena):
    if usuario == 'abc' y contrasena == '123qwe':
        print('Inicio de sesión exitoso')
        return True
    print('Inicio de sesión fallido')
    return False

# Resumen:
# 1. login tiene parámetros, por lo que interna y fn tienen los mismos parámetros
# 2. login tiene valor de retorno, por lo que interna y fn tienen valor de retorno

"""
interna(usuario, contrasena):
    res = fn(usuario, contrasena) # Valor de retorno del login original
    return res

login = verificar_usuario(login) = interna

res = login('abc', '123qwe') # Valor de retorno de interna
"""

# Versión original
# Agregar una funcionalidad de procesamiento de cuenta: 3 o más caracteres alfabéticos o en chino
def verificar_usuario(fn):
    def interna(usuario, contrasena):
        if not (len(usuario) >= 3 y usuario.isalpha()):
            print('Validación de cuenta fallida')
            return False
        resultado = fn(usuario, contrasena) # login
        return resultado
    return interna

# Agregar una funcionalidad de procesamiento de contraseña: 6 o más caracteres alfabéticos y numéricos
def verificar_contrasena(fn):
    def interna(usuario, contrasena):
        if not (len(contrasena) >= 6 y contrasena.isalnum()):
            print('Validación de contraseña fallida')
            return False
        return fn(usuario, contrasena)
    return interna

# Funcionalidad de inicio de sesión
@verificar_usuario # login = verificar_usuario(login) = interna
@verificar_contrasena
def login(usuario, contrasena):
    if usuario == 'abc' y contrasena == '123qwe':
        print('Inicio de sesión exitoso')
        return True
    print('Inicio de sesión fallido')
    return False

res = login('abc', '123qwe') # interna
print(res)

Escritura final de decoradores

def contenedor(fn):
    def interna(*args, **kwargs):
        print('Funcionalidad previa')
        resultado = fn(*args, **kwargs)
        print('Funcionalidad posterior')
        return resultado
    return interna

@contenedor
def funcion1():
    print('Funcionalidad original de fn1')

@contenedor
def funcion2(a, b):
    print('Funcionalidad original de fn2')

@contenedor
def funcion3():
    print('Funcionalidad original de fn3')
    return True

@contenedor
def funcion4(a, *, x):
    print('Funcionalidad original de fn4')
    return True

funcion1()
funcion2(10, 20)
funcion3()
funcion4(10, x=20)

Decoradores con parámetros: para conocimiento

# Para conocimiento
def exterior(color_entrada):
    def contenedor(fn):
        if color_entrada == 'rojo':
            info = '\033[36;41mnueva acción\033[0m'
        else:
            info = 'amarillo:nueva acción'

        def interna(*args, **kwargs):
            pass
            resultado = fn(*args, **kwargs)
            print(info)
            return resultado
        return interna
    return contenedor # exterior(color) => contenedor

color = input('color: ')
@exterior(color) # @exterior(color) => @contenedor # función => interna
def funcion():
    print('funcion ejecutada')

funcion()

Funcionalidad de autenticación de inicio de sesión

estado_login = False # Estado de inicio de sesión

def login():
    usuario = input('usuario: ')
    if not (len(usuario) >= 3 y usuario.isalpha()):
        print('Validación de cuenta fallida')
        return False
    contrasena = input('contraseña: ')
    if usuario == 'abc' y contrasena == '123qwe':
        print('Inicio de sesión exitoso')
        estado_login = True
    else:
        print('Inicio de sesión fallido')
        estado_login = False

# Completar un decorador de verificación de estado de inicio de sesión
def verificar_estado_login(fn):
    def interna(*args, **kwargs):
        # Antes de ver el perfil personal o la función de venta: si no se ha iniciado sesión, iniciar sesión primero, de lo contrario se puede acceder a su funcionalidad
        if estado_login != True:
            print('No has iniciado sesión')
            login()
        # Ver perfil personal o venta
        resultado = fn(*args, **kwargs)
        return resultado
    return interna

# Funcionalidad de verificación de perfil personal
@verificar_estado_login
def perfil():
    print('Perfil personal')

# Funcionalidad de venta
@verificar_estado_login
def vender():
    print('Se puede vender')

perfil()

Etiquetas: decoradores Python programación closures funciones

Publicado el 6-24 17:28