Análisis línea por línea con sintaxis, ejecución en contexto y glosario pedagógico.
Habitacion — El Moldeclass Habitacion: # Define la clase Habitacion
def __init__(self, numero, tipo, precio_noche): # Constructor: inicializa el objeto con los datos recibidos
self.numero = numero # Guarda numero como atributo de esta instancia
self.tipo = tipo # Guarda tipo como atributo de esta instancia
self.precio_noche = precio_noche # Guarda precio_noche como atributo de esta instancia
self.disponible = True # toda habitación nace libre
Cuando escribes hab101 = Habitacion(101, "Suite", 350):
self.numero, "Suite" en self.tipo.self.disponible = True automáticamente, sin que el programador tenga que indicarlo.self.tipo).Habitacion(...) construyes una instancia diferente en memoria.
True. El constructor lo fija solo. Es una regla de negocio invariable al crear.__init__? — El constructorSi no defines __init__, el objeto se crea pero sin ningún dato. Sería como entregarle una habitación sin número, sin tipo ni precio. Inútil para el hotel.
Python llama a __init__ en el momento exacto de la creación: Habitacion(101, ...). No necesitas llamarlo manualmente. Es el "arranque" del objeto.
Obliga a que cada objeto nazca con los datos mínimos necesarios. Si intentas crear Habitacion() sin argumentos, Python lanza un error inmediato.
En Python solo puede existir un __init__ por clase (a diferencia de otros lenguajes). Si defines dos, el segundo sobreescribe al primero silenciosamente.
self y qué pasa si no lo pones?def __init__(self, numero): # Constructor: inicializa el objeto con los datos recibidos
self.numero = numero # Guarda numero como atributo de esta instancia
hab101 = Habitacion(101) # Asigna un valor inicial o calculado a hab101
hab202 = Habitacion(202) # Asigna un valor inicial o calculado a hab202
print(hab101.numero) # 101
print(hab202.numero) # 202
self es la dirección de memoria del objeto actual — distingue hab101 de hab202.
def __init__(numero): # falta self
numero = numero # variable local
hab101 = Habitacion(101) # Asigna un valor inicial o calculado a hab101
# TypeError: __init__() takes 1
# positional argument but 2 were given
self declarado, los argumentos se desplazan y Python lanza TypeError.
📍 Memoria RAM — dos objetos, datos separados
self garantiza que numero = 101 va al espacio de hab101 y no al de hab202.
self.nombre = nombre — ¿Por qué el mismo nombre? ¿Qué pasa si pongo otro?
La expresión self.numero = numero tiene dos espacios distintos:
el lado izquierdo vive en el objeto; el derecho es la variable local del parámetro.
Son mundos separados que el = une.
| Atributo del objeto | Parámetro recibido | ¿Funciona? | Notas |
|---|---|---|---|
| self.numero | numero | ✅ Sí | Convención recomendada. Mismo nombre = fácil de leer y mantener. |
| self.numero | n | ✅ Sí | self.numero = n funciona perfecto. El atributo se llama numero pero el parámetro se llama n. Confuso pero válido. |
| self.num_hab | numero | ✅ Sí | El atributo puede tener nombre diferente. Pero ahora debes recordar usar .num_hab en todo el código. |
| self.numero | numero | ❌ Error lógico | Si escribes solo numero = numero sin self., estás asignando la variable local a sí misma. No se guarda nada en el objeto. Al salir del método, el dato se pierde. |
| — (ninguno) | numero | ❌ No accesible | Si no haces self.algo = numero, el parámetro existe solo dentro de __init__ y desaparece. No podrás hacer hab101.numero después. |
✅ self.x = x — dato persiste
def __init__(self, numero): # Constructor: inicializa el objeto con los datos recibidos
self.numero = numero # ← guardado en objeto
hab = Habitacion(101) # Asigna un valor inicial o calculado a hab
print(hab.numero) # ✅ 101
❌ sin self. — dato se pierde
def __init__(self, numero): # Constructor: inicializa el objeto con los datos recibidos
numero = numero # ← solo variable local
hab = Habitacion(101) # Asigna un valor inicial o calculado a hab
print(hab.numero) # ❌ AttributeError
Es la definición, el plano. No ocupa datos reales de habitaciones. Es como el diseño arquitectónico de un cuarto — no es el cuarto.
Solo existe una en el programa.
Una variable que pertenece al objeto. Guarda un dato específico de esa instancia. Dos habitaciones pueden tener el mismo atributo pero con valores distintos.
Existen tantos como instancias.
Una función que vive dentro de la clase. Siempre recibe self para poder leer o modificar los atributos del objeto que lo llama.
Se comparte entre todas las instancias.
# Atributos
self.numero # 101
self.tipo # "Suite"
self.precio_noche # 350.0
self.disponible # True/False
self.piso # 3
self.capacidad # 2 personas
# Métodos
.reservar() # bloquea
.liberar() # disponible=True
.calcular_costo(dias) # precio*dias
.aplicar_descuento(pct) # precio*0.9
.__str__() # texto legible
# Atributos
self.nombre # "Ana López"
self.email # "ana@mail.com"
self.telefono # "9999-0000"
self.reservaciones # []
self.puntos_fide # 120
# Métodos
.agregar_reservacion(r) # guarda historial
.ver_historial() # lista reservas
.acumular_puntos(monto) # +puntos
.es_cliente_vip() # puntos>500
.__str__() # texto legible
# Atributos
self.cliente # objeto Cliente
self.habitacion # objeto Habitacion
self.fecha_entrada # "2026-06-01"
self.fecha_salida # "2026-06-05"
self.estado # "activa"
# Métodos
.confirmar() # activa todo
.cancelar() # libera hab.
.calcular_noches() # días entre fechas
.total_a_pagar() # precio final
.__str__() # resumen
# Atributos
self.nombre # "Grand Plaza"
self.habitaciones # [hab101, ...]
self.clientes # [cli1, ...]
self.reservaciones # [res1, ...]
# Métodos
.agregar_habitacion(h) # registra cuarto
.buscar_disponibles() # filtra libres
.registrar_cliente(c) # agrega cliente
.crear_reservacion(...) # orquesta todo
.reporte_ocupacion() # % ocupado
Python lee class Habitacion: y guarda la plantilla en memoria. No crea ningún objeto todavía. Solo registra: "existe un tipo llamado Habitacion con estos atributos y métodos".
Cuando escribes hab101 = Habitacion(101, "Suite", 350), Python hace 3 cosas internas: reserva espacio en RAM, llama a __init__ automáticamente y pasa el objeto nuevo como self.
Sorpresa: los atributos se guardan en cada objeto, pero los métodos viven en la clase (solo una copia). Cuando llamas hab101.reservar(), Python busca reservar en la clase y automáticamente pasa hab101 como self.
Cuando Reservacion llama a self.habitacion.reservar(), Python sigue la cadena: busca habitacion en la reservación, luego busca reservar en la clase Habitacion, y finalmente ejecuta con ese objeto como self.
Python tiene un garbage collector. Cuando ninguna variable apunta a un objeto, lo elimina de memoria automáticamente. No necesitas destruirlo manualmente (a diferencia de C++ o C).
| Tipo | Sintaxis | Ejemplo Hotel | ¿Accesible desde fuera? | Convención |
|---|---|---|---|---|
| Público | self.nombre |
self.numero, self.tipo |
✅ Sí, directo | Sin guión bajo. Cualquiera puede leerlo y modificarlo. |
| Protegido | self._nombre |
self._descuento, self._historial |
⚠️ Sí, pero "no deberías" | Un guión bajo. Es una convención: "uso interno, maneja con cuidado". Python no lo impide. |
| Privado | self.__nombre |
self.__id_interno, self.__pin |
❌ No directamente — Python lo renombra | Doble guión bajo. Python aplica name mangling: renombra a _Clase__nombre. Protege de acceso accidental externo. |
class Habitacion: # Define la clase Habitacion
def __init__(self, numero, tipo, precio_noche): # Constructor: inicializa el objeto con los datos recibidos
# ── PÚBLICO: cualquiera puede leer y cambiar ──
self.numero = numero # acceso libre
self.tipo = tipo # Guarda tipo como atributo de esta instancia
# ── PROTEGIDO: convención "uso interno" ──
self._precio_noche = precio_noche # un guión bajo
self._descuento = 0 # Guarda _descuento como atributo protegido de la instancia
# ── PRIVADO: Python lo renombra internamente ──
self.__id_interno = id(self) # doble guión bajo
self.__disponible = True # Guarda __disponible como atributo privado de la instancia
# Método para acceder al privado desde DENTRO (getter)
def get_disponible(self): # Getter: devuelve el valor de disponible
return self.__disponible # Retorna el valor calculado al lugar donde se llamó este método
# Método para cambiar el privado desde DENTRO (setter)
def set_precio(self, nuevo_precio): # Setter: valida y actualiza el atributo precio
if nuevo_precio > 0: # Verifica nuevo_precio > 0 antes de continuar
self._precio_noche = nuevo_precio # Guarda _precio_noche como atributo protegido de la instancia
else: # Se ejecuta si la condición anterior no se cumplió
print("❌ El precio debe ser positivo.") # Muestra en consola el resultado de esta operación
hab = Habitacion(101, "Suite", 350) # Asigna un valor inicial o calculado a hab
# Leer
print(hab.numero) # 101 ✅
print(hab.tipo) # "Suite" ✅
# Modificar
hab.tipo = "Doble" # ✅ permitido
hab = Habitacion(101, "Suite", 350) # Asigna un valor inicial o calculado a hab
# Leer (funciona, pero no es ideal)
print(hab._precio_noche) # 350 ⚠️
# Modificar (funciona, pero es tu riesgo)
hab._precio_noche = -50 # ⚠️ sin validación
# Lo correcto: usar el setter
hab.set_precio(400) # ✅ validado
hab = Habitacion(101, "Suite", 350)
# Acceso directo → AttributeError
hab.__disponible # ❌ AttributeError
# Python lo renombró a:
hab._Habitacion__disponible # ✅ funciona
# (pero nunca deberías usarlo así)
# Forma correcta: usar el getter
hab.get_disponible() # ✅ True
__x a _Clase__x (name mangling). Esto evita que subclases o código externo lo sobreescriban por accidente. Accede siempre con métodos.
numero, tipo, nombre. Son la "identidad" del objeto.
_precio_noche, _descuento. Una subclase de HabitacionVIP sí podría usarlos.
__id_interno, __pin_acceso, __numero_tarjeta. Solo el objeto mismo los manipula mediante sus propios métodos.
self.__disponible
_Habitacion__disponible
hab.__disponible → ❌
hab._Habitacion__disponible → ✅
HabitacionVIP(Habitacion) y defines self.__disponible ahí, no colisiona con el de la clase padre porque tendrá nombre _HabitacionVIP__disponible.
[corchetes] son lo que debes reemplazarclass [NombreDeLaClase]:
"""[Descripción breve del objeto]"""
def __init__(self, [param1], [param2]):
# ── ATRIBUTOS: lo que el objeto TIENE ──
self.[param1] = [param1]
self.[param2] = [param2]
self.[estado] = [ValorPorDefecto]
# ── MÉTODO: lo que el objeto HACE ──
def [nombre_metodo](self):
[lógica de la acción]
return [resultado]
# ── REPRESENTACIÓN ──
def __str__(self):
return f"[texto con {self.param1}]"class Habitacion:
"""Representa un cuarto en el hotel"""
def __init__(self, numero, tipo):
# ── ATRIBUTOS: lo que el objeto TIENE ──
self.numero = numero
self.tipo = tipo
self.disponible = True
# ── MÉTODO: lo que el objeto HACE ──
def reservar(self):
if self.disponible:
self.disponible = False
return f"Hab {self.numero} reservada"
# ── REPRESENTACIÓN ──
def __str__(self):
return f"Hab {self.numero} — {self.tipo}"Siempre dentro de __init__. El nombre izquierdo queda en el objeto para siempre; el derecho es el parámetro que llegó del exterior.
self. el dato existe solo durante __init__ y luego desaparece.self es obligatorio como primer parámetro — permite acceder a los atributos. Los parámetros extra son opcionales.
self Python lo trata como función suelta, no como método del objeto.Crea un objeto real en RAM. Los valores que pasas entre paréntesis van directo a los parámetros del __init__ (excepto self, que Python pasa automáticamente).
__init__.Producto| Sección | Código | ¿Qué hace? |
|---|---|---|
| Clase | class Producto: |
Define el concepto general de producto. |
| Atributos | self.nombre |
Guarda nombre, unidades disponibles y precio de cada producto. |
| Método | def vender(self) |
Resta 1 al stock si hay unidades disponibles. |
| Método | def reabastecer(self, cantidad) |
Suma unidades al stock cuando llega mercancía. |
| Invocación | p1 = Producto("Laptop", 10, 999) |
Crea una laptop real con 10 unidades a $999. |
class Producto:
"""Artículo en el sistema de ventas"""
def __init__(self, nombre, stock, precio):
self.nombre = nombre # "Laptop"
self.stock = stock # 10
self.precio = precio # 999.0
self.activo = True # siempre activo al crear
def vender(self):
if self.stock > 0:
self.stock -= 1
return f"✅ Venta OK. Stock: {self.stock}"
return "❌ Sin stock disponible."
def reabastecer(self, cantidad):
self.stock += cantidad
return f"📦 +{cantidad}. Stock: {self.stock}"
def __str__(self):
return f"{self.nombre} | ${self.precio} | Stock: {self.stock}"
# ── Invocación ──
p1 = Producto("Laptop", 10, 999)
print(p1.vender()) # ✅ Venta OK. Stock: 9
print(p1.reabastecer(5)) # 📦 +5. Stock: 14
print(p1) # Laptop | $999 | Stock: 14En VS Code: File → Preferences → Configure User Snippets (o Ctrl + Shift + P y escribe "snippets")
En el menú desplegable selecciona python. Se abre el archivo python.json.
Copia el código de abajo y pégalo dentro de las llaves { } del archivo JSON.
En un archivo Python escribe el prefix del snippet y presiona Tab o Enter.
prefix: "clase")"Clase Basica": { # Continúa la lógica del ejemplo paso a paso
"prefix": "clase", # Continúa la lógica del ejemplo paso a paso
"body": [ # Continúa la lógica del ejemplo paso a paso
"class ${1:NombreClase}:", # Continúa la lógica del ejemplo paso a paso
" \"\"\"${2:Descripcion de la clase}\"\"\"", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __init__(self, ${3:param}):", # Continúa la lógica del ejemplo paso a paso
" self.${3:param} = ${3:param}", # Continúa la lógica del ejemplo paso a paso
" self.${4:estado} = ${5:True}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def ${6:metodo}(self):", # Continúa la lógica del ejemplo paso a paso
" ${7:pass}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __str__(self):", # Continúa la lógica del ejemplo paso a paso
" return f\"${1:NombreClase}: {self.${3:param}}\"" # Continúa la lógica del ejemplo paso a paso
], # Continúa la lógica del ejemplo paso a paso
"description": "Clase con init, metodo y __str__" # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
prefix: "clasehereda")"Clase con Herencia": { # Continúa la lógica del ejemplo paso a paso
"prefix": "clasehereda", # Continúa la lógica del ejemplo paso a paso
"body": [ # Continúa la lógica del ejemplo paso a paso
"class ${1:Hija}(${2:Padre}):", # Continúa la lógica del ejemplo paso a paso
" \"\"\"${3:Descripcion}\"\"\"", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __init__(self, ${4:param_padre}, ${5:param_extra}):", # Continúa la lógica del ejemplo paso a paso
" super().__init__(${4:param_padre})", # Continúa la lógica del ejemplo paso a paso
" self.${5:param_extra} = ${5:param_extra}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __str__(self):", # Continúa la lógica del ejemplo paso a paso
" return f\"${1:Hija}: {self.${5:param_extra}}\"" # Continúa la lógica del ejemplo paso a paso
], # Continúa la lógica del ejemplo paso a paso
"description": "Clase que hereda de otra con super()" # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
prefix: "getset")"Getter Setter Privado": { # Continúa la lógica del ejemplo paso a paso
"prefix": "getset", # Continúa la lógica del ejemplo paso a paso
"body": [ # Continúa la lógica del ejemplo paso a paso
"@property", # Continúa la lógica del ejemplo paso a paso
"def ${1:nombre}(self):", # Continúa la lógica del ejemplo paso a paso
" return self.__${1:nombre}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
"@${1:nombre}.setter", # Continúa la lógica del ejemplo paso a paso
"def ${1:nombre}(self, valor):", # Continúa la lógica del ejemplo paso a paso
" if ${2:condicion}:", # Continúa la lógica del ejemplo paso a paso
" self.__${1:nombre} = valor", # Continúa la lógica del ejemplo paso a paso
" else:", # Continúa la lógica del ejemplo paso a paso
" raise ValueError(f\"${3:Mensaje de error}\")" # Continúa la lógica del ejemplo paso a paso
], # Continúa la lógica del ejemplo paso a paso
"description": "Property con getter y setter validado" # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
prefix: "classpro")"Clase Pro Hotel": { # Continúa la lógica del ejemplo paso a paso
"prefix": "classpro", # Continúa la lógica del ejemplo paso a paso
"body": [ # Continúa la lógica del ejemplo paso a paso
"class ${1:Nombre}:", # Continúa la lógica del ejemplo paso a paso
" \"\"\"${2:Descripcion}\"\"\"", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __init__(self, ${3:param1}, ${4:param2}):", # Continúa la lógica del ejemplo paso a paso
" self.${3:param1} = ${3:param1}", # Continúa la lógica del ejemplo paso a paso
" self.${4:param2} = ${4:param2}", # Continúa la lógica del ejemplo paso a paso
" self._${5:protegido} = ${6:valor}", # Continúa la lógica del ejemplo paso a paso
" self.__${7:privado} = ${8:valor}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def ${9:accion}(self):", # Continúa la lógica del ejemplo paso a paso
" \"\"\"${10:Descripcion del metodo}\"\"\"", # Continúa la lógica del ejemplo paso a paso
" ${11:pass}", # Continúa la lógica del ejemplo paso a paso
"", # Continúa la lógica del ejemplo paso a paso
" def __str__(self):", # Continúa la lógica del ejemplo paso a paso
" return (f\"${1:Nombre} | {self.${3:param1}}\")" # Continúa la lógica del ejemplo paso a paso
], # Continúa la lógica del ejemplo paso a paso
"description": "Clase completa con publico, protegido y privado" # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
🎯 ¿Qué son los ${1:placeholder} en el JSON?
${1:NombreClase}1 es el orden del Tab stop. VS Code resalta este texto primero para que lo reemplaces.
${3:param} = ${3:param}⇥ Tab para avanzarTab, el cursor salta al siguiente número ($1 → $2 → $3...) hasta completar la clase.
"prefix": "clase"__init__, los atributos y los métodos base los genera VS Code por ti en segundos.
reservar() def reservar(self): # Método reservar(): acción que puede realizar este objeto
if self.disponible: # evalúa el booleano directamente
self.disponible = False # Guarda disponible como atributo de esta instancia
return f"✅ Habitación {self.numero} reservada con éxito." # Retorna el valor calculado al lugar donde se llamó este método
return f"❌ Habitación {self.numero} ya está ocupada." # Retorna el valor calculado al lugar donde se llamó este método
== True; Python infiere la verdad de la variable.{} sin concatenación.True → cambia el interruptor a False y retorna mensaje de éxito.reservar() para la misma habitación.{variable}.True o False.Persona y Clienteclass Persona: # Define la clase Persona
def __init__(self, nombre, email): # Constructor: inicializa el objeto con los datos recibidos
self.nombre = nombre # Guarda nombre como atributo de esta instancia
self.email = email # Guarda email como atributo de esta instancia
class Cliente(Persona): # Cliente hereda de Persona
def __init__(self, nombre, email, telefono): # Constructor: inicializa el objeto con los datos recibidos
super().__init__(nombre, email) # delega al padre
self.telefono = telefono # Guarda telefono como atributo de esta instancia
self.reservaciones = [] # historial vacío
cliente1 = Cliente("Ana", "ana@mail.com", "9999")__init__ de Cliente.super().__init__ delega a Persona → guarda nombre y email.telefono y reservaciones.Cliente).Persona).Reservacionclass Reservacion: # Define la clase Reservacion
def __init__(self, cliente, habitacion, fecha_entrada, fecha_salida): # Constructor: inicializa el objeto con los datos recibidos
self.cliente = cliente # objeto Cliente
self.habitacion = habitacion # objeto Habitacion
self.fecha_entrada = fecha_entrada # Guarda fecha_entrada como atributo de esta instancia
self.fecha_salida = fecha_salida # Guarda fecha_salida como atributo de esta instancia
def confirmar(self): # Método confirmar(): acción que puede realizar este objeto
resultado = self.habitacion.reservar() # 1. cierra la habitación
self.cliente.agregar_reservacion(self) # 2. registra en el cliente
return resultado # 3. devuelve resultado
Habitacion). Esto es composición.self al final envía la reservación entera (no solo un dato) al historial del cliente.Al ejecutar reserva.confirmar() se dispara una cadena:
Habitacion y a Cliente. Este es el principio de responsabilidad única en acción.
| Paso | Acción | ¿Qué se evalúa / almacena? | Concepto POO |
|---|---|---|---|
| 1 · Instanciación | hab101 = Habitacion(...) |
Crea el objeto en memoria con ID único y disponible = True. |
Instancia |
| 2 · Lógica | if self.disponible |
Revisa el estado booleano de la propiedad. | Estado |
| 3 · Confirmación | reserva.confirmar() |
Ejecuta el cambio de estado de True → False y actualiza historial. |
Colaboración |
| 4 · Visualización | print(hab101) |
Llama automáticamente al método __str__ para texto legible. |
Dunder method |
La regla es simple: cada [corchete] de la plantilla se convierte en un
${número:texto} en el snippet.
El número indica el orden en que el cursor saltará al presionar Tab.
El texto es el placeholder que verás resaltado.
class [NombreDeLaClase]:
"""[Descripción]"""
def __init__(self, [param1], [param2]):
self.[param1] = [param1]
self.[param2] = [param2]
self.[estado] = [ValorDefecto]
def [nombre_metodo](self):
[lógica]
return [resultado]
def __str__(self):
return f"[texto]""Clase Pro": {
"prefix": "clase",
"body": [
"class ${1:NombreDeLaClase}:",
" \"\"\"${2:Descripción}\"\"\"",
"",
" def __init__(self, ${3:param1}, ${4:param2}):",
" self.${3:param1} = ${3:param1}",
" self.${4:param2} = ${4:param2}",
" self.${5:estado} = ${6:True}",
"",
" def ${7:nombre_metodo}(self):",
" ${8:pass}",
" return ${9:None}",
"",
" def __str__(self):",
" return f\"${10:texto}\""
],
"description": "Clase completa con init, método y __str__"
}self.x y en la asignación.True como sugerencia.pass es el placeholder porque el código necesita al menos una línea válida.None como sugerencia por defecto.__str__. Último campo antes de terminar..py y escribes el prefixEscribe la palabra "clase" (el prefix del snippet). VS Code muestra una sugerencia en el autocompletado.
VS Code expande toda la estructura. El cursor salta automáticamente al Tab stop 1 — el nombre de la clase — que aparece resaltado en azul.
Escribes "Habitacion". El placeholder se reemplaza. Al presionar Tab el cursor salta al docstring (stop 2).
Escribes "numero" y VS Code reemplaza simultáneamente el parámetro del __init__, el self.numero y la asignación = numero. ¡Un solo tecleo, tres cambios!
Cada Tab salta al siguiente número. Al llegar al stop 10, el cursor queda libre. Tu clase está 100% completada.
Si le pones número diferente a cada campo ($1, $2, $3...) el cursor los recorre uno por uno.
Si repites el mismo número ($3, $3, $3) escribes una vez y se actualiza en todos los sitios simultáneamente.
: es el placeholder${1:NombreClase} → muestra "NombreClase" en gris hasta que empiezas a escribir. Lo que escribes lo reemplaza.
$0 es la posición finalSi añades $0 al final del snippet, el cursor termina ahí después del último Tab. Opcional pero útil para posicionarte tras la clase.
clase → Tab → se expande toda la estructura → Tab × 10 para llenar cada campo → clase completa en menos de 30 segundos.
Hasta aquí ya dominas clases, constructores, self, __str__, super(), herencia y modificadores de acceso. Sin embargo, en los ejercicios de biblioteca, veterinaria, nómina, tienda, universidad y figuras aparecen herramientas extra de Python que conviene entender con calma.
Esta sección F reúne esas piezas “intermedias” para que no copies código a ciegas: la idea es que ahora puedas leer, explicar, modificar y crear soluciones por tu cuenta.
La POO no vive aislada: se apoya en funciones integradas, módulos estándar, expresiones compactas y reglas especiales del lenguaje.
Si entiendes estas herramientas, tus clases dejan de ser “solo estructura” y empiezan a comportarse como sistemas completos.
if __name__ == "__main__": — Guardián de MóduloSirve para distinguir si un archivo Python fue ejecutado directamente o si solo fue importado desde otro archivo.
__name__?__name__ es una variable especial que Python crea automáticamente para cada archivo cargado como módulo.
"__main__".Porque muchas veces quieres que un archivo tenga funciones reutilizables y además un pequeño bloque de prueba o arranque.
El guardián evita que esas pruebas se ejecuten accidentalmente al importar el archivo desde otro módulo.
.py que Python puede ejecutar o importar.# archivo biblioteca.py
def cargar_libros(): # Método cargar_libros(): acción que puede realizar este objeto
return ["POO desde cero", "Diseño de clases", "Herencia útil"] # Retorna el valor calculado al lugar donde se llamó este método
def mostrar_catalogo(): # Método mostrar_catalogo(): acción que puede realizar este objeto
for titulo in cargar_libros(): # Itera sobre cada titulo en cargar_libros()
print(f"• {titulo}") # Muestra en consola el resultado de esta operación
if __name__ == "__main__": # Se ejecuta solo cuando este archivo se corre directamente (no cuando se importa)
print("Ejecutando prueba rápida del módulo biblioteca") # Muestra en consola el resultado de esta operación
mostrar_catalogo() # Completa la llamada o expresión iniciada anteriormente
# archivo app.py
from biblioteca import mostrar_catalogo # Importa mostrar_catalogo del módulo biblioteca
print("Bienvenido al sistema de biblioteca") # Muestra en consola el resultado de esta operación
mostrar_catalogo() # Completa la llamada o expresión iniciada anteriormente
# Al importar biblioteca.py NO se ejecuta el bloque protegido
# porque __name__ ya no vale "__main__".
| Parte | Qué significa |
|---|---|
if |
Evalúa una condición antes de ejecutar un bloque. |
__name__ |
Nombre especial que Python asigna al módulo actual. |
== |
Compara si ambos valores son exactamente iguales. |
"__main__" |
Nombre reservado del archivo que se está ejecutando como programa principal. |
| Escenario | Valor de __name__ |
¿Se ejecuta el bloque? |
|---|---|---|
Corres python biblioteca.py |
"__main__" |
Sí, porque el archivo es el punto de entrada. |
Haces import biblioteca |
"biblioteca" |
No, porque el archivo ahora es un módulo auxiliar. |
import, ese código también corre y ensucia la salida o modifica datos inesperadamente.
from abc import ABC, abstractmethod — Clases abstractasSe usan cuando quieres definir un molde obligatorio: una clase base que describe qué métodos deben existir, aunque todavía no sepas cómo se implementan en cada hija.
ABC?ABC significa Abstract Base Class (Clase Base Abstracta).
Cuando una clase hereda de ABC, puede declarar métodos abstractos que actúan como contrato.
@abstractmethod?Marca un método que debe ser implementado por las subclases concretas.
Si una hija no lo implementa, Python impide crear objetos de esa clase.
from abc import ABC, abstractmethod # Importa ABC, abstractmethod del módulo abc
import math # Importa el módulo math para usar sus funciones y clases
class Figura(ABC): # Figura hereda de ABC — recibe todos sus atributos y métodos
@abstractmethod # Obliga a las subclases a implementar el siguiente método
def area(self): # Método area(): acción que puede realizar este objeto
pass # Deja el bloque vacío porque la implementación se completará después
@abstractmethod # Obliga a las subclases a implementar el siguiente método
def perimetro(self): # Método perimetro(): acción que puede realizar este objeto
pass # Deja el bloque vacío porque la implementación se completará después
class Circulo(Figura): # Circulo hereda de Figura — recibe todos sus atributos y métodos
def __init__(self, radio): # Constructor: inicializa el objeto con los datos recibidos
self.radio = radio # Guarda radio como atributo de esta instancia
def area(self): # Método area(): acción que puede realizar este objeto
return math.pi * self.radio ** 2 # Retorna el valor calculado al lugar donde se llamó este método
def perimetro(self): # Método perimetro(): acción que puede realizar este objeto
return 2 * math.pi * self.radio # Retorna el valor calculado al lugar donde se llamó este método
| Fragmento | Función dentro del diseño |
|---|---|
from abc import ABC, abstractmethod |
Importa las herramientas necesarias desde el módulo estándar abc. |
class Figura(ABC) |
Declara que Figura es una base abstracta. |
@abstractmethod |
Marca un método como obligatorio para toda subclase concreta. |
pass |
Deja el bloque vacío porque la implementación real vivirá en las clases hijas. |
| Sin ABC | Con ABC |
|---|---|
Puedes olvidar implementar area() y el error aparece más tarde, durante la ejecución. |
Python detecta desde el inicio que la hija está incompleta y no deja instanciarla. |
| La clase base solo “sugiere” una estructura. | La clase base obliga a respetar esa estructura. |
| Es fácil crear hijos inconsistentes. | Las hijas quedan alineadas con el mismo contrato de trabajo. |
class Rectangulo(Figura): # Rectangulo hereda de Figura — recibe todos sus atributos y métodos
def __init__(self, base, altura): # Constructor: inicializa el objeto con los datos recibidos
self.base = base # Guarda base como atributo de esta instancia
self.altura = altura # Guarda altura como atributo de esta instancia
# Falta implementar perimetro()
def area(self): # Método area(): acción que puede realizar este objeto
return self.base * self.altura # Retorna el valor calculado al lugar donde se llamó este método
# rect = Rectangulo(4, 5)
# TypeError: Can't instantiate abstract class Rectangulo
# with abstract method perimetro
area() y perimetro(), aunque cada fórmula cambie.
isinstance(objeto, Clase) — Verificación de tipo en tiempo de ejecuciónPermite preguntar si un objeto pertenece a cierta clase o a alguna de sus clases hijas mientras el programa está corriendo.
isinstance(objeto, Clase) devuelve True o False.
También acepta una tupla de clases: isinstance(x, (Perro, Gato)).
Su mayor ventaja es que entiende la jerarquía de herencia.
Si Perro hereda de Mascota, entonces un perro también “cuenta” como Mascota.
Animal.class Animal: # Define la clase Animal
pass # Deja el bloque vacío porque la implementación se completará después
class Mascota(Animal): # Mascota hereda de Animal — recibe todos sus atributos y métodos
pass # Deja el bloque vacío porque la implementación se completará después
class Perro(Mascota): # Perro hereda de Mascota — recibe todos sus atributos y métodos
pass # Deja el bloque vacío porque la implementación se completará después
paciente = Perro() # Asigna un valor inicial o calculado a paciente
print(isinstance(paciente, Perro)) # Muestra en consola el resultado de esta operación
print(isinstance(paciente, Mascota)) # Muestra en consola el resultado de esta operación
print(isinstance(paciente, Animal)) # Muestra en consola el resultado de esta operación
print(isinstance(paciente, object)) # Muestra en consola el resultado de esta operación
| Expresión | Resultado | Razón |
|---|---|---|
isinstance(paciente, Perro) |
True |
El objeto fue creado exactamente desde Perro. |
isinstance(paciente, Mascota) |
True |
Perro hereda de Mascota. |
isinstance(paciente, Animal) |
True |
Toda la cadena de herencia cuenta. |
isinstance(paciente, object) |
True |
En Python, casi todo hereda finalmente de object. |
# MENOS FLEXIBLE
if type(paciente) == Mascota: # Verifica type(paciente) == Mascota antes de continuar
print("Es mascota") # Muestra en consola el resultado de esta operación
# MEJOR
if isinstance(paciente, Mascota): # Verifica isinstance(paciente, Mascota) antes de continuar
print("Es mascota") # Muestra en consola el resultado de esta operación
| Opción | Comportamiento |
|---|---|
type(obj) == Clase |
Solo funciona cuando el tipo exacto coincide; ignora herencia. |
isinstance(obj, Clase) |
Reconoce el tipo exacto y también cualquier relación válida de herencia. |
isinstance() es la herramienta idiomática.
type() y luego sorprenderse porque una subclase no entra en la condición. Ese enfoque rompe la flexibilidad natural de la herencia.
import math — Módulo matemáticoEl módulo math reúne operaciones y constantes matemáticas listas para usar, sin reinventar fórmulas manualmente.
Porque ofrece resultados correctos, nombres claros y funciones que ya vienen optimizadas en Python.
math.pi para el valor de π.math.sqrt(x) para raíz cuadrada.math.pow(a, b) para elevar potencias.En el ejercicio de figuras, math.pi hace más legible el cálculo del círculo y sqrt() ayuda con diagonales o distancias derivadas.
pi.import math # Importa el módulo math para usar sus funciones y clases
class Circulo: # Define la clase Circulo
def __init__(self, radio): # Constructor: inicializa el objeto con los datos recibidos
self.radio = radio # Guarda radio como atributo de esta instancia
def area(self): # Método area(): acción que puede realizar este objeto
return math.pi * math.pow(self.radio, 2) # Retorna el valor calculado al lugar donde se llamó este método
def diametro(self): # Método diametro(): acción que puede realizar este objeto
return self.radio * 2 # Retorna el valor calculado al lugar donde se llamó este método
def diagonal_de_caja(self): # Método diagonal_de_caja(): acción que puede realizar este objeto
lado = self.diametro() # Asigna un valor inicial o calculado a lado
return math.sqrt(lado ** 2 + lado ** 2) # Retorna el valor calculado al lugar donde se llamó este método
| Herramienta | Qué hace | Ejemplo típico |
|---|---|---|
math.pi |
Constante π para círculos y trigonometría. | math.pi * radio ** 2 |
math.sqrt(x) |
Devuelve la raíz cuadrada de x. |
math.sqrt(49) → 7.0 |
math.pow(a, b) |
Eleva a a la potencia b. |
math.pow(5, 2) → 25.0 |
math.ceil(x) |
Redondea hacia arriba. | math.ceil(3.1) → 4 |
math.floor(x) |
Redondea hacia abajo. | math.floor(3.9) → 3 |
math.pow() y cuándo **?| Forma | Lectura |
|---|---|
radio ** 2 |
Más común y directa para exponentes sencillos. |
math.pow(radio, 2) |
Útil cuando quieres resaltar visualmente que estás llamando una función matemática. |
math mejora la legibilidad. Cuando otro estudiante lee math.sqrt(), entiende de inmediato que hay una raíz cuadrada, sin descifrar fórmulas improvisadas.
pi o sqrt() sin importar antes el módulo. Si no haces import math, Python no sabe de dónde salen esos nombres.
Ya conoces la f-string básica. Ahora toca aprender la parte que vuelve elegantes los reportes: alineación, decimales y separadores de miles.
Permiten controlar cómo se ve un valor al imprimirlo, sin cambiar el valor original.
:.2f fija dos decimales.:>14 alinea a la derecha en 14 espacios.:<19 alinea a la izquierda en 19 espacios.:,.2f agrega comas y dos decimales.Son ideales para tickets, planillas, facturas y recibos, donde las columnas deben verse ordenadas aunque los datos tengan longitudes distintas.
{total:>14,.2f}| Parte | Significado |
|---|---|
total |
La variable que quieres mostrar. |
: |
Indica que a partir de ahí viene la especificación de formato. |
>14 |
Alinea a la derecha y reserva 14 caracteres. |
, |
Agrega separador de miles. |
.2f |
Muestra exactamente 2 decimales en formato flotante. |
nombre = "Ana López" # Asigna un valor inicial o calculado a nombre
total = 15432.5 # Asigna un valor inicial o calculado a total
print(f"Empleado: {nombre:<19}") # Muestra en consola el resultado de esta operación
print(f"Total bruto: {total:.2f}") # Muestra en consola el resultado de esta operación
print(f"Total alineado: {total:>14}") # Muestra en consola el resultado de esta operación
print(f"Total con miles: {total:,.2f}") # Muestra en consola el resultado de esta operación
print(f"Columna completa: {total:>14,.2f}") # Muestra en consola el resultado de esta operación
| Especificador | Resultado esperado | Uso típico |
|---|---|---|
{precio:.2f} |
1450.50 | Monedas y promedios. |
{nombre:>14} |
Texto a la derecha en 14 espacios | Columnas numéricas o etiquetas cortas. |
{nombre:<19} |
Texto a la izquierda en 19 espacios | Listados y reportes tabulares. |
{valor:,.2f} |
12,540.75 | Montos grandes en nómina o ventas. |
{valor:>14,.2f} |
Valor alineado, con miles y decimales | Recibos profesionales y tablas impresas. |
# SIN FORMATO
print(f"Laptop {15432.5}") # Muestra en consola el resultado de esta operación
print(f"Mouse {250}") # Muestra en consola el resultado de esta operación
# CON FORMATO
print(f"{'Laptop':<19} L {15432.5:>14,.2f}") # Muestra en consola el resultado de esta operación
print(f"{'Mouse':<19} L {250:>14,.2f}") # Muestra en consola el resultado de esta operación
15432.5 sigue siendo el mismo número; simplemente decides mostrarlo como 15,432.50 para que el usuario lo lea mejor.
:>14 “convierte” el número. No lo convierte; solo reserva espacio visual. El tipo del dato sigue siendo el mismo hasta que lo imprimes.
generator expressions)Son expresiones compactas que producen valores uno por uno, bajo demanda, sin construir una lista completa desde el inicio.
(expresion for variable in iterable)
Se parecen a las listas por comprensión, pero usan paréntesis y trabajan de forma perezosa (lazy).
No calculan todo de golpe. Van entregando cada valor cuando otra función lo necesita, por ejemplo sum().
# carrito.__items contiene objetos Producto
total = sum(item.get_precio() for item in carrito._Carrito__items) # Asigna un valor inicial o calculado a total
# equivalente más largo
total = 0 # Asigna un valor inicial o calculado a total
for item in carrito._Carrito__items: # Itera sobre cada item en carrito._Carrito__items
total += item.get_precio() # Completa la llamada o expresión iniciada anteriormente
sum(p.get_precio() for p in items)| Parte | Papel |
|---|---|
sum(...) |
Recibe una secuencia de números y los acumula. |
p.get_precio() |
Transforma cada objeto p en un valor numérico. |
for p in items |
Recorre todos los productos del carrito. |
(...) |
Indica que es un generador y no una lista creada por adelantado. |
| Forma | Característica | Ejemplo |
|---|---|---|
[x.area() for x in figuras] |
Crea una lista completa en memoria. | Útil si necesitas reutilizar los resultados varias veces. |
(x.area() for x in figuras) |
Produce valores uno por uno. | Ideal cuando otra función consume el resultado inmediatamente. |
# universidad: promedio de créditos aprobados
creditos_aprobados = sum(curso.creditos for curso in cursos if curso.aprobado) # Asigna un valor inicial o calculado a creditos_aprobados
# nómina: total de pagos
pago_total = sum(empleado.calcular_pago() for empleado in empleados) # Asigna un valor inicial o calculado a pago_total
sum(), max(), min() o any(). Te dejan escribir “qué dato quieres” sin montar manualmente una lista intermedia.
sum(), tendrás que crearlo otra vez.
lambdaUna lambda es una función anónima y corta, pensada para usarse justo en el lugar donde se necesita.
lambda parametros: expresion
No lleva nombre y su cuerpo debe ser una sola expresión.
Cuando necesitas una función muy pequeña y de uso inmediato, por ejemplo para decirle a max() cuál valor comparar.
# figuras: encontrar la de mayor área
mayor = max(figuras, key=lambda f: f.area())
# veterinaria: ordenar pacientes por peso
pacientes_ordenados = sorted(pacientes, key=lambda p: p.peso)
# universidad: alumno con mejor promedio
mejor = max(estudiantes, key=lambda e: e.promedio())
| Forma | Lectura | Cuándo usarla |
|---|---|---|
lambda x: x.area() |
“Dado x, devuelve su área”. |
Cuando solo necesitas una función breve para una sola llamada. |
def criterio(x): return x.area() |
Versión con nombre del mismo criterio. | Cuando el criterio se reutiliza o merece un nombre descriptivo. |
lambda f: f.area()| Parte | Interpretación |
|---|---|
lambda |
Le dice a Python que vas a crear una función anónima. |
f |
Parámetro temporal que representa cada objeto recibido. |
: |
Separa los parámetros de la expresión a devolver. |
f.area() |
Valor que la lambda calcula y devuelve automáticamente. |
# CON lambda
mayor = max(figuras, key=lambda f: f.area()) # Asigna un valor inicial o calculado a mayor
# SIN lambda
def obtener_area(figura): # Método obtener_area(): acción que puede realizar este objeto
return figura.area() # Retorna el valor calculado al lugar donde se llamó este método
mayor = max(figuras, key=obtener_area) # Asigna un valor inicial o calculado a mayor
lambda no es “más poderosa” que def; simplemente es más cómoda cuando el criterio es corto y no vale la pena bautizarlo con un nombre.
lambda. Si la expresión ya no es clara de leer, mejor crea una función normal con def.
sum(), max(), min() y el parámetro key=Estas funciones integradas trabajan sobre iterables. sum() acumula números; max() y min() buscan extremos; y key= permite decirles qué criterio usar.
sum(iterable) suma números.max(iterable) obtiene el mayor.min(iterable) obtiene el menor.max(iterable, key=funcion) compara usando un criterio.key=?Recibe una función que transforma cada objeto en el valor por el que se va a comparar.
Normalmente se usa con lambda, aunque cualquier función sirve.
key=; para “sumar por criterio” primero transformas con un generador.key=.key=.# tienda: total de la compra
total = sum(producto.get_precio() for producto in items) # Asigna un valor inicial o calculado a total
# figuras: la de mayor área
figura_mayor = max(figuras, key=lambda f: f.area()) # Asigna un valor inicial o calculado a figura_mayor
# universidad: el alumno con menor promedio
en_riesgo = min(estudiantes, key=lambda e: e.promedio()) # Asigna un valor inicial o calculado a en_riesgo
| Función | ¿Acepta key=? |
Uso correcto |
|---|---|---|
sum() |
No | sum(x.precio for x in items) |
max() |
Sí | max(figuras, key=lambda f: f.area()) |
min() |
Sí | min(empleados, key=lambda e: e.salario) |
# SIN key=
mayor = max([10, 25, 7]) # Asigna un valor inicial o calculado a mayor
# CON key= sobre objetos
mayor_salario = max(empleados, key=lambda e: e.calcular_pago()) # Asigna un valor inicial o calculado a mayor_salario
# sum() transforma primero y suma después
pago_total = sum(e.calcular_pago() for e in empleados) # Asigna un valor inicial o calculado a pago_total
key=| Paso | Qué ocurre |
|---|---|
| 1 | Toma un objeto del iterable. |
| 2 | Le aplica la función definida en key=. |
| 3 | Compara el valor devuelto, no el objeto completo. |
| 4 | Retorna el objeto original que produjo el máximo o mínimo. |
key= es como ponerle a cada objeto una etiqueta numérica temporal para compararlo. Python no destruye el objeto; solo usa esa etiqueta para decidir cuál gana.
sum() no acepta key=. Si escribes sum(items, key=...), obtendrás error. Primero extrae los números con un generador y luego suma.
append() y join()En varios ejercicios guardas objetos en listas y luego conviertes esas listas en texto para mostrar recibos o reportes.
append()lista.append(elemento) agrega un solo elemento al final de la lista.
Es ideal para carritos, historiales, matrículas y colecciones internas del objeto.
join()"separador".join(lista_de_textos) une muchas cadenas en una sola.
Es perfecto para generar recibos línea por línea y luego convertirlos en un bloque final.
class Carrito: # Define la clase Carrito
def __init__(self): # Constructor: inicializa el objeto con los datos recibidos
self._Carrito__items = [] # Guarda _Carrito__items como atributo protegido de la instancia
def agregar_producto(self, producto): # Método agregar_producto(): acción que puede realizar este objeto
self._Carrito__items.append(producto) # Agrega un elemento al final de self._Carrito__items
def generar_recibo(self): # Método generar_recibo(): acción que puede realizar este objeto
lineas = [] # Crea una lista vacía para almacenar datos en lineas
for producto in self._Carrito__items: # Itera sobre cada producto en self._Carrito__items
lineas.append(f"{producto.nombre:<19} L {producto.get_precio():>10,.2f}") # Agrega un elemento al final de lineas
return "\n".join(lineas) # Retorna el valor calculado al lugar donde se llamó este método
| Método | Entrada esperada | Resultado |
|---|---|---|
append() |
Un elemento cualquiera | La lista crece en un elemento más al final. |
"\n".join(...) |
Lista de cadenas | Devuelve una sola cadena con saltos de línea entre cada parte. |
| Paso | Acción |
|---|---|
| 1 | Creas una lista vacía llamada lineas. |
| 2 | En cada iteración haces lineas.append(...). |
| 3 | Al final unes todas las líneas con "\n".join(lineas). |
| 4 | Obtienes un solo texto listo para imprimir o retornar. |
# INCORRECTO
lineas.join("\n")
# CORRECTO
"\n".join(lineas)
append() construye la colección; join() construye la presentación final. En otras palabras: primero guardas piezas, luego las pegas en un solo texto.
list.join(). En Python, join() pertenece al string separador, por eso escribimos ", ".join(lista) y no lista.join(", ").
__class__.__name__ — IntrospecciónLa introspección es la capacidad de un programa para examinarse a sí mismo: descubrir tipos, nombres y estructura en tiempo de ejecución.
obj.__class__ da la clase real del objeto.obj.__class__.__name__ da el nombre de esa clase como texto.Para crear descripciones automáticas, mensajes genéricos, depuración y reportes sin escribir el nombre de la clase “a mano”.
class Figura(ABC): # Figura hereda de ABC — recibe todos sus atributos y métodos
@abstractmethod # Obliga a las subclases a implementar el siguiente método
def area(self): # Método area(): acción que puede realizar este objeto
pass # Deja el bloque vacío porque la implementación se completará después
def describir(self): # Método describir(): acción que puede realizar este objeto
return f"Soy un objeto de tipo {self.__class__.__name__}" # Retorna el valor calculado al lugar donde se llamó este método
class Triangulo(Figura): # Triangulo hereda de Figura — recibe todos sus atributos y métodos
def area(self): # Método area(): acción que puede realizar este objeto
return 0 # Retorna el valor calculado al lugar donde se llamó este método
figura = Triangulo() # Asigna un valor inicial o calculado a figura
print(figura.describir()) # Muestra en consola el resultado de esta operación
self.__class__.__name__| Parte | Qué aporta |
|---|---|
self |
La instancia actual. |
self.__class__ |
La clase concreta desde la que fue creada esa instancia. |
self.__class__.__name__ |
El nombre de esa clase convertido en texto, por ejemplo "Triangulo". |
| Objeto | obj.__class__ |
obj.__class__.__name__ |
|---|---|---|
Circulo(5) |
<class 'Circulo'> |
"Circulo" |
Rectangulo(4, 8) |
<class 'Rectangulo'> |
"Rectangulo" |
Estudiante(...) |
<class 'Estudiante'> |
"Estudiante" |
# SIN introspección
def describir(self): # Método describir(): acción que puede realizar este objeto
return "Soy una figura" # Retorna el valor calculado al lugar donde se llamó este método
# CON introspección
def describir(self): # Método describir(): acción que puede realizar este objeto
return f"Soy un objeto de tipo {self.__class__.__name__}" # Retorna el valor calculado al lugar donde se llamó este método
"Circulo", dejas que cada objeto revele su identidad automáticamente.
__class__.__name__, el texto se mantiene correcto.
| Herramienta | Para qué te servirá en los ejercicios |
|---|---|
if __name__ == "__main__" |
Separar pruebas locales de código reutilizable. |
ABC + @abstractmethod |
Obligar a las clases hijas a cumplir un contrato común. |
isinstance() |
Validar tipos sin romper la herencia. |
math |
Resolver cálculos claros y confiables en figuras. |
| Formato en f-strings | Presentar tickets, nóminas y reportes alineados. |
| Generadores | Sumar o recorrer datos sin crear listas innecesarias. |
lambda |
Definir criterios breves de comparación u ordenamiento. |
sum(), max(), min() |
Calcular totales y encontrar extremos con poco código. |
append() y join() |
Construir colecciones y luego convertirlas en texto. |
__class__.__name__ |
Describir objetos dinámicamente mediante introspección. |
get y set — Getters y Setters
Cuando los atributos de una clase son privados (comienzan con __), el código externo no puede leerlos ni cambiarlos directamente. Los getters y setters son métodos públicos que abren una ventana controlada hacia esos datos privados: los getters los leen y los setters los modifican con validación.
🚫 Acceso directo — PROHIBIDO
Como intentar abrir la caja fuerte con un mazo. Viola el encapsulamiento; si alguien escribe cuenta.__saldo = -99999, nadie lo valida.
✅ Getter — leer a través de ventanilla
El empleado (getter) te muestra el saldo cuando lo pides: cuenta.get_saldo(). El dato sale pero el original permanece intacto.
✅ Setter — depositar con control
Solo puedes modificar el saldo si cumples las reglas: cuenta.set_saldo(500) valida que el valor sea positivo antes de guardarlo.
class CuentaBancaria: # Define la clase CuentaBancaria
def __init__(self, titular, saldo): # Constructor: inicializa el objeto con los datos recibidos
self.__titular = titular # __ = privado: nadie accede desde fuera
self.__saldo = saldo # __ = privado: solo accessible por get/set
# ── GETTER ────────────────────────────────────────────
def get_titular(self): # Nombre: get_ + nombre del atributo (convención)
return self.__titular # Solo devuelve — no modifica nada
def get_saldo(self): # Getter: devuelve el valor de saldo
return self.__saldo # Retorna el valor calculado al lugar donde se llamó este método
# ── SETTER ────────────────────────────────────────────
def set_saldo(self, nuevo_saldo): # Nombre: set_ + nombre del atributo
if nuevo_saldo >= 0: # VALIDACIÓN: regla de negocio antes de guardar
self.__saldo = nuevo_saldo # Solo modifica si pasa la validación
else: # Se ejecuta si la condición anterior no se cumplió
print("⚠️ Saldo no puede ser negativo") # Rechaza el valor inválido
def __str__(self): # Define cómo se muestra el objeto al usar print()
return f"[{self.__titular}] Saldo: ${self.__saldo:,.2f}" # Retorna el valor calculado al lugar donde se llamó este método
# ── USO ────────────────────────────────────────────────
cuenta = CuentaBancaria("Ana López", 1000) # Asigna un valor inicial o calculado a cuenta
print(cuenta.get_titular()) # → "Ana López" (getter lee el dato)
print(cuenta.get_saldo()) # → 1000 (getter lee el dato)
cuenta.set_saldo(1500) # ✅ válido: 1500 >= 0 → se actualiza
cuenta.set_saldo(-200) # ⚠️ rechazado: imprime advertencia, saldo queda en 1500
print(cuenta.get_saldo()) # → 1500 (el saldo negativo fue bloqueado)
print(cuenta) # → [Ana López] Saldo: $1,500.00
❌ Sin encapsulamiento — peligroso
class Producto: # Define la clase Producto
def __init__(self, precio): # Constructor: inicializa el objeto con los datos recibidos
self.precio = precio # público
p = Producto(100) # Asigna un valor inicial o calculado a p
p.precio = -500 # ⚠️ nadie lo valida
p.precio = "gratis" # ⚠️ tipo incorrecto
print(p.precio) # → "gratis" 💥
El atributo público puede recibir cualquier valor sin control: números negativos, strings, None. Errores silenciosos que explotan en otro lugar del código.
✅ Con getter/setter — controlado
class Producto: # Define la clase Producto
def __init__(self, precio): # Constructor: inicializa el objeto con los datos recibidos
self.__precio = precio # privado
def get_precio(self): # Getter: devuelve el valor de precio
return self.__precio # Retorna el valor calculado al lugar donde se llamó este método
def set_precio(self, v): # Setter: valida y actualiza el atributo precio
if isinstance(v, (int, float)) and v > 0: # Verifica isinstance(v, (int, float)) and v > 0 antes de continuar
self.__precio = v # ✅ válido
else: # Se ejecuta si la condición anterior no se cumplió
print("Precio inválido") # Muestra en consola el resultado de esta operación
Ahora el setter valida tipo (número) y rango (positivo) antes de guardar. La clase controla sus propios datos — eso es encapsulamiento real.
| Situación | ¿Getter? | ¿Setter? | Razón |
|---|---|---|---|
| Dato que otros necesitan leer | ✅ Sí | — | Sin getter nadie puede mostrar el dato |
| Dato que puede cambiar con reglas | ✅ Sí | ✅ Sí | Setter valida antes de modificar |
| Dato calculado (no almacenado) | ✅ Sí | ❌ No | Ej: get_total() calcula y devuelve; no hay nada que "guardar" |
| Dato inmutable (ID, ISBN, matrícula) | ✅ Sí | ❌ No | Solo se asigna en __init__, nunca cambia |
| Dato completamente interno | ❌ No | ❌ No | Nadie fuera de la clase debe ver ni tocar ese dato |
En el ejercicio de Biblioteca, la clase Libro tiene atributos privados __titulo, __autor e __isbn. Solo se definen getters (sin setters) porque estos datos no deben cambiar tras crearse el libro.
class Libro: # Define la clase Libro
def __init__(self, titulo, autor, isbn): # Constructor: inicializa el objeto con los datos recibidos
self.__titulo = titulo # privado — nunca debe cambiar
self.__autor = autor # Guarda __autor como atributo privado de la instancia
self.__isbn = isbn # Guarda __isbn como atributo privado de la instancia
# Solo GETTERS — sin setters porque titulo/autor/isbn son inmutables
def get_titulo(self): return self.__titulo # ← getter
def get_autor(self): return self.__autor # ← getter
def get_isbn(self): return self.__isbn # ← getter
# ── Por qué no hay set_titulo() ──────────────────────────
# Un libro NO cambia su título después de imprimirse.
# Si no hay setter → el atributo es de solo lectura.
libro = Libro("POO con Python", "J. García", "978-001") # Asigna un valor inicial o calculado a libro
print(libro.get_titulo()) # → "POO con Python"
# libro.__titulo = "Hacking" # ❌ AttributeError — privado
# libro.set_titulo("Hacking") # ❌ no existe el setter
| Elemento | Sintaxis | ¿Puede leer? | ¿Puede escribir? | ¿Valida? |
|---|---|---|---|---|
| Atributo público | self.nombre |
✅ | ✅ | ❌ Nunca |
| Atributo privado directo | self.__nombre |
✅ (solo dentro) | ✅ (solo dentro) | ❌ |
| Getter | def get_x(self): return self.__x |
✅ Desde fuera | ❌ | — |
| Setter | def set_x(self, v): if ... self.__x=v |
❌ | ✅ Con control | ✅ Siempre |
get_nombreAtributo().set_nombreAtributo(valor).__init__ y no tiene setter — como ISBN o matrícula.selfx en get_x y set_x debe coincidir con el atributo privado __xdef set_saldo(self, nuevo_saldo) y dentro self.__saldo = nuevo_saldo. Si usas el mismo nombre — def set_saldo(self, __saldo) — Python lo trata como variable local y el atributo interno nunca se actualiza.
from abc import ABC, abstractmethodUna clase abstracta es una clase que define un contrato: declara qué métodos deben existir en todas sus subclases, pero no los implementa. Nadie puede crear un objeto directamente de una clase abstracta; solo sus subclases concretas (que implementen todos sus métodos abstractos) pueden instanciarse. En Python esto se logra con el módulo estándar abc.
ABC — clase base que activa el mecanismo abstracto.@abstractmethod — decorador que marca un método como obligatorio en subclases.TypeError al intentar instanciarla.abc.ABC)El hotel ofrece distintos servicios: habitación, restaurante y spa. Todos deben poder calcular su costo y mostrar una descripción. Usamos una clase abstracta para garantizar que cualquier servicio nuevo que se agregue en el futuro respete esa interfaz.
| Clase | Tipo | Métodos abstractos que debe implementar |
|---|---|---|
Servicio | Abstracta (ABC) | calcular_costo(), descripcion() |
Habitacion | Concreta | ✅ implementa ambos |
Restaurante | Concreta | ✅ implementa ambos |
Spa | Concreta | ✅ implementa ambos |
# ── Importación del módulo abc ──────────────────────────────────────────
from abc import ABC, abstractmethod # Importa ABC, abstractmethod del módulo abc
# ── Clase abstracta: define el CONTRATO ─────────────────────────────────
class Servicio(ABC): # hereda de ABC para activar el mecanismo
"""Clase base abstracta para todos los servicios del hotel.""" # Continúa la lógica del ejemplo paso a paso
def __init__(self, nombre: str): # Constructor: inicializa el objeto con los datos recibidos
self.__nombre = nombre # atributo privado compartido
def get_nombre(self) -> str: # Getter: devuelve el valor de nombre
return self.__nombre # Retorna el valor calculado al lugar donde se llamó este método
@abstractmethod # ← método OBLIGATORIO en subclases
def calcular_costo(self) -> float: # Método calcular_costo(): acción que puede realizar este objeto
"""Retorna el costo total del servicio.""" # Continúa la lógica del ejemplo paso a paso
... # el cuerpo puede ser ... o pass
@abstractmethod # ← otro método OBLIGATORIO
def descripcion(self) -> str: # Método descripcion(): acción que puede realizar este objeto
"""Retorna una descripción legible del servicio.""" # Continúa la lógica del ejemplo paso a paso
... # Continúa la lógica del ejemplo paso a paso
def recibo(self) -> str: # método CONCRETO: usa los abstractos
return ( # Retorna el valor calculado al lugar donde se llamó este método
f" Servicio : {self.get_nombre()}\n" # Continúa la lógica del ejemplo paso a paso
f" Detalle : {self.descripcion()}\n" # Continúa la lógica del ejemplo paso a paso
f" Costo : ${self.calcular_costo():.2f}" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
# ── Clase concreta 1: Habitacion ─────────────────────────────────────────
class Habitacion(Servicio): # Habitacion hereda de Servicio — recibe todos sus atributos y métodos
def __init__(self, numero: int, noches: int, tarifa_noche: float): # Constructor: inicializa el objeto con los datos recibidos
super().__init__(f"Habitación {numero}") # Llama al constructor de la clase padre para inicializar atributos heredados
self.__noches = noches # Guarda __noches como atributo privado de la instancia
self.__tarifa_noche = tarifa_noche # Guarda __tarifa_noche como atributo privado de la instancia
def calcular_costo(self) -> float: # ✅ implementa contrato
return self.__noches * self.__tarifa_noche # Retorna el valor calculado al lugar donde se llamó este método
def descripcion(self) -> str: # ✅ implementa contrato
return f"{self.__noches} noche(s) × ${self.__tarifa_noche:.2f}/noche" # Retorna el valor calculado al lugar donde se llamó este método
# ── Clase concreta 2: Restaurante ────────────────────────────────────────
class Restaurante(Servicio): # Restaurante hereda de Servicio — recibe todos sus atributos y métodos
def __init__(self, comensales: int, precio_cubierto: float): # Constructor: inicializa el objeto con los datos recibidos
super().__init__("Restaurante del Hotel") # Llama al constructor de la clase padre para inicializar atributos heredados
self.__comensales = comensales # Guarda __comensales como atributo privado de la instancia
self.__precio_cubierto = precio_cubierto # Guarda __precio_cubierto como atributo privado de la instancia
def calcular_costo(self) -> float: # Método calcular_costo(): acción que puede realizar este objeto
return self.__comensales * self.__precio_cubierto # Retorna el valor calculado al lugar donde se llamó este método
def descripcion(self) -> str: # Método descripcion(): acción que puede realizar este objeto
return f"{self.__comensales} comensal(es) × ${self.__precio_cubierto:.2f}/cubierto" # Retorna el valor calculado al lugar donde se llamó este método
# ── Clase concreta 3: Spa ────────────────────────────────────────────────
class Spa(Servicio): # Spa hereda de Servicio — recibe todos sus atributos y métodos
def __init__(self, tratamiento: str, duracion_min: int, tarifa_hora: float): # Constructor: inicializa el objeto con los datos recibidos
super().__init__("Spa & Bienestar") # Llama al constructor de la clase padre para inicializar atributos heredados
self.__tratamiento = tratamiento # Guarda __tratamiento como atributo privado de la instancia
self.__duracion_min = duracion_min # Guarda __duracion_min como atributo privado de la instancia
self.__tarifa_hora = tarifa_hora # Guarda __tarifa_hora como atributo privado de la instancia
def calcular_costo(self) -> float: # Método calcular_costo(): acción que puede realizar este objeto
return (self.__duracion_min / 60) * self.__tarifa_hora # Retorna el valor calculado al lugar donde se llamó este método
def descripcion(self) -> str: # Método descripcion(): acción que puede realizar este objeto
return f"{self.__tratamiento} — {self.__duracion_min} min a ${self.__tarifa_hora:.2f}/h" # Retorna el valor calculado al lugar donde se llamó este método
# ── Uso: polimorfismo con lista ──────────────────────────────────────────
servicios = [ # Asigna un valor inicial o calculado a servicios
Habitacion(101, 3, 1200.00), # Continúa la lógica del ejemplo paso a paso
Restaurante(2, 350.00), # Continúa la lógica del ejemplo paso a paso
Spa("Masaje sueco", 90, 800.00), # Continúa la lógica del ejemplo paso a paso
] # Cierra la estructura abierta en las líneas anteriores
print("═══ FACTURA DE SERVICIOS ═══") # Muestra en consola el resultado de esta operación
total = 0.0 # Asigna un valor inicial o calculado a total
for s in servicios: # Itera sobre cada s en servicios
print(s.recibo()) # Muestra en consola el resultado de esta operación
print(" ─────────────────────") # Muestra en consola el resultado de esta operación
total += s.calcular_costo() # Completa la llamada o expresión iniciada anteriormente
print(f" TOTAL A PAGAR: ${total:.2f}") # Muestra en consola el resultado de esta operación
# ── ¿Qué pasa si intentamos instanciar Servicio directamente? ────────────
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
mal = Servicio("Prueba") # ❌ TypeError — es abstracta
except TypeError as e: # Captura el error TypeError as e si ocurre en el try
print(f"\nError esperado: {e}") # Muestra en consola el resultado de esta operación
═══ FACTURA DE SERVICIOS ═══ Servicio : Habitación 101 Detalle : 3 noche(s) × $1200.00/noche Costo : $3600.00 ───────────────────── Servicio : Restaurante del Hotel Detalle : 2 comensal(es) × $350.00/cubierto Costo : $700.00 ───────────────────── Servicio : Spa & Bienestar Detalle : Masaje sueco — 90 min a $800.00/h Costo : $1200.00 ───────────────────── TOTAL A PAGAR: $5500.00 Error esperado: Can't instantiate abstract class Servicio without an implementation for abstract methods 'calcular_costo', 'descripcion'
| Línea / Elemento | Explicación |
|---|---|
from abc import ABC, abstractmethod |
Importa el mecanismo abstracto del módulo estándar de Python. No necesitas instalar nada. |
class Servicio(ABC): |
Al heredar de ABC, Servicio se convierte en clase abstracta. Intentar instanciarla directamente lanzará TypeError. |
@abstractmethod |
Decorador que marca el método como abstracto. La subclase debe sobreescribirlo o no podrá instanciarse. |
def recibo(self) en Servicio |
Método concreto en la clase abstracta — se hereda tal cual. Llama a calcular_costo() y descripcion() aunque no estén definidos aquí; cada subclase los provee. |
super().__init__(...) |
Llama al constructor de Servicio desde la subclase para inicializar el atributo __nombre. |
for s in servicios: |
Polimorfismo en acción: el mismo for llama a recibo() sobre objetos de tipos distintos; Python despacha el método correcto para cada uno. |
recibo() está definido una sola vez en la clase abstracta y ya funciona para todos los servicios presentes y futuros. Esto es el principio Open/Closed: abierto para extender (crear un nuevo servicio), cerrado para modificar (no tocas recibo()).
ABC ni abstractmethodAntes de que existiera (o se conociera) el módulo abc, los desarrolladores simulaban el comportamiento abstracto usando raise NotImplementedError. El resultado funcional es similar, pero las diferencias son importantes.
# ── Sin importar abc — el "contrato" es una convención, no una ley ───────
class Servicio: # clase normal, NO hereda de ABC
"""Clase base 'pseudo-abstracta' — sin mecanismo real de Python.""" # Continúa la lógica del ejemplo paso a paso
def __init__(self, nombre: str): # Constructor: inicializa el objeto con los datos recibidos
self.__nombre = nombre # Guarda __nombre como atributo privado de la instancia
def get_nombre(self) -> str: # Getter: devuelve el valor de nombre
return self.__nombre # Retorna el valor calculado al lugar donde se llamó este método
def calcular_costo(self) -> float: # Método calcular_costo(): acción que puede realizar este objeto
# En lugar de @abstractmethod, lanzamos el error manualmente
raise NotImplementedError( # Lanza una excepción para señalar un error y detener el flujo normal
f"{self.__class__.__name__} debe implementar 'calcular_costo()'" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
def descripcion(self) -> str: # Método descripcion(): acción que puede realizar este objeto
raise NotImplementedError( # Lanza una excepción para señalar un error y detener el flujo normal
f"{self.__class__.__name__} debe implementar 'descripcion()'" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
def recibo(self) -> str: # Método recibo(): acción que puede realizar este objeto
return ( # Retorna el valor calculado al lugar donde se llamó este método
f" Servicio : {self.get_nombre()}\n" # Continúa la lógica del ejemplo paso a paso
f" Detalle : {self.descripcion()}\n" # Continúa la lógica del ejemplo paso a paso
f" Costo : ${self.calcular_costo():.2f}" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
# Las subclases son idénticas al ejemplo anterior ─────────────────────────
class Habitacion(Servicio): # Habitacion hereda de Servicio — recibe todos sus atributos y métodos
def __init__(self, numero, noches, tarifa_noche): # Constructor: inicializa el objeto con los datos recibidos
super().__init__(f"Habitación {numero}") # Llama al constructor de la clase padre para inicializar atributos heredados
self.__noches = noches # Guarda __noches como atributo privado de la instancia
self.__tarifa_noche = tarifa_noche # Guarda __tarifa_noche como atributo privado de la instancia
def calcular_costo(self): # Método calcular_costo(): acción que puede realizar este objeto
return self.__noches * self.__tarifa_noche # Retorna el valor calculado al lugar donde se llamó este método
def descripcion(self): # Método descripcion(): acción que puede realizar este objeto
return f"{self.__noches} noche(s) × ${self.__tarifa_noche:.2f}/noche" # Retorna el valor calculado al lugar donde se llamó este método
# ── DIFERENCIA CLAVE: ¿qué pasa si NO implementamos descripcion? ─────────
class ServicioIncompleto(Servicio): # olvida implementar ambos métodos
def __init__(self): # Constructor: inicializa el objeto con los datos recibidos
super().__init__("Incompleto") # Llama al constructor de la clase padre para inicializar atributos heredados
# Sin ABC: Python permite crear el objeto sin error ──────────────────────
obj_malo = ServicioIncompleto() # ⚠️ NO lanza error aquí...
print("Objeto creado sin error (sin ABC).") # Muestra en consola el resultado de esta operación
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
obj_malo.calcular_costo() # ❌ el error llega TARDE, al llamar el método
except NotImplementedError as e: # Captura el error NotImplementedError as e si ocurre en el try
print(f"Error tardío: {e}") # Muestra en consola el resultado de esta operación
# Uso normal — igual que con ABC ─────────────────────────────────────────
h = Habitacion(202, 2, 950.00) # Asigna un valor inicial o calculado a h
print(h.recibo()) # Muestra en consola el resultado de esta operación
Objeto creado sin error (sin ABC). Error tardío: ServicioIncompleto debe implementar 'calcular_costo()' Servicio : Habitación 202 Detalle : 2 noche(s) × $950.00/noche Costo : $1900.00
ServicioIncompleto() se crea sin ningún error. El error solo aparece cuando intentas usar el método. En un sistema grande, ese error puede surgir mucho después, en una parte del código completamente diferente, lo que hace el bug muy difícil de rastrear.
ABC + @abstractmethod vs. raise NotImplementedError| Aspecto | ✅ Con ABC | ⚠️ Sin ABC (NotImplementedError) |
|---|---|---|
| ¿Cuándo detecta el error? | Al instanciar — inmediatamente | Al llamar el método — tarde |
| ¿Se puede instanciar la base? | No — Python lo impide con TypeError |
Sí — nadie lo impide |
| ¿Requiere importación? | from abc import ABC, abstractmethod |
No — solo Python puro |
| Documentación del contrato | Explícita y verificada por Python | Solo convención — depende del programador |
| Compatibilidad con IDEs / type checkers | Total — mypy, PyCharm, VS Code lo entienden | Parcial — el IDE no sabe que es "abstracto" |
| Facilidad de refactorización | Alta — Python avisa si olvidas implementar algo | Baja — el olvido pasa desapercibido |
| Verbosidad del código | Media — requiere decorador y herencia de ABC | Alta — cada método necesita raise manual |
| ¿Cuándo usarlo? | Proyectos profesionales, trabajo en equipo, APIs | Scripts simples, código legacy, compatibilidad antigua |
mypy).ABC. Es más seguro, más legible y es la forma estándar en Python moderno. El enfoque con NotImplementedError sirve para entender qué hace ABC internamente, pero no lo uses como primera opción.
Un getter es un método que lee un atributo privado. Un setter es un método que modifica un atributo privado tras validarlo. El objetivo es proteger los datos internos del objeto y mantener su consistencia.
CuentaBancariaUna cuenta bancaria tiene un saldo privado. Se puede consultar (getter), depositar y retirar (setter con validación). El saldo nunca puede ser negativo.
class CuentaBancaria: # Define la clase CuentaBancaria
"""Cuenta bancaria con saldo protegido por getter y setters.""" # Continúa la lógica del ejemplo paso a paso
def __init__(self, titular: str, saldo_inicial: float): # Constructor: inicializa el objeto con los datos recibidos
self.__titular = titular # Guarda __titular como atributo privado de la instancia
if saldo_inicial < 0: # Verifica saldo_inicial < 0 antes de continuar
raise ValueError("El saldo inicial no puede ser negativo.") # Lanza una excepción para señalar un error y detener el flujo normal
self.__saldo = saldo_inicial # atributo privado
# ── Getters ──────────────────────────────────────────────────────────
def get_titular(self) -> str: # Getter: devuelve el valor de titular
return self.__titular # Retorna el valor calculado al lugar donde se llamó este método
def get_saldo(self) -> float: # Getter: devuelve el valor de saldo
return self.__saldo # Retorna el valor calculado al lugar donde se llamó este método
# ── Setters con validación ────────────────────────────────────────────
def depositar(self, monto: float) -> None: # Método depositar(): acción que puede realizar este objeto
if monto <= 0: # Verifica monto <= 0 antes de continuar
raise ValueError("El monto a depositar debe ser positivo.") # Lanza una excepción para señalar un error y detener el flujo normal
self.__saldo += monto # Continúa la lógica del ejemplo paso a paso
print(f" ✅ Depósito de ${monto:,.2f} realizado. Saldo: ${self.__saldo:,.2f}") # Muestra en consola el resultado de esta operación
def retirar(self, monto: float) -> None: # Método retirar(): acción que puede realizar este objeto
if monto <= 0: # Verifica monto <= 0 antes de continuar
raise ValueError("El monto a retirar debe ser positivo.") # Lanza una excepción para señalar un error y detener el flujo normal
if monto > self.__saldo: # Verifica monto > self.__saldo antes de continuar
raise ValueError(f"Fondos insuficientes. Saldo disponible: ${self.__saldo:,.2f}") # Lanza una excepción para señalar un error y detener el flujo normal
self.__saldo -= monto # Continúa la lógica del ejemplo paso a paso
print(f" ✅ Retiro de ${monto:,.2f} realizado. Saldo: ${self.__saldo:,.2f}") # Muestra en consola el resultado de esta operación
def estado(self) -> str: # Método estado(): acción que puede realizar este objeto
return f"Cuenta de {self.__titular} | Saldo: ${self.__saldo:,.2f}" # Retorna el valor calculado al lugar donde se llamó este método
# ── Uso ──────────────────────────────────────────────────────────────────
cuenta = CuentaBancaria("Ana López", 5000.00) # Asigna un valor inicial o calculado a cuenta
print("Estado inicial:", cuenta.get_saldo()) # Muestra en consola el resultado de esta operación
cuenta.depositar(2000) # Completa la llamada o expresión iniciada anteriormente
cuenta.retirar(800) # Completa la llamada o expresión iniciada anteriormente
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
cuenta.retirar(99999) # ❌ intento de sobregiro
except ValueError as e: # Captura el error ValueError as e si ocurre en el try
print(f" ❌ Error: {e}") # Muestra en consola el resultado de esta operación
print(cuenta.estado()) # Muestra en consola el resultado de esta operación
# Acceso directo al atributo privado — Python lo rechaza ─────────────────
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
cuenta.__saldo = -9999 # ⚠️ crea un atributo nuevo, no modifica el real
print("Saldo real:", cuenta.get_saldo()) # el __saldo real no cambió
except AttributeError as e: # Captura el error AttributeError as e si ocurre en el try
print(f" ❌ {e}") # Muestra en consola el resultado de esta operación
Estado inicial: 5000.0 ✅ Depósito de $2,000.00 realizado. Saldo: $7,000.00 ✅ Retiro de $800.00 realizado. Saldo: $6,200.00 ❌ Error: Fondos insuficientes. Saldo disponible: $6,200.00 Cuenta de Ana López | Saldo: $6,200.00 Saldo real: 6200.0
cuenta.__saldo = -9999 fuera de la clase, Python crea un atributo nuevo llamado __saldo en el objeto, pero el atributo real internamente se llama _CuentaBancaria__saldo. Por eso get_saldo() sigue devolviendo el valor correcto.
Empleado del HotelUn empleado del hotel tiene nombre, puesto y salario. El salario no puede ser negativo y el nombre no puede estar vacío. El puesto es read-only (solo getter, sin setter).
class Empleado: # Define la clase Empleado
"""Empleado del hotel con encapsulamiento completo.""" # Continúa la lógica del ejemplo paso a paso
def __init__(self, nombre: str, puesto: str, salario: float): # Constructor: inicializa el objeto con los datos recibidos
self.set_nombre(nombre) # reutiliza la validación del setter
self.__puesto = puesto # read-only: solo getter
self.set_salario(salario) # Completa la llamada o expresión iniciada anteriormente
# ── Getters ──────────────────────────────────────────────────────────
def get_nombre(self) -> str: return self.__nombre # Getter: devuelve el valor de nombre
def get_puesto(self) -> str: return self.__puesto # read-only
def get_salario(self) -> float: return self.__salario # Getter: devuelve el valor de salario
# ── Setters con validación ────────────────────────────────────────────
def set_nombre(self, nuevo_nombre: str) -> None: # Setter: valida y actualiza el atributo nombre
nuevo_nombre = nuevo_nombre.strip() # Asigna un valor inicial o calculado a nuevo_nombre
if not nuevo_nombre: # Verifica not nuevo_nombre antes de continuar
raise ValueError("El nombre no puede estar vacío.") # Lanza una excepción para señalar un error y detener el flujo normal
self.__nombre = nuevo_nombre # Guarda __nombre como atributo privado de la instancia
def set_salario(self, nuevo_salario: float) -> None: # Setter: valida y actualiza el atributo salario
if nuevo_salario < 0: # Verifica nuevo_salario < 0 antes de continuar
raise ValueError(f"El salario no puede ser negativo. Recibido: {nuevo_salario}") # Lanza una excepción para señalar un error y detener el flujo normal
self.__salario = nuevo_salario # Guarda __salario como atributo privado de la instancia
def aplicar_aumento(self, porcentaje: float) -> None: # Método aplicar_aumento(): acción que puede realizar este objeto
if porcentaje <= 0: # Verifica porcentaje <= 0 antes de continuar
raise ValueError("El porcentaje de aumento debe ser positivo.") # Lanza una excepción para señalar un error y detener el flujo normal
aumento = self.__salario * (porcentaje / 100) # Asigna un valor inicial o calculado a aumento
self.__salario += aumento # Continúa la lógica del ejemplo paso a paso
print(f" Aumento del {porcentaje}%: +${aumento:,.2f} → Nuevo salario: ${self.__salario:,.2f}") # Muestra en consola el resultado de esta operación
def __str__(self) -> str: # Define cómo se muestra el objeto al usar print()
return f"[{self.__puesto}] {self.__nombre} — Salario: ${self.__salario:,.2f}" # Retorna el valor calculado al lugar donde se llamó este método
# ── Uso ──────────────────────────────────────────────────────────────────
emp = Empleado("Carlos Ruiz", "Recepcionista", 18000.00) # Asigna un valor inicial o calculado a emp
print(emp) # Muestra en consola el resultado de esta operación
emp.aplicar_aumento(10) # Completa la llamada o expresión iniciada anteriormente
emp.set_nombre("Carlos A. Ruiz") # Completa la llamada o expresión iniciada anteriormente
print(emp) # Muestra en consola el resultado de esta operación
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
emp.set_salario(-5000) # ❌ salario negativo
except ValueError as e: # Captura el error ValueError as e si ocurre en el try
print(f" ❌ {e}") # Muestra en consola el resultado de esta operación
[Recepcionista] Carlos Ruiz — Salario: $18,000.00 Aumento del 10%: +$1,800.00 → Nuevo salario: $19,800.00 [Recepcionista] Carlos A. Ruiz — Salario: $19,800.00 ❌ El salario no puede ser negativo. Recibido: -5000
Reservacion con múltiples atributos privadosUna reservación tiene número de habitación, nombre del huésped y cantidad de noches. La cantidad de noches debe ser entre 1 y 30. El número de habitación es write-once (se asigna al crear, no tiene setter).
class Reservacion: # Define la clase Reservacion
"""Reservación hotelera con encapsulamiento de todos sus atributos.""" # Continúa la lógica del ejemplo paso a paso
def __init__(self, num_habitacion: int, huesped: str, noches: int): # Constructor: inicializa el objeto con los datos recibidos
self.__num_habitacion = num_habitacion # write-once: sin setter
self.set_huesped(huesped) # Completa la llamada o expresión iniciada anteriormente
self.set_noches(noches) # Completa la llamada o expresión iniciada anteriormente
# ── Getters ──────────────────────────────────────────────────────────
def get_num_habitacion(self) -> int: return self.__num_habitacion # Getter: devuelve el valor de num_habitacion
def get_huesped(self) -> str: return self.__huesped # Getter: devuelve el valor de huesped
def get_noches(self) -> int: return self.__noches # Getter: devuelve el valor de noches
# ── Setters ──────────────────────────────────────────────────────────
def set_huesped(self, nombre: str) -> None: # Setter: valida y actualiza el atributo huesped
nombre = nombre.strip() # Asigna un valor inicial o calculado a nombre
if not nombre: # Verifica not nombre antes de continuar
raise ValueError("El nombre del huésped no puede estar vacío.") # Lanza una excepción para señalar un error y detener el flujo normal
self.__huesped = nombre # Guarda __huesped como atributo privado de la instancia
def set_noches(self, cantidad: int) -> None: # Setter: valida y actualiza el atributo noches
if not (1 <= cantidad <= 30): # Verifica not (1 <= cantidad <= 30) antes de continuar
raise ValueError(f"Las noches deben estar entre 1 y 30. Recibido: {cantidad}") # Lanza una excepción para señalar un error y detener el flujo normal
self.__noches = cantidad # Guarda __noches como atributo privado de la instancia
def costo_estimado(self, tarifa_noche: float = 1200.0) -> float: # Método costo_estimado(): acción que puede realizar este objeto
return self.__noches * tarifa_noche # Retorna el valor calculado al lugar donde se llamó este método
def __str__(self) -> str: # Define cómo se muestra el objeto al usar print()
return ( # Retorna el valor calculado al lugar donde se llamó este método
f"Reservación | Hab: {self.__num_habitacion} | " # Continúa la lógica del ejemplo paso a paso
f"Huésped: {self.__huesped} | Noches: {self.__noches} | " # Continúa la lógica del ejemplo paso a paso
f"Costo est.: ${self.costo_estimado():,.2f}" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
# ── Uso ──────────────────────────────────────────────────────────────────
r = Reservacion(305, "María García", 3) # Asigna un valor inicial o calculado a r
print(r) # Muestra en consola el resultado de esta operación
r.set_noches(5) # extensión de estancia
r.set_huesped("María G. Pérez") # Completa la llamada o expresión iniciada anteriormente
print(r) # Muestra en consola el resultado de esta operación
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
r.set_noches(0) # ❌ valor fuera de rango
except ValueError as e: # Captura el error ValueError as e si ocurre en el try
print(f" ❌ {e}") # Muestra en consola el resultado de esta operación
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
r.set_noches(45) # ❌ demasiadas noches
except ValueError as e: # Captura el error ValueError as e si ocurre en el try
print(f" ❌ {e}") # Muestra en consola el resultado de esta operación
Reservación | Hab: 305 | Huésped: María García | Noches: 3 | Costo est.: $3,600.00 Reservación | Hab: 305 | Huésped: María G. Pérez | Noches: 5 | Costo est.: $6,000.00 ❌ Las noches deben estar entre 1 y 30. Recibido: 0 ❌ Las noches deben estar entre 1 y 30. Recibido: 45
__num_habitacion no tiene setter. Una vez creada la reservación, el número de habitación no puede cambiar — si se necesita otro número, se crea una nueva reservación. Este patrón es ideal para identificadores que no deben mutar (ISBN, matrícula, número de factura).
forEn POO, los objetos no existen solos: un hotel tiene muchos huéspedes, muchas reservaciones, muchos servicios. Las listas y los diccionarios son las estructuras que permiten almacenar y recorrer colecciones de objetos de forma eficiente. Son el pegamento que une las clases con el mundo real.
lista[0]append(), remove(), pop(), len()for elemento in lista:dic["nombre"]keys(), values(), items()for k, v in dic.items():x al final de la lista.(clave, valor) del diccionario.for dentro de [].Almacena una lista de objetos Huesped, recórrela con for, filtra y transforma. Se cubren: creación, append(), iteración, enumerate() y comprensión de listas.
# ── Definición de la clase ───────────────────────────────────────────────
class Huesped: # Define la clase Huesped
def __init__(self, nombre: str, habitacion: int, noches: int): # Constructor: inicializa el objeto con los datos recibidos
self.nombre = nombre # Guarda nombre como atributo de esta instancia
self.habitacion = habitacion # Guarda habitacion como atributo de esta instancia
self.noches = noches # Guarda noches como atributo de esta instancia
def costo(self, tarifa: float = 1000.0) -> float: # Método costo(): acción que puede realizar este objeto
return self.noches * tarifa # Retorna el valor calculado al lugar donde se llamó este método
def __str__(self) -> str: # Define cómo se muestra el objeto al usar print()
return f"Hab {self.habitacion:>3} | {self.nombre:<20} | {self.noches} noche(s)" # Retorna el valor calculado al lugar donde se llamó este método
# ── 1. Crear lista vacía y agregar con append() ──────────────────────────
huespedes = [] # lista vacía
huespedes.append(Huesped("Ana Torres", 101, 3)) # Agrega un elemento al final de huespedes
huespedes.append(Huesped("Luis Ramos", 102, 1)) # Agrega un elemento al final de huespedes
huespedes.append(Huesped("Sandra Paz", 203, 5)) # Agrega un elemento al final de huespedes
huespedes.append(Huesped("Jorge Medina", 204, 2)) # Agrega un elemento al final de huespedes
huespedes.append(Huesped("Laura Soto", 305, 7)) # Agrega un elemento al final de huespedes
# ── 2. Recorrer con for básico ────────────────────────────────────────────
print("═══ LISTA COMPLETA DE HUÉSPEDES ═══") # Muestra en consola el resultado de esta operación
for h in huespedes: # Itera sobre cada h en huespedes
print(f" {h}") # usa __str__ automáticamente
# ── 3. Recorrer con enumerate() — índice + elemento ──────────────────────
print("\n═══ CON NÚMERO DE ORDEN ═══") # Muestra en consola el resultado de esta operación
for i, h in enumerate(huespedes, start=1): # Itera sobre cada i, h en enumerate(huespedes, start=1)
print(f" {i}. {h.nombre}") # Muestra en consola el resultado de esta operación
# ── 4. Filtrar con for + if ───────────────────────────────────────────────
print("\n═══ HUÉSPEDES CON MÁS DE 3 NOCHES ═══") # Muestra en consola el resultado de esta operación
for h in huespedes: # Itera sobre cada h en huespedes
if h.noches > 3: # Verifica h.noches > 3 antes de continuar
print(f" {h.nombre} — {h.noches} noches") # Muestra en consola el resultado de esta operación
# ── 5. Calcular total con for ─────────────────────────────────────────────
total = 0.0 # Asigna un valor inicial o calculado a total
for h in huespedes: # Itera sobre cada h en huespedes
total += h.costo() # Completa la llamada o expresión iniciada anteriormente
print(f"\n Ingresos totales del hotel: ${total:,.2f}") # Muestra en consola el resultado de esta operación
# ── 6. Comprensión de lista: obtener solo los nombres ────────────────────
nombres = [h.nombre for h in huespedes] # Asigna un valor inicial o calculado a nombres
print(f"\n Nombres (comprensión): {nombres}") # Muestra en consola el resultado de esta operación
# ── 7. Comprensión de lista con filtro ───────────────────────────────────
estancias_largas = [h.nombre for h in huespedes if h.noches >= 5] # Asigna un valor inicial o calculado a estancias_largas
print(f" Estancias ≥ 5 noches: {estancias_largas}") # Muestra en consola el resultado de esta operación
═══ LISTA COMPLETA DE HUÉSPEDES ═══ Hab 101 | Ana Torres | 3 noche(s) Hab 102 | Luis Ramos | 1 noche(s) Hab 203 | Sandra Paz | 5 noche(s) Hab 204 | Jorge Medina | 2 noche(s) Hab 305 | Laura Soto | 7 noche(s) ═══ CON NÚMERO DE ORDEN ═══ 1. Ana Torres 2. Luis Ramos 3. Sandra Paz 4. Jorge Medina 5. Laura Soto ═══ HUÉSPEDES CON MÁS DE 3 NOCHES ═══ Sandra Paz — 5 noches Laura Soto — 7 noches Ingresos totales del hotel: $18,000.00 Nombres (comprensión): ['Ana Torres', 'Luis Ramos', 'Sandra Paz', 'Jorge Medina', 'Laura Soto'] Estancias ≥ 5 noches: ['Sandra Paz', 'Laura Soto']
| Patrón | Sintaxis | Cuándo usarlo |
|---|---|---|
for x in lista |
for h in huespedes: print(h) |
Recorrer todos los elementos sin necesitar el índice. |
enumerate(lista) |
for i, h in enumerate(huespedes, 1): |
Cuando necesitas el número de orden o el índice. |
for x in lista if cond |
for h in huespedes: if h.noches > 3: |
Filtrar elementos que cumplen una condición. |
| Comprensión básica | [h.nombre for h in huespedes] |
Extraer una propiedad de todos los objetos en una línea. |
| Comprensión con filtro | [h.nombre for h in huespedes if h.noches >= 5] |
Extraer + filtrar en una sola expresión concisa. |
for de 3 líneas con append(), lo expresas en una sola línea. Es más legible, más rápido y más Pythónico. Aprende a leerla de izquierda a derecha: "dame h.nombre por cada h en huespedes si h.noches >= 5".
Usa un diccionario donde la clave es el número de habitación y el valor es su estado. Cubre: creación, acceso, items(), get(), actualización y diccionarios anidados.
# ── 1. Diccionario simple: clave → valor ─────────────────────────────────
# clave = número de habitación (int)
# valor = estado (str)
habitaciones = { # Asigna un valor inicial o calculado a habitaciones
101: "disponible", # Continúa la lógica del ejemplo paso a paso
102: "ocupada", # Continúa la lógica del ejemplo paso a paso
103: "mantenimiento", # Continúa la lógica del ejemplo paso a paso
201: "disponible", # Continúa la lógica del ejemplo paso a paso
202: "ocupada", # Continúa la lógica del ejemplo paso a paso
203: "disponible", # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
# ── 2. Acceso directo por clave ───────────────────────────────────────────
print("Estado hab. 102:", habitaciones[102]) # Muestra en consola el resultado de esta operación
# ── 3. Acceso seguro con get() ─────────────────────────────────────────────
estado = habitaciones.get(999, "no existe") # clave inexistente
print("Estado hab. 999:", estado) # Muestra en consola el resultado de esta operación
# ── 4. Recorrer con items() ──────────────────────────────────────────────
print("\n═══ INVENTARIO COMPLETO ═══") # Muestra en consola el resultado de esta operación
for numero, estado in habitaciones.items(): # Itera sobre cada numero, estado en habitaciones.items()
print(f" Hab {numero}: {estado}") # Muestra en consola el resultado de esta operación
# ── 5. Filtrar con for + if sobre items() ────────────────────────────────
print("\n═══ HABITACIONES DISPONIBLES ═══") # Muestra en consola el resultado de esta operación
for numero, estado in habitaciones.items(): # Itera sobre cada numero, estado en habitaciones.items()
if estado == "disponible": # Verifica estado == "disponible" antes de continuar
print(f" ✅ Habitación {numero}") # Muestra en consola el resultado de esta operación
# ── 6. Actualizar un valor ────────────────────────────────────────────────
habitaciones[201] = "ocupada" # check-in en hab. 201
print(f"\nHab 201 actualizada: {habitaciones[201]}") # Muestra en consola el resultado de esta operación
# ── 7. Agregar una habitación nueva ──────────────────────────────────────
habitaciones[301] = "disponible" # Asigna un valor inicial o calculado a habitaciones[301]
print(f"Total de habitaciones: {len(habitaciones)}") # Muestra en consola el resultado de esta operación
# ── 8. Comprensión de diccionario: solo las disponibles ──────────────────
disponibles = {num: est for num, est in habitaciones.items() if est == "disponible"} # Asigna un valor inicial o calculado a disponibles
print(f"\nDisponibles (dict comprehension): {disponibles}") # Muestra en consola el resultado de esta operación
Estado hab. 102: ocupada
Estado hab. 999: no existe
═══ INVENTARIO COMPLETO ═══
Hab 101: disponible
Hab 102: ocupada
Hab 103: mantenimiento
Hab 201: disponible
Hab 202: ocupada
Hab 203: disponible
═══ HABITACIONES DISPONIBLES ═══
✅ Habitación 101
✅ Habitación 201
✅ Habitación 203
Hab 201 actualizada: ocupada
Total de habitaciones: 7
Disponibles (dict comprehension): {101: 'disponible', 203: 'disponible', 301: 'disponible'}
Un diccionario anidado almacena información más rica: la clave es el ID de reservación y el valor es otro diccionario con todos los detalles del huésped. Se cubren: anidamiento, iteración profunda y acceso seguro.
# ── Diccionario anidado: cada clave es un ID, cada valor es un dict ───────
reservaciones = { # Asigna un valor inicial o calculado a reservaciones
"RES-001": { # Continúa la lógica del ejemplo paso a paso
"huesped" : "Ana Torres", # Continúa la lógica del ejemplo paso a paso
"habitacion" : 101, # Continúa la lógica del ejemplo paso a paso
"noches" : 3, # Continúa la lógica del ejemplo paso a paso
"desayuno" : True, # Continúa la lógica del ejemplo paso a paso
}, # Continúa la lógica del ejemplo paso a paso
"RES-002": { # Continúa la lógica del ejemplo paso a paso
"huesped" : "Luis Ramos", # Continúa la lógica del ejemplo paso a paso
"habitacion" : 204, # Continúa la lógica del ejemplo paso a paso
"noches" : 1, # Continúa la lógica del ejemplo paso a paso
"desayuno" : False, # Continúa la lógica del ejemplo paso a paso
}, # Continúa la lógica del ejemplo paso a paso
"RES-003": { # Continúa la lógica del ejemplo paso a paso
"huesped" : "Sandra Paz", # Continúa la lógica del ejemplo paso a paso
"habitacion" : 305, # Continúa la lógica del ejemplo paso a paso
"noches" : 5, # Continúa la lógica del ejemplo paso a paso
"desayuno" : True, # Continúa la lógica del ejemplo paso a paso
}, # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
# ── 1. Recorrer con items(): id_reservacion, datos ───────────────────────
TARIFA = 1200.0 # Asigna un valor inicial o calculado a TARIFA
COSTO_DESAYUNO = 150.0 # Asigna un valor inicial o calculado a COSTO_DESAYUNO
print("═══ RESUMEN DE RESERVACIONES ═══") # Muestra en consola el resultado de esta operación
for id_res, datos in reservaciones.items(): # Itera sobre cada id_res, datos en reservaciones.items()
costo = datos["noches"] * TARIFA # Asigna un valor inicial o calculado a costo
if datos["desayuno"]: # Verifica datos["desayuno"] antes de continuar
costo += datos["noches"] * COSTO_DESAYUNO # Continúa la lógica del ejemplo paso a paso
print(f" {id_res} | {datos['huesped']:<15} | Hab {datos['habitacion']} | ${costo:,.2f}") # Muestra en consola el resultado de esta operación
# ── 2. Acceder a un dato específico de una reservación ───────────────────
print(f"\nHuésped RES-002: {reservaciones['RES-002']['huesped']}") # Muestra en consola el resultado de esta operación
# ── 3. Agregar una nueva reservación en tiempo de ejecución ──────────────
reservaciones["RES-004"] = { # Continúa la lógica del ejemplo paso a paso
"huesped" : "Jorge Medina", # Continúa la lógica del ejemplo paso a paso
"habitacion" : 102, # Continúa la lógica del ejemplo paso a paso
"noches" : 2, # Continúa la lógica del ejemplo paso a paso
"desayuno" : False, # Continúa la lógica del ejemplo paso a paso
} # Cierra la estructura abierta en las líneas anteriores
print(f"\nTotal reservaciones: {len(reservaciones)}") # Muestra en consola el resultado de esta operación
# ── 4. Filtrar con for: reservaciones CON desayuno ───────────────────────
print("\n═══ RESERVACIONES CON DESAYUNO ═══") # Muestra en consola el resultado de esta operación
for id_res, datos in reservaciones.items(): # Itera sobre cada id_res, datos en reservaciones.items()
if datos["desayuno"]: # Verifica datos["desayuno"] antes de continuar
print(f" ☕ {id_res} — {datos['huesped']}") # Muestra en consola el resultado de esta operación
# ── 5. Comprensión de diccionario ────────────────────────────────────────
# Obtener solo {id: nombre_huesped} para las reservaciones largas
largas = { # Asigna un valor inicial o calculado a largas
id_res: datos["huesped"] # Cierra la lista o el índice construido en las líneas anteriores
for id_res, datos in reservaciones.items() # Recorre cada elemento de la colección indicada
if datos["noches"] >= 3 # Verifica datos["noches"] >= 3 antes de continuar
} # Cierra la estructura abierta en las líneas anteriores
print(f"\nReservaciones largas (≥3 noches): {largas}") # Muestra en consola el resultado de esta operación
═══ RESUMEN DE RESERVACIONES ═══
RES-001 | Ana Torres | Hab 101 | $4,050.00
RES-002 | Luis Ramos | Hab 204 | $1,200.00
RES-003 | Sandra Paz | Hab 305 | $6,750.00
Huésped RES-002: Luis Ramos
Total reservaciones: 4
═══ RESERVACIONES CON DESAYUNO ═══
☕ RES-001 — Ana Torres
☕ RES-003 — Sandra Paz
Reservaciones largas (≥3 noches): {'RES-001': 'Ana Torres', 'RES-003': 'Sandra Paz'}
| Patrón | Sintaxis | ¿Qué devuelve? |
|---|---|---|
for k in dic |
for num in habitaciones: |
Solo las claves |
dic.values() |
for est in habitaciones.values(): |
Solo los valores |
dic.items() |
for num, est in habitaciones.items(): |
Pares clave, valor — el más usado |
dic.get(k, default) |
habitaciones.get(999, "no existe") |
Valor o default si la clave no existe — evita KeyError |
| Dict comprehension | {k: v for k, v in dic.items() if cond} |
Nuevo diccionario filtrado/transformado |
KeyError al acceder con dic[clave] si la clave no existe — usa dic.get(clave).IndexError al acceder con lista[i] con un índice fuera de rango.for — itera sobre una copia: for x in lista[:].append(obj) con extend([obj]).for es el salto que diferencia al programador principiante del intermedio. En POO, prácticamente toda colección de objetos se almacena en uno de estos dos contenedores. Con las comprensiones, además, puedes filtrar y transformar datos en una sola línea elegante — una habilidad muy valorada en el mundo profesional.
= vs ==, append, self y el método confirmar= versus == — son completamente distintosEste es uno de los errores más frecuentes en programación. Tienen símbolos parecidos pero significados completamente opuestos.
= — Asignación== — ComparaciónTrue o False.if, while, filtros.= dentro de un if.==.# ── = (asignación) — GUARDA un valor ────────────────────────────────────
estado = "disponible" # la variable 'estado' ahora CONTIENE "disponible"
noches = 3 # la variable 'noches' ahora CONTIENE 3
precio = noches * 1200 # calcula y GUARDA el resultado en 'precio'
# ── == (comparación) — PREGUNTA si dos cosas son iguales ─────────────────
print(estado == "disponible") # → True (sí son iguales)
print(estado == "ocupada") # → False (no son iguales)
print(noches == 5) # → False
print(noches == 3) # → True
# ── En un if siempre usamos == ───────────────────────────────────────────
if estado == "disponible": # ✅ correcto: comparamos
print("La habitación está libre") # Muestra en consola el resultado de esta operación
# ── = dentro de un objeto ────────────────────────────────────────────────
class Habitacion: # Define la clase Habitacion
def __init__(self, numero): # Constructor: inicializa el objeto con los datos recibidos
self.numero = numero # = GUARDA el número en el objeto
self.disponible = True # = GUARDA True en el atributo
def reservar(self): # Método reservar(): acción que puede realizar este objeto
if self.disponible == True: # == PREGUNTA si está disponible
self.disponible = False # = CAMBIA el estado a False
return "✅ Habitación reservada" # Retorna el valor calculado al lugar donde se llamó este método
return "❌ Ya estaba ocupada" # Retorna el valor calculado al lugar donde se llamó este método
h = Habitacion(101) # Asigna un valor inicial o calculado a h
print(h.reservar()) # → ✅ Habitación reservada
print(h.reservar()) # → ❌ Ya estaba ocupada
| Aspecto | = Asignación | == Comparación |
|---|---|---|
| ¿Qué hace? | Guarda / escribe un valor | Compara dos valores |
| ¿Devuelve algo? | No — solo almacena | Sí — True o False |
| ¿Cambia datos? | Sí, siempre | Nunca |
| ¿Dónde se usa? | x = 5, self.nombre = "Ana" | if x == 5:, while estado == "activo": |
| Error típico | — | Escribir if x = 5: → SyntaxError |
if estado = "disponible": con un solo = dentro de un if provoca un SyntaxError. Python espera una comparación (==), no una asignación. Este error es tan común que Python lo detecta de inmediato.
append() — qué significa y qué hace exactamenteappend viene del inglés "to append" que significa adjuntar / agregar al final. Es el método más usado para construir listas dinámicas en Python.
# ── Una lista es como una fila de cajas numeradas ───────────────────────
reservaciones = [] # fila vacía
# append() AGREGA una nueva caja AL FINAL de la fila
reservaciones.append("RES-001") # → ["RES-001"]
reservaciones.append("RES-002") # → ["RES-001", "RES-002"]
reservaciones.append("RES-003") # → ["RES-001", "RES-002", "RES-003"]
print(reservaciones) # → ['RES-001', 'RES-002', 'RES-003']
print(len(reservaciones)) # → 3
# ── append de objetos enteros ────────────────────────────────────────────
class Reservacion: # Define la clase Reservacion
def __init__(self, id_res): # Constructor: inicializa el objeto con los datos recibidos
self.id_res = id_res # Guarda id_res como atributo de esta instancia
def __str__(self): # Define cómo se muestra el objeto al usar print()
return f"Reservación({self.id_res})" # Retorna el valor calculado al lugar donde se llamó este método
historial = [] # Crea una lista vacía para almacenar datos en historial
historial.append(Reservacion("RES-001")) # guarda el OBJETO completo
historial.append(Reservacion("RES-002")) # Agrega un elemento al final de historial
for r in historial: # Itera sobre cada r en historial
print(r) # → Reservación(RES-001) / Reservación(RES-002)
# ── Diferencia clave entre append() y = ──────────────────────────────────
lista_a = [1, 2, 3] # Asigna un valor inicial o calculado a lista_a
lista_a.append(4) # AGREGA 4 al final → [1, 2, 3, 4]
lista_b = [1, 2, 3] # Asigna un valor inicial o calculado a lista_b
lista_b = [4] # REEMPLAZA toda la lista → [4] ⚠️ pierdes [1,2,3]
# ── Error clásico: guardar el resultado de append() ──────────────────────
lista_c = [1, 2] # Asigna un valor inicial o calculado a lista_c
lista_c = lista_c.append(3) # ⚠️ append devuelve None
print(lista_c) # → None ❌ la lista original se perdió
| Momento | Estado de la lista | Índice del nuevo elemento |
|---|---|---|
| Inicio | [] | — (vacía) |
Primer append(r1) | [r1] | índice 0 |
Segundo append(r2) | [r1, r2] | índice 1 |
Tercer append(r3) | [r1, r2, r3] | índice 2 |
| Acceso directo | historial[0] → r1 | Los índices empiezan en 0, no en 1 |
append(x) modifica la lista en su lugar (in-place) — no devuelve nada útil (None). Por eso escribes lista.append(x) y nunca lista = lista.append(x).
confirmar()Vamos a desmenuzar este método línea por línea y responder cada pregunta que surge al leerlo.
def confirmar(self): # Método confirmar(): acción que puede realizar este objeto
resultado = self.habitacion.reservar() # línea 1
self.cliente.agregar_reservacion(self) # línea 2
return resultado # línea 3
resultado está a la IZQUIERDA del =?# Python evalúa siempre de DERECHA a IZQUIERDA cuando hay =
#
# resultado = self.habitacion.reservar()
# ↑ ↑
# DESTINO ORIGEN (se ejecuta primero)
#
# Paso 1: Python ejecuta self.habitacion.reservar()
# Paso 2: ese método DEVUELVE un valor (ej: "✅ Habitación reservada")
# Paso 3: ese valor se GUARDA en la variable 'resultado'
# Paso 4: 'return resultado' lo entrega al código que llamó confirmar()
# Mismo principio con una suma:
x = 5 + 3 # primero calcula 5+3=8, luego guarda 8 en x
# Sin variable intermedia — también válido:
return self.habitacion.reservar() # ✅ directo, pero llama al método una sola vez
# Con variable — más legible y evita doble llamada:
resultado = self.habitacion.reservar() # Asigna un valor inicial o calculado a resultado
return resultado # ✅ preferido en código profesional
resultado existe solo dentro de la función (variable local). Cuando return resultado termina de ejecutarse, esa variable desaparece. Su único propósito fue guardar temporalmente el valor para devolverlo.
self.habitacion.reservar() — ¿cómo llega al método de otra clase?# En __init__ de Reservacion guardamos el OBJETO Habitacion completo:
class Reservacion: # Define la clase Reservacion
def __init__(self, cliente, habitacion, fecha_entrada, fecha_salida): # Constructor: inicializa el objeto con los datos recibidos
self.cliente = cliente # ← OBJETO Cliente guardado aquí
self.habitacion = habitacion # ← OBJETO Habitacion guardado aquí
def confirmar(self): # Método confirmar(): acción que puede realizar este objeto
# self → este objeto Reservacion
# self.habitacion → el objeto Habitacion guardado en el atributo
# .reservar() → llama al método reservar() de ESE objeto Habitacion
resultado = self.habitacion.reservar() # Asigna un valor inicial o calculado a resultado
# Es exactamente igual a: hab101.reservar() — donde hab101 === self.habitacion
# ¿Qué pasa si no pongo self en la definición del método? ────────────────
class ReservacionMal: # Define la clase ReservacionMal
def confirmar(): # ⚠️ sin self
resultado = habitacion.reservar() # ❌ NameError: 'habitacion' no existe
reserva = ReservacionMal() # Asigna un valor inicial o calculado a reserva
reserva.confirmar() # ❌ TypeError: confirmar() takes 0 arguments but 1 was given
# Python intenta pasar el objeto pero no hay parámetro para recibirlo
self.habitacion.reservar() — de izquierda a derecha| Fragmento | Significa | Analogía |
|---|---|---|
self |
Este objeto Reservacion actual |
Yo (la reservación) |
self.habitacion |
El objeto Habitacion guardado en mi atributo |
Mi habitación asignada |
self.habitacion.reservar |
El método reservar de ese objeto Habitacion |
La acción "reservar" de esa habitación |
self.habitacion.reservar() |
Ejecutar ese método ahora. El () lo dispara. |
Pedirle a mi habitación que se reserve |
self. Python lo rellena automáticamente con el objeto que llama al método. Sin self, el método no puede acceder a ningún atributo ni a ningún otro método del objeto.
self.cliente — ¿qué significa y qué más puedo acceder?# self.cliente ES el objeto Cliente completo — puedes usar TODOS sus atributos y métodos
class Cliente: # Define la clase Cliente
def __init__(self, nombre, email): # Constructor: inicializa el objeto con los datos recibidos
self.nombre = nombre # Guarda nombre como atributo de esta instancia
self.email = email # Guarda email como atributo de esta instancia
self.reservaciones = [] # Guarda reservaciones como atributo de esta instancia
def agregar_reservacion(self, reservacion): # Método agregar_reservacion(): acción que puede realizar este objeto
self.reservaciones.append(reservacion) # Agrega un elemento al final de self.reservaciones
def get_nombre(self): return self.nombre # Getter: devuelve el valor de nombre
def get_email(self): return self.email # Getter: devuelve el valor de email
def total_reservaciones(self): return len(self.reservaciones) # Método total_reservaciones(): acción que puede realizar este objeto
# Desde confirmar() puedes hacer CUALQUIERA de estas cosas:
def confirmar(self): # Método confirmar(): acción que puede realizar este objeto
# Acceder a atributos:
print(self.cliente.nombre) # → "Ana Torres"
print(self.cliente.email) # → "ana@email.com"
# Llamar a métodos:
print(self.cliente.get_nombre()) # → "Ana Torres" (getter)
print(self.cliente.total_reservaciones()) # → número de reservaciones
# Agregar esta reservación al historial del cliente:
self.cliente.agregar_reservacion(self) # ← lo que hace la línea 2
agregar_reservacion(self) — ¿por qué self está dentro del paréntesis? ¿Qué pasa si no lo pongo?# ── Hay DOS usos distintos de self — no confundirlos ────────────────────
# USO 1: self en la DEFINICIÓN (parámetro) — significa "yo, este objeto"
def agregar_reservacion(self, reservacion): # self = el objeto Cliente
self.reservaciones.append(reservacion) # Agrega un elemento al final de self.reservaciones
# USO 2: self en la LLAMADA (argumento) — pasa la Reservacion como dato
self.cliente.agregar_reservacion(self) # Completa la llamada o expresión iniciada anteriormente
# ↑ objeto Reservacion ↑ ¡este self también es la Reservacion!
# llama al método del cliente y le pasa ESTA reservación como argumento
# Traducido a nombres más claros:
# self = la Reservacion actual
# self.cliente = el objeto Cliente
# agregar_reservacion(self) → "cliente, guarda ESTA reservación en tu historial"
# ── ¿QUÉ PASA SI NO PONGO self? ──────────────────────────────────────────
class Cliente: # Define la clase Cliente
def __init__(self, nombre): # Constructor: inicializa el objeto con los datos recibidos
self.nombre = nombre # Guarda nombre como atributo de esta instancia
self.reservaciones = [] # Guarda reservaciones como atributo de esta instancia
def agregar_reservacion(self, reservacion): # Método agregar_reservacion(): acción que puede realizar este objeto
self.reservaciones.append(reservacion) # Agrega un elemento al final de self.reservaciones
class Reservacion: # Define la clase Reservacion
def __init__(self, cliente, id_res): # Constructor: inicializa el objeto con los datos recibidos
self.cliente = cliente # Guarda cliente como atributo de esta instancia
self.id_res = id_res # Guarda id_res como atributo de esta instancia
def confirmar_correcto(self): # Método confirmar_correcto(): acción que puede realizar este objeto
self.cliente.agregar_reservacion(self) # ✅ pasa la reservación completa
def confirmar_incorrecto(self): # Método confirmar_incorrecto(): acción que puede realizar este objeto
self.cliente.agregar_reservacion() # ❌ falta el argumento reservacion
cli = Cliente("Ana Torres") # Asigna un valor inicial o calculado a cli
res = Reservacion(cli, "RES-001") # Asigna un valor inicial o calculado a res
res.confirmar_correcto() # Completa la llamada o expresión iniciada anteriormente
print(cli.reservaciones[0].id_res) # → RES-001 ✅
try: # Intenta ejecutar el bloque; si hay error, lo captura el except
res.confirmar_incorrecto() # Completa la llamada o expresión iniciada anteriormente
except TypeError as e: # Captura el error TypeError as e si ocurre en el try
print(f"Error: {e}") # → TypeError: missing 1 required argument: 'reservacion'
self en una sola línea| Posición | Código | ¿Qué objeto es? | ¿Quién lo proporciona? |
|---|---|---|---|
| Antes del punto | self.cliente.agregar_reservacion(self) |
La Reservacion actual |
Python — automáticamente al ejecutar confirmar |
| Dentro del paréntesis | self.cliente.agregar_reservacion(self) |
También la Reservacion — pasada como argumento para que el cliente la guarde |
El programador — explícitamente |
self.cliente.agregar_reservacion(self) hay tres objetos involucrados: la Reservacion (quien ejecuta confirmar), el Cliente (guardado en self.cliente), y de nuevo la Reservacion (pasada como argumento para que el cliente la guarde en su historial). El self que aparece dos veces siempre se refiere a la misma Reservacion.
Integramos todo lo anterior en un sistema funcional: clases, append, self, for y los métodos que ya conocemos.
# ════════════════════════════════════════════════════════════════════
# SISTEMA COMPLETO — Hotel con historial de reservaciones por cliente # Comentario de apoyo que guía la lectura del ejemplo
# ════════════════════════════════════════════════════════════════════ # Comentario de apoyo que guía la lectura del ejemplo
class Habitacion: # Define la clase Habitacion
def __init__(self, numero: int, tipo: str, tarifa: float): # Constructor: inicializa el objeto con los datos recibidos
self.numero = numero # Guarda numero como atributo de esta instancia
self.tipo = tipo # Guarda tipo como atributo de esta instancia
self.tarifa = tarifa # Guarda tarifa como atributo de esta instancia
self.disponible = True # Guarda disponible como atributo de esta instancia
def reservar(self) -> str: # Método reservar(): acción que puede realizar este objeto
if self.disponible: # Verifica self.disponible antes de continuar
self.disponible = False # Guarda disponible como atributo de esta instancia
return f"✅ Hab {self.numero} ({self.tipo}) reservada" # Retorna el valor calculado al lugar donde se llamó este método
return f"❌ Hab {self.numero} ya está ocupada" # Retorna el valor calculado al lugar donde se llamó este método
def __str__(self) -> str: # Define cómo se muestra el objeto al usar print()
est = "disponible" if self.disponible else "ocupada" # Asigna un valor inicial o calculado a est
return f"Hab {self.numero} [{self.tipo}] ${self.tarifa:.2f}/noche — {est}" # Retorna el valor calculado al lugar donde se llamó este método
class Cliente: # Define la clase Cliente
def __init__(self, nombre: str, email: str): # Constructor: inicializa el objeto con los datos recibidos
self.nombre = nombre # Guarda nombre como atributo de esta instancia
self.email = email # Guarda email como atributo de esta instancia
self.reservaciones = [] # lista vacía — se llena con append()
def agregar_reservacion(self, reservacion) -> None: # Método agregar_reservacion(): acción que puede realizar este objeto
self.reservaciones.append(reservacion) # Agrega un elemento al final de self.reservaciones
def ver_reservaciones(self) -> None: # Método ver_reservaciones(): acción que puede realizar este objeto
"""Imprime todo el historial del cliente con totales.""" # Continúa la lógica del ejemplo paso a paso
print(f"\n 📋 Historial de {self.nombre} ({self.email})") # Muestra en consola el resultado de esta operación
print(f" {'─' * 50}") # Muestra en consola el resultado de esta operación
if not self.reservaciones: # Verifica not self.reservaciones antes de continuar
print(" Sin reservaciones registradas.") # Muestra en consola el resultado de esta operación
return # Termina el método y devuelve el control al llamador
total = 0.0 # Asigna un valor inicial o calculado a total
for i, res in enumerate(self.reservaciones, start=1): # Itera sobre cada i, res en enumerate(self.reservaciones, start=1)
costo = res.noches * res.habitacion.tarifa # Asigna un valor inicial o calculado a costo
total += costo # Continúa la lógica del ejemplo paso a paso
print( # Muestra en consola el resultado de esta operación
f" {i}. Hab {res.habitacion.numero} | " # Continúa la lógica del ejemplo paso a paso
f"{res.fecha_entrada} → {res.fecha_salida} | " # Continúa la lógica del ejemplo paso a paso
f"{res.noches} noche(s) | ${costo:,.2f}" # Continúa la lógica del ejemplo paso a paso
) # Cierra la estructura abierta en las líneas anteriores
print(f" {'─' * 50}") # Muestra en consola el resultado de esta operación
print(f" Total gastado: ${total:,.2f}") # Muestra en consola el resultado de esta operación
def __str__(self) -> str: # Define cómo se muestra el objeto al usar print()
return f"Cliente: {self.nombre} | {len(self.reservaciones)} reservación(es)" # Retorna el valor calculado al lugar donde se llamó este método
class Reservacion: # Define la clase Reservacion
def __init__(self, cliente: Cliente, habitacion: Habitacion, # Constructor: inicializa el objeto con los datos recibidos
fecha_entrada: str, fecha_salida: str, noches: int): # Completa la firma o la condición iniciada en la línea anterior
self.cliente = cliente # Guarda cliente como atributo de esta instancia
self.habitacion = habitacion # Guarda habitacion como atributo de esta instancia
self.fecha_entrada = fecha_entrada # Guarda fecha_entrada como atributo de esta instancia
self.fecha_salida = fecha_salida # Guarda fecha_salida como atributo de esta instancia
self.noches = noches # Guarda noches como atributo de esta instancia
def confirmar(self) -> str: # Método confirmar(): acción que puede realizar este objeto
resultado = self.habitacion.reservar() # 1. cierra la habitación
self.cliente.agregar_reservacion(self) # 2. agrega al historial del cliente
return resultado # 3. devuelve el mensaje
# ════════════════════════════════════════════════════════════════════
# Crear habitaciones
hab101 = Habitacion(101, "Estándar", 1200.0) # Asigna un valor inicial o calculado a hab101
hab202 = Habitacion(202, "Junior Suite", 2500.0) # Asigna un valor inicial o calculado a hab202
hab305 = Habitacion(305, "Suite Master", 4500.0) # Asigna un valor inicial o calculado a hab305
# Crear clientes
ana = Cliente("Ana Torres", "ana@email.com") # Asigna un valor inicial o calculado a ana
luis = Cliente("Luis Ramos", "luis@email.com") # Asigna un valor inicial o calculado a luis
# Crear y confirmar reservaciones
r1 = Reservacion(ana, hab101, "2026-06-01", "2026-06-04", 3) # Asigna un valor inicial o calculado a r1
r2 = Reservacion(ana, hab305, "2026-07-15", "2026-07-20", 5) # Asigna un valor inicial o calculado a r2
r3 = Reservacion(luis, hab202, "2026-06-10", "2026-06-12", 2) # Asigna un valor inicial o calculado a r3
print(r1.confirmar()) # ✅ Hab 101 reservada
print(r2.confirmar()) # ✅ Hab 305 reservada
print(r3.confirmar()) # ✅ Hab 202 reservada
# ── VER TODAS LAS RESERVACIONES DE UN CLIENTE ────────────────────────────
ana.ver_reservaciones() # Completa la llamada o expresión iniciada anteriormente
luis.ver_reservaciones() # Completa la llamada o expresión iniciada anteriormente
# ── Acceso directo a la lista con for ────────────────────────────────────
print(f"\nAna tiene {len(ana.reservaciones)} reservación(es):") # Muestra en consola el resultado de esta operación
for r in ana.reservaciones: # Itera sobre cada r en ana.reservaciones
print(f" → Hab {r.habitacion.numero} — {r.fecha_entrada}") # Muestra en consola el resultado de esta operación
# ── Intento de reservar habitación ya ocupada ─────────────────────────────
r4 = Reservacion(luis, hab101, "2026-06-05", "2026-06-07", 2) # Asigna un valor inicial o calculado a r4
print("\n" + r4.confirmar()) # ❌ ya estaba ocupada
✅ Hab 101 (Estándar) reservada ✅ Hab 305 (Suite Master) reservada ✅ Hab 202 (Junior Suite) reservada 📋 Historial de Ana Torres (ana@email.com) ────────────────────────────────────────────────── 1. Hab 101 | 2026-06-01 → 2026-06-04 | 3 noche(s) | $3,600.00 2. Hab 305 | 2026-07-15 → 2026-07-20 | 5 noche(s) | $22,500.00 ────────────────────────────────────────────────── Total gastado: $26,100.00 📋 Historial de Luis Ramos (luis@email.com) ────────────────────────────────────────────────── 1. Hab 202 | 2026-06-10 → 2026-06-12 | 2 noche(s) | $5,000.00 ────────────────────────────────────────────────── Total gastado: $5,000.00 Ana tiene 2 reservación(es): → Hab 101 — 2026-06-01 → Hab 305 — 2026-07-15 ❌ Hab 101 ya está ocupada
r1.confirmar() — paso a paso| Paso | Código que se ejecuta | Qué ocurre |
|---|---|---|
| 1 | r1.confirmar() | Python entra al método. self = r1. |
| 2 | self.habitacion.reservar() | Accede a hab101. Llama a su reservar(). |
| 3 | Dentro de hab101.reservar() | disponible cambia de True a False. Retorna "✅ Hab 101...". |
| 4 | resultado = "✅ Hab 101..." | El texto devuelto se guarda en la variable local resultado. |
| 5 | self.cliente.agregar_reservacion(self) | Accede a ana. Pasa r1 (self) como argumento. |
| 6 | ana.agregar_reservacion(r1) | Ejecuta self.reservaciones.append(r1). r1 queda en el historial de Ana. |
| 7 | return resultado | El texto "✅ Hab 101 (Estándar) reservada" viaja al código principal. |
Reservacion no hace todo el trabajo — delega: le pide a Habitacion que se reserve, y le pide a Cliente que guarde el registro. Cada clase hace solo lo que le corresponde. Eso es el principio de responsabilidad única y el corazón de la Programación Orientada a Objetos.