Aplicar varios decoradores a una misma función y pasarles parámetros es una técnica que vas a usar en proyectos reales de Python cuando necesites combinar validaciones, registros y permisos sin ensuciar tu lógica principal. Aquí aprendes cómo encadenar decoradores anidados y decoradores con parámetros para escribir código modular y reutilizable.
¿Qué son los decoradores anidados y con parámetros?
Los decoradores son funciones que envuelven a otras funciones para extender su comportamiento sin modificar su código original. Cuando los combinas, ganas flexibilidad real.
¿Qué es un decorador anidado? Es la aplicación de dos o más decoradores sobre la misma función. Cada uno añade una capa de funcionalidad y se ejecutan en orden, de arriba hacia abajo en la pila de decoradores.
¿Qué es un decorador con parámetros? Es un decorador que recibe argumentos extra para tomar decisiones dinámicas, como validar un rol o configurar un comportamiento específico.
Esta combinación te permite, por ejemplo, validar permisos y registrar acciones en una sola función sin repetir código.
¿Cómo construir un decorador con parámetros paso a paso?
El ejemplo de la clase simula una acción donde un empleado intenta eliminar a otro, pero solo los administradores tienen permiso. Para resolverlo necesitas tres niveles de funciones anidadas [01:30].
La estructura del decorador checkAccess sigue este patrón:
La función externa recibe el parámetro, en este caso el rol requerido.
La función intermedia recibe la función original que será decorada.
La función wrapper recibe los argumentos reales, como la información del empleado, y ejecuta la lógica de validación.
python
def check_access(rol):
# Decorador que comprueba si un empleado tiene un rol específico
def decorador(func):
def wrapper(empleado):
# Comprobamos si el rol del empleado coincide con el rol requerido
if empleado["rol"] == rol:
return func(empleado)
else:
print(f"Acceso denegado. Solo {rol} pueden realizar esta acción.")
return wrapper
return decorador
Fíjate que se retornan tres cosas: la función original cuando se cumple la condición, el wrapper dentro del decorador y el decorador dentro de la función externa. Ese triple return es lo que hace posible pasar parámetros [02:50].
¿Cómo añadir un segundo decorador para registrar la acción?
El segundo decorador, logAction, no necesita parámetros externos. Solo registra la acción antes de ejecutar la función original.
python
def log_action(func):
def wrapper(empleado):
print(f"Registrando acción para el empleado {empleado['nombre']}")
return func(empleado)
return wrapper
En un proyecto real, aquí escribirías en una base de datos o archivo de logs. La clase usa un print para enfocarse en la mecánica del decorador [05:10].
¿Cómo aplicar dos decoradores a la misma función?
El orden de aplicación importa. Cuando apilas decoradores, el de arriba envuelve al de abajo. Por eso debes pensar en el flujo lógico antes de escribir el código.
python
@check_access("admin")
@log_action
def eliminar_empleado(empleado):
print(f"El empleado {empleado['nombre']} ha sido eliminado")
Al ejecutar eliminar_empleado con un usuario admin, primero se valida el rol, luego se registra la acción y finalmente se elimina al empleado. Si pasas un usuario con rol empleado, el flujo se corta en la validación y nunca llega al registro ni a la eliminación [06:20].
¿Por qué importa el orden de los decoradores? El primer decorador en aparecer es el más externo y controla qué tanto del flujo se ejecuta. Si checkAccess va arriba, ninguna acción se registra cuando el acceso es denegado.
¿Qué resultados obtienes al ejecutar el código?
Los dos escenarios del ejemplo dejan claro el comportamiento:
Con un administrador: se imprime "Registrando acción para el empleado Carlos" y luego "El empleado Carlos ha sido eliminado".
Con un empleado regular: se imprime "Acceso denegado. Solo admin pueden realizar esta acción" y el flujo termina ahí.
Este comportamiento confirma que los decoradores anidados se ejecutan en cadena, pero respetan las condiciones de cada capa.
¿Cuándo usar decoradores anidados en proyectos reales?
Esta técnica brilla cuando necesitas separar responsabilidades y mantener tu código principal limpio. Algunos casos típicos donde la vas a aplicar:
Validación de permisos y roles antes de ejecutar acciones sensibles.
Registro de auditoría o logs de operaciones críticas.
Medición de tiempos de ejecución combinada con manejo de errores.
Caché de resultados junto con autenticación de usuarios.
Dominar decoradores con parámetros y decoradores anidados te da control sobre el flujo de ejecución sin acoplar lógica transversal a cada función. Es una de esas habilidades que diferencian código junior de código senior.
¿Ya pensaste en qué parte de tu proyecto actual podrías aplicar esta técnica? Cuéntame en los comentarios cómo combinarías dos decoradores para resolver un problema real.
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.
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.
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.
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