¿Qué son los decoradores anidados y cómo se utilizan en Python?
Los decoradores anidados son una técnica avanzada en Python que permite aplicar múltiples decoradores a una sola función. Son herramientas poderosas que facilitan la modificación del comportamiento de las funciones de manera dinámica y reutilizable. Esta capacidad de anidar decoradores y hacer uso de parámetros adicionales es especialmente útil en proyectos grandes y complejos que requieren control sobre el flujo de ejecución.
¿Cómo se crean decoradores anidados?
Para crear decoradores anidados, es esencial seguir un orden lógico en su construcción:
Primero, define el decorador que se llamará inicialmente en el código.
Luego, crea el decorador que será invocado por el primero.
Por ejemplo, en este escenario, queremos simular que un empleado intenta eliminar a otro, y solo los administradores tienen permiso para esa acción. Aquí está el paso a paso para crear ambos decoradores:
Implementación del decorador 'Check Access'
Este decorador se asegura de que solo los usuarios con rol de administrador puedan realizar la acción:
defCheckAccess(role_required):defdecorator(func):defwrapper(employee):# Comprueba si el rol del empleado coincide con el requeridoif employee['role']== role_required:return func(employee)else:print(f"Acceso denegado. Solo {role_required}s pueden realizar esta acción.")return wrapper
return decorator
Implementación del decorador 'Log Action'
Este decorador se encarga de registrar cada acción realizada por el usuario:
defLogAction(func):defwrapper(employee):print(f"Registrando acción para el empleado {employee['nombre']}.") func(employee)return wrapper
Aplicación de los decoradores en una función
Con ambos decoradores definidos, podemos aplicarlos a una función que elimina a un empleado:
@CheckAccess('admin')@LogActiondefeliminar_empleado(employee):print(f"El empleado {employee['nombre']} ha sido eliminado.")
¿Cómo funcionan juntos los decoradores?
Estos decoradores, cuando se aplican en conjunto a una función, se ejecutan en el orden en que están colocados, comenzando por el decorador más interno. Así, en el ejemplo anterior, LogAction se ejecutará antes que CheckAccess en el contexto de la función eliminar_empleado.
Primero, LogAction registrará la acción independientemente del rol del empleado.
Luego, CheckAccess verificará si el empleado tiene el rol necesario antes de permitir la eliminación.
Beneficios de usar decoradores anidados
Modularidad: Permiten aislar funcionalidades como la verificación de permisos y el registro de acciones, lo que hace que el código sea más limpio y fácil de mantener.
Reutilizabilidad: Puedes reutilizar los decoradores con diferentes funciones, ajustándolos con parámetros para casos específicos.
Flexibilidad: La capacidad de pasar parámetros a los decoradores permite una personalización extrema para adaptarse a las necesidades específicas de las aplicaciones.
Dominar el uso de decoradores anidados te permitirá escribir código Python más eficiente y limpio, mejorando así tu productividad como desarrollador. ¡Sigue practicando y verás cómo estas habilidades se vuelven indispensables en tu carrera profesional!
De verdad que me he concentrado lo más que puedo en las clases pero ya a estas alturas me siento fatigado, yo estoy desde 0 y desde un principio he percibido que este curso es para personas que ya manejan la herramienta en cierto nivel y no para principiantes o nuevos como yo, aún así lo terminaré dado que al menos he entendido una que otra cosa y me familiarice con la herramienta .
Es la segunda vez que vengo a este curso. El problema no es tu nivel inicial. Es que ella no está explicando nada y las clases son un salpicón sin cohesión. Hay mejores cursos en YouTube con enfoque ABP de principio a fin. Por ejemplo, El video de 3 horas de POO de programación fácil me dejó mucho más claro ese tema.
Si puedes, mira los cursos de Nicolás Molina antes de que los deprequen, que también son mucho mejores.
No estas solo en esto. Tengo la misma percepción. No se niega que Carli domine python, sin embargo, los que somos principiantes y novatos nos toca dificil al principio. Personalmente estoy consultando otras fuentes, ver otros profesores, inscribirnos a otros cursos para poder tener un nivel mínimo y entender. No te rindas, continua estudiando, si lo haces como habito dentro de un año volverás a este comentario y estarás orgulloso de tu crecimiento de lo que haz logrado y en lo duro que pegó la curva de aprendizaje.
CON PARAMETROS VS ANIDADOS
1.
Propósito y Uso:
•
Decoradores con Parámetros: Se utilizan cuando necesitas pasar parámetros adicionales al decorador, permitiendo personalizar su comportamiento.
•
Decoradores Anidados: Se usan para aplicar múltiples decoradores secuencialmente, añadiendo capas de funcionalidad a la función.
2.
Complejidad de la Estructura:
•
Decoradores con Parámetros: Requieren una función externa adicional para manejar los parámetros.
•
Decoradores Anidados: Implican aplicar múltiples decoradores uno encima de otro, pero cada decorador sigue la estructura básica de un decorador.
3.
Nivel de Control:
•
Decoradores con Parámetros: Ofrecen un control más fino sobre la lógica del decorador debido a los parámetros adicionales.
•
Decoradores Anidados: Permiten combinar varios decoradores de manera modular, aplicando diferentes aspectos de funcionalidad sin modificar la función original.
Quisiera compartir mi experiencia como estudiante del curso. En las últimas secciones he sentido que las explicaciones han sido bastante superficiales, lo que dificulta seguir el hilo y entender los conceptos clave. A veces se siente forzado y me veo obligado a buscar explicaciones en otras plataformas para comprender lo que se intenta enseñar.
Reconozco el dominio técnico de la docente, y entiendo que la didáctica es un reto complejo. Por ello, quizá sería útil combinar una guía práctica conceptual del objetivo del código con un paso a paso para construirlo. Un enfoque así podría ayudar a quienes empezamos desde cero a ir comprendiendo el propósito y la lógica antes de entrar en detalles de implementación.
Quisiera destacar que está bien buscar apoyo externo cuando uno se siente estancado en algún punto. Pero este curso no es gratuito y, como estudiantes, esperamos un aprendizaje estructurado que realmente nos lleve desde lo básico. Sería valioso también fomentar la empatía entre quienes encuentran fácil entender el contenido y quienes necesitamos más guía, así tendríamos un ambiente más colaborativo.
Soy yo o en este codigo el admin tiene persmiso pero de eliminarse a si mismo 🤨
Correcto pero eso es porque el unico parametro que se pide para borrar es el nombre del empleado, en este caso es el mismo que tiene el rol (el mismo) asumo que por temas de enfocarse solo en el tema de decoradores.
De lo contrario tocaria añadirle un parametro extra a la función para elegir el empleado que desea borrar
Exactamente, jajajajajajajaja. Esas funciones deberían de tener dos argumentos: quién borra y a quién se borra. La verdad sea dicha me da la impresión que esta última parte del curso no fue preparada debidamente. Como decimos en Colombia: "esta clase parece guitarriada".
Nuevamente, acá dejo la versión donde el usuario "admin" puede realizar la acción de eliminar empleado, ya sea sobre él mismo, o sobre otro empleado.
# Decorador que comprueba si un usuario tiene un rol específicodefcheck_acces(role):defdecorator(func):defwrapper(user, employee):# Si el rol del user coincide con el rol requeridoif user.get('role')== role:return func(user, employee)else:print(f'{user['name']} ---> ACCES DENIED. Only users with role "{role}" can employees.')return wrapper
return decorator
deflog_action(func):defwrapper(user, employee):print(f'Log action by the user {user['name']}')return func(user, employee)return wrapper
@check_acces('admin')@log_actiondefdelete_employee(user, employee):print(f'Admin {user['name']} has deleted the employee {employee['name']}.')admin ={'name':'Andrés','role':'admin'}employee ={'name':'Salua','role':'employee'}# Posibles acciones delete_employee(admin, employee)# delete_employee(admin, admin)# delete_employee(employee, employee)# delete_employee(employee, admin)
Al ver esta clase me pregunté: ¿Cuánto afecta el rendimiento cuando se anidan muchos decoradores?
Cómo soy nuevo en Python realicé la pregunta la IA y la respuesta fue la siguiente (resumida):
Lo decoradores al ser funciones que se ejecutar en orden, no afectan mucho el rendimiento de la app. A menos que los utilicemos en 3 casos
Recursividad
Operaciones de I/O
Uso de *args y *kwargs
me ha parecido muy bueno hasta este punto!!!
no entiendo bien esto porque algunos tienen func como parametro y otro no y porque anidados asi osea decorador y luego un wraper no se puede de una desde decorador?
Los decoradores anidados permiten añadir múltiples funcionalidades a una función. El 'wrapper' es necesario para poder aplicar la lógica adicional del decorador a la función objetivo. El decorador recibe como parámetro la función a decorar y el 'wrapper' la información específica para ejecutar esa lógica adicional.
Para el comprobador de rol se puede usar el propio parámetro de check_access, tanto para comprobar el rol y para mencionarlo en el rol requerido =>
# Decorator that checks if an employee has a specific roledefcheck_access(required_role):"""
Decorator factory that creates a decorator to restrict access to a function based on an employee's role.
Args:
required_role (str): The role required to access the decorated function.
Returns:
function: A decorator that checks if the employee's role matches the required role before allowing access.
Usage:
@check_access('admin')
def sensitive_action(employee):
# Function implementation
"""defdecorador(func):defwrapper(employee):# Check if the employee's role matches the required roleif employee.get('role')==str(required_role):# Consider using required_rolereturn func(employee)else:print(f'ACCESS DENIED. Only {required_role} can perform this action.')return wrapper
return decorador
deflog_action(func):defwrapper(employee):# Log the action performed by the employeeprint(f'Logging action for employee {employee["name"]}')return func(employee)return wrapper
@check_access('user')@log_actiondefdelete_employee(employee):# Delete the specified employeeprint(f'Employee {employee["name"]} has been deleted')# Ejemplo de usoemployee1 ={'name':'Alice','role':'abmin'}employee2 ={'name':'Bob','role':'user'}print("\nIntentando eliminar a Alice (rol correcto):")delete_employee(employee1)# Debería permitir la acciónprint("\nIntentando eliminar a Bob (rol incorrecto):")delete_employee(employee2)# Debería denegar el acceso```# Decorator that checks if an employee has a specific roledef check\_access(required\_role): """ Decorator factory that creates a decorator to restrict access to a function based on an employee's role.  Args: required\_role (str): The role required to access the decorated function.  Returns: function: A decorator that checks if the employee's role matches the required role before allowing access.  Usage: @check\_access('admin') def sensitive\_action(employee): # Function implementation """  def decorador(func): def wrapper(employee): # Check if the employee's role matches the required role if employee.get('role') == str(required\_role): # Consider using required\_role return func(employee) else: print(f'ACCESS DENIED. Only {required\_role} can perform this action.') return wrapper return decorador  def log\_action(func):defwrapper(employee):# Log the action performed by the employee print(f'Logging action for employee {employee\["name"]}') return func(employee) return wrapper @check\_access('user')@log\_actiondef delete\_employee(employee): # Delete the specified employee print(f'Employee {employee\["name"]} has been deleted')\# Ejemplo de usoemployee1 ={'name':'Alice','role':'abmin'}employee2 ={'name':'Bob','role':'user'}print("\nIntentando eliminar a Alice (rol correcto):")delete\_employee(employee1)# Debería permitir la acciónprint("\nIntentando eliminar a Bob (rol incorrecto):")delete\_employee(employee2)# Debería denegar el acceso
Me es difícil entender por qué en el decorador de check_access se están usando 3 funciones: la del decorador propiamente, la de decorator y la de wrapper, ya que en la clase pasada, se hizo un decorador con la misma funcionalidad pero solo definiendo 2 funciones: check_access y wrapper.
se usan 3 funciones por el parametro de @check_access('admin')
en la clase anterior no estaba parametrizado el required_role
Los decoradores anidados y con parámetros en Python permiten aplicar múltiples transformaciones a funciones o métodos de una forma flexible y reutilizable. Aquí te explico cómo funcionan ambos conceptos y te muestro ejemplos.
### 1. Decoradores Anidados
Los decoradores anidados son simplemente varios decoradores aplicados a una misma función, uno tras otro. Se aplican en el orden en que aparecen, de afuera hacia adentro. Esto significa que el decorador más cercano a la función será ejecutado primero.
Ejecutando decorador1
Ejecutando decorador2
Hola, Carlos
En este caso:
- decorador2 se aplica primero, luego decorador1.
- saludo pasa por ambos decoradores antes de ejecutar su lógica.
### 2. Decoradores con Parámetros
Los decoradores con parámetros permiten personalizar el comportamiento del decorador. Para crear un decorador con parámetros, se define una función que recibe estos parámetros y que, a su vez, devuelve el decorador propiamente dicho.
**Ejemplo de decorador con parámetros:**
defrepetir(n):  def decorador(funcion):  def wrapper(\*args, \*\*kwargs):  for \_ in range(n):  funcion(\*args, \*\*kwargs)  return wrapper  return decorador@repetir(3)defsaludo(nombre):  print(f"Hola, {nombre}")saludo("Carlos")
**Salida:**
Hola, Carlos
Hola, Carlos
Hola, Carlos
Aquí:
- repetir(3) llama al decorador repetir con n=3.
- El decorador envuelve a saludo, que se ejecutará 3 veces.
### 3. Decoradores Anidados con Parámetros
Podemos combinar ambas ideas aplicando varios decoradores con parámetros. Esto permite una gran flexibilidad y personalización en el comportamiento de las funciones decoradas.
**Ejemplo de decoradores anidados con parámetros:**
- @repetir(2) envuelve la función saludo para ejecutarse 2 veces.
- @prefijo("Atención:") envuelve saludo añadiendo el prefijo "Atención:" antes de cada saludo.
### Detalles Técnicos
Cuando usas decoradores anidados con parámetros, el orden de ejecución sigue siendo de afuera hacia adentro. Esto permite aplicar las personalizaciones de los decoradores en el orden deseado, haciendo que cada paso dependa del resultado del anterior.
Esta técnica es especialmente útil cuando necesitas funcionalidades como:
- **Autorización y autenticación:** Un decorador puede verificar permisos antes de que otro ejecute la lógica principal.
- **Registro y monitoreo:** Puedes aplicar un decorador que registre la ejecución de una función antes de realizar otras acciones.
- **Manipulación de datos:** Un decorador puede transformar datos de entrada o salida antes de que otro decorador aplique sus cambios.
classUser: def __init__(self,name:str,role:str)->None: self.name= name
self.role= role
def get_current_user():returnUser('gb.so','admin')
# función decoradora
def check_access(required_role: str): #aquí recibimos el parámetro enviado al decorador
def decorator(func): #este es el decorador que recibe la función
def wrapper(user:User): # función envolvente, es necesario poner los parámetros
current_user =get_current_user()if current_user.role== required_role:returnfunc(user)else: raise Exception(f'el usuario {current_user.name} no tiene permitida la acción de eliminar usuarios porque no es {required_role}')return wrapper
return decorator
@check_access('admin') # decorador en uso
def delete_user(user:User):print('usuario eliminado')user_to_delete =User('pepe.cierra','cliente')delete_user(user_to_delete)```classUser: def \_\_init\_\_(self,name:str,role:str)->None: self.name= name self.role= role
def get\_current\_user():returnUser('gb.so','admin')\# función decoradoradef check\_access(required\_role: str): #aquí recibimos el parámetro enviado al decorador def decorator(func): #este es el decorador que recibe la función def wrapper(user:User): # función envolvente, es necesario poner los parámetros current\_user = get\_current\_user()if current\_user.role== required\_role:returnfunc(user)else: raise Exception(f'el usuario {current\_user.name} no tiene permitida la acción de eliminar usuarios porque no es {required\_role}')return wrapper return decorator
@check\_access('admin') # decorador en usodef delete\_user(user:User):print('usuario eliminado')user\_to\_delete =User('pepe.cierra','cliente')delete\_user(user\_to\_delete)
Debo ser franco, la profesora es una eminencia y admiro mucho su conocimiento, pero esta el debe en docencia. el 80% de las clases he tenido que reforzarlas por fuera con otros videos de youtube que lo explican mucho mas didacticamente y al final siento que podría tomar toda materia de este curso y verlo por fuera e irme directo a la certificación. Escribir el codigo y narrarlo en el camino no creo que sea la mejor forma de explicar como funciona ya que no hay una abstracción didactica de lo que se esta haciendo. Es como querer aprender un idioma doblando en el camino lo que se esta escribiendo cuando la sutilezas del idioma no estan simplemente doblandolas mientras las escribo si no entendiendo la logica y estructura de lo que estoy escribiendo. Muchas diran, "te faltan fundamentos" pero no es el caso. Esta bien querer explicar el codigo dando casos practicos, pero pasa que el caso practico no va a apoyado en ninguna referencia visual, ningun esquema que me ayude a antender la logica, nada. Simplemente se lanza a picar el codigo y a subtitular lo que esta escribiendo. Una lastima porque se nota la sabiduria en lo que escribe.
El curso esta lleno de errores de logica, errores de escritura, muchas veces parece que tiene afan en terminar la clase mas que en enfocarse a enseñar realmente. es una carrera para terminar el video sin detenerse ni siquiera en ver si las palabras estan bien escritas.
Realmente desepcionante.
Es cierto como esta el video solo el administrador puede borrarse a si mismo pero no quita saber como anidar decoradores es util
yo lo modifique un un poco
un decorador para que verifique el rol y resiva los roles validos
un de corado para que que cree un log si el el usuario tiene el rol deseado
finalmente la funcion de crear el log
'''
Decorator inner function
'''
admin = {"name": "admin", "role": "admin", }
user = {"name": "user", "role": "user"}
gest = {"name": "gest", "role": "gest"}
def valid_role(roles_valid):
def decorator_function(func):
def wrapper(user, post):
if user.get("role") in roles_valid:
func(user, post)
else:
print("user has to login first")
return wrapper
return decorator_function
def log_activity(func):
def wrapper(user, post):
print(f"{user.get('name')} with role {user.get('role')} ")
func(user, post)
return wrapper
@valid_role(["admin",** "user"])
@log_activity
def create_post(user, post):
print(f"{user['name']} created a post {post} successfully")
# create_post(admin, "this is a post")
# create_post(user, "this is a post")
create_post(gest,** "this is a post")
hola chicos estaba haciendo este ejercicio para una siguiente clase, pero estoy teniendo problemas con el decorador, si aguien puede detectar que estoy haciendo mal me serviria mucho
classNuAcount: def __init__(self,money,**owner): self.__money__=money
self._owner_=owner
def __update_money__(self,cuantity):if cuantity <0 and cuantity*-1> self.__money__:returnValueError('not enougth founds')else: self.__money__+=cuantity
return'succes' @property
def money(self):return self.__money__; def __log__(self): def arguments(func): def wraper(*args):withopen(f'transaccionsLog{self._owner_['name']}.txt','a')aslog: result=func(*args)if result=='succes': log.write(f'una transaccion fue realizada el saldo actual es {self.money}')return result
return wraper
return arguments
@__log__
def transaction(self,direction,quantity):ifisinstance(direction,str)and isinstance(quantity,int|float): match direction:case'send':if quantity>0:return self.__update_money__(quantity*-1)else:return self.__update_money__(quantity)case'recive':if quantity>0:return self.__update_money__(quantity)else:return self.__update_money__(quantity*1)case_:print('do you want to send or recive the money?')if __name__=='__main__': owner={'name':'sutanito','id':'666','nacionality':'valhala'} cuenta=NuAcount(45,**owner) cuenta.transaction('recive',50)print(cuenta.money)
Hola Daniel 👋🏼
El bug con el decorador es debido al parámetro self y a la función arguments. self en lugar de ir en la definición del decorador, debe ir en la definición del wrapper en lugar de la función arguments
El código del decorador quedaría así:
def__log__(func):defwraper(self,*args,**kwargs):withopen(f'transaccionsLog{self._owner_['name']}.txt','a')as log: result= func(self,*args,**kwargs)if result=='succes': log.write(f'una transaccion fue realizada el saldo actual es {self.money}')return result
return wraper
Los decoradores creados dentro las clases pueden ser confusos, pues el decorador opera sobre la definición del método (en este caso sobre transaction), no sombre una instancia del método. En cambio wraper si recibe el parámetro self, por que éste envuelve el método de la instancia (en este caso cuando se ejecuta cuenta.transaction('recive', 50)).
Espero este aporte te sea de utilidad.
Saludos!
func es el regalo (la función original).
wrapper es el papel de regalo (envuelve, protege y añade algo bonito).
No se si sea correcto, pero quería que cuando se llama a delete_employee también entrara al log, pero sin ejecutar la función delete_employee, cuando no cumple la condición (enredado explicar con palabras).
A ensayo y error, realice el siguiente código, se que podía buscar/investigar, pero con lo que se de programación pensé que esto podía funcionar, si funciono, pero no se si sea correcto, ¿es correcto?, así debería ser o como será la forma correcta de pasarle un parámetro al segundo decorador???
#Decorador que comprueba si un empleado tiene un rol en especifico
def check_access(required_role):try: def decorator(func): def wrapper(employee): #Comprobar si el empleado tiene rol 'admin'print(required_role, employee['role'])if employee.get('role')== required_role:returnfunc(employee)else:print(f"ACCIÓN DENEGADA. Solo los usuarios con rol {required_role} pueden realizar la acción")returnFalsereturn wrapper
return decorator
except Exceptionasex:print(ex)def log_action(call_delete_function): def decorator(func): def wrapper(employee):if(call_delete_function):print(f"Registrando evento para el empleado {employee['name']}")returnfunc(employee)else:print(f"Registrando evento para el empleado {employee['name']}")return wrapper
return decorator
@check_access('admin')@log_action(True)def delete_employee(employee):try:print(f"Empleado {employee['name']} eliminado \n") except Exceptionasex:print(ex)admin ={'name':'Carlos','role':'admin'}employee ={'name':'Ana','role':'employee'}delete_employee(admin)delete_employee(employee)
Mi aporte
Yo quise crear un archivo .txt que guardara los logs. Y así me quedó:
import os
# decorador que comprueba si un empleado tiene un rol específicodefcheckaccess(requiered_role):#param: str = 'admin'defdecorator(func):#func: log_actiondefwrapper(employee):#dict: employeeif employee.get('role')== requiered_role:return func(employee)# se rompe aquí else:print(f'you cannot erase this user. {employee.get("name")}')return wrapper
return decorator
deflog_action(func):defwrapper(employee):if os.path.exists('log.txt'):withopen('log.txt','a')asfile:file.write(f'11/11/2001 - {func.__name__} created {employee} \n')else:# no sé si cuando no existe el archivo, el anterior if ya lo crea. explicamewithopen('log.txt', mode='w')asfile:file.write(f'1/1/1999 log created. \n 11/11/2001 - {func.__name__} created {employee}\n')print(f'logging done')return func(employee)# llama a delete_ereturn wrapper
"""decoradores anidados:
- El primero, tiene la información del decorador de adentro
- Primero, crea el decorador de afuera y luego el de adentro
"""@checkaccess('admin')@log_actiondefdelete_e(employee):print(f'delete employee {employee.get("name")}')employee ={'name':'Juan','role':'admin'}delete_e(employee)# delete employee Juan