La Programación Orientada a Objetos (POO) es un paradigma de programación basado en el concepto de "objetos", que representan entidades del mundo real. Cada objeto contiene datos (atributos) y código (métodos) para manipular esos datos.
La POO nos permite organizar nuestro código de manera modular, facilitando su reutilización y mantenimiento.
Una clase es un plano o plantilla que define las características y comportamientos de un tipo de objeto. Es como un molde para crear objetos.
Un objeto es una instancia particular de una clase. Representa una entidad específica basada en la plantilla definida por su clase.
class NombreClase:
# Atributos de clase
atributo_clase = valor
# Método constructor
def __init__(self, parámetros):
# Atributos de instancia
self.atributo1 = valor1
self.atributo2 = valor2
# Métodos de instancia
def nombre_metodo(self, parámetros):
# Código del método
pass
class Persona:
"""
Clase que representa a una persona.
"""
# Atributo de clase (compartido por todas las instancias)
especie = "Humano"
# Método constructor
def __init__(self, nombre, edad):
# Atributos de instancia (únicos para cada objeto)
self.nombre = nombre
self.edad = edad
# Método de instancia
def presentarse(self):
return f"¡Hola! Me llamo {self.nombre} y tengo {self.edad} años."
# Otro método de instancia
def cumplir_años(self):
self.edad += 1
return f"{self.nombre} ahora tiene {self.edad} años."
# Crear instancias (objetos) de la clase Persona
persona1 = Persona("Ana", 25)
persona2 = Persona("Carlos", 30)
# Acceder a atributos
print(f"Nombre de persona1: {persona1.nombre}")
print(f"Edad de persona1: {persona1.edad}")
print(f"Especie de persona1: {persona1.especie}")
print(f"Nombre de persona2: {persona2.nombre}")
print(f"Edad de persona2: {persona2.edad}")
print(f"Especie de persona2: {persona2.especie}")
# Llamar a métodos
print(persona1.presentarse())
print(persona2.presentarse())
# Modificar atributos
persona1.nombre = "Ana María"
print(f"Nuevo nombre de persona1: {persona1.nombre}")
# Llamar al método cumplir_años
print(persona1.cumplir_años())
Python permite definir varios tipos de métodos dentro de una clase, cada uno con un propósito específico:
Son los métodos más comunes. Operan en una instancia específica de la clase y pueden acceder y modificar los atributos del objeto.
Operan en la clase en sí misma, en lugar de en instancias específicas. Se definen usando el decorador @classmethod
.
No operan en la clase ni en sus instancias. Son funciones normales que pertenecen al espacio de nombres de la clase. Se definen usando el decorador @staticmethod
.
class Estudiante:
# Atributo de clase
escuela = "Instituto Tecnológico"
def __init__(self, nombre, edad, calificaciones=None):
# Atributos de instancia
self.nombre = nombre
self.edad = edad
self.calificaciones = calificaciones if calificaciones else []
# Método de instancia
def agregar_calificacion(self, calificacion):
self.calificaciones.append(calificacion)
return f"Calificación {calificacion} agregada para {self.nombre}"
def promedio(self):
if not self.calificaciones:
return 0
return sum(self.calificaciones) / len(self.calificaciones)
# Método de clase
@classmethod
def cambiar_escuela(cls, nueva_escuela):
cls.escuela = nueva_escuela
return f"La escuela ahora es: {cls.escuela}"
@classmethod
def crear_desde_cadena(cls, cadena):
"""Crea un estudiante desde una cadena con formato 'nombre,edad,cal1,cal2,...'"""
partes = cadena.split(',')
nombre = partes[0]
edad = int(partes[1])
calificaciones = [float(cal) for cal in partes[2:]]
return cls(nombre, edad, calificaciones)
# Método estático
@staticmethod
def es_aprobado(calificacion):
"""Verifica si una calificación es aprobatoria (>= 70)"""
return calificacion >= 70
# Crear estudiantes
estudiante1 = Estudiante("María", 20)
estudiante2 = Estudiante("Juan", 19, [85, 90, 78])
# Usar método de instancia
print(estudiante1.agregar_calificacion(95))
print(estudiante1.agregar_calificacion(87))
print(f"Promedio de {estudiante1.nombre}: {estudiante1.promedio()}")
print(f"Promedio de {estudiante2.nombre}: {estudiante2.promedio()}")
# Usar método de clase
print(Estudiante.cambiar_escuela("Universidad Nacional"))
print(f"Escuela de estudiante1: {estudiante1.escuela}")
print(f"Escuela de estudiante2: {estudiante2.escuela}")
# Usar método de clase para crear un nuevo estudiante
nuevo_estudiante = Estudiante.crear_desde_cadena("Pedro,21,76,82,91")
print(f"Nuevo estudiante: {nuevo_estudiante.nombre}")
print(f"Calificaciones: {nuevo_estudiante.calificaciones}")
print(f"Promedio: {nuevo_estudiante.promedio()}")
# Usar método estático
for calificacion in [65, 70, 90]:
estado = "aprobado" if Estudiante.es_aprobado(calificacion) else "reprobado"
print(f"Calificación {calificacion}: {estado}")
Tipo | Decorador | Primer parámetro | Acceso a atributos de clase | Acceso a atributos de instancia |
---|---|---|---|---|
Instancia | Ninguno | self |
Sí | Sí |
Clase | @classmethod |
cls |
Sí | No |
Estático | @staticmethod |
Ninguno | No | No |
El constructor es un método especial que se llama automáticamente cuando se crea una instancia de una clase. En Python, el constructor se define con el método __init__
.
class MiClase:
def __init__(self, parametro1, parametro2, ...):
self.atributo1 = parametro1
self.atributo2 = parametro2
# Inicialización de otros atributos
...
class Producto:
# Constructor con parámetros obligatorios y opcionales
def __init__(self, nombre, precio, stock=0, categoria=None):
# Inicialización de atributos
self.nombre = nombre
self.precio = precio
self.stock = stock
self.categoria = categoria if categoria else "Sin categoría"
# Validación básica
if precio < 0:
print(f"Advertencia: El precio de {nombre} no debería ser negativo")
# Cálculo de atributos derivados
self.impuesto = precio * 0.16 # 16% de impuesto
# Contador de productos vendidos
self.vendidos = 0
def mostrar_info(self):
return f"Producto: {self.nombre}, Precio: ${self.precio}, Stock: {self.stock}, Categoría: {self.categoria}"
def vender(self, cantidad=1):
if cantidad <= self.stock:
self.stock -= cantidad
self.vendidos += cantidad
return f"Vendidos {cantidad} de {self.nombre}. Stock restante: {self.stock}"
else:
return f"Error: Stock insuficiente. Solo hay {self.stock} unidades disponibles."
# Crear productos con diferentes combinaciones de parámetros
producto1 = Producto("Laptop", 999.99, 10, "Electrónica")
producto2 = Producto("Teclado", 49.99, 20) # Sin categoría (usa el valor por defecto)
producto3 = Producto("Mouse", 19.99) # Sin stock ni categoría (usa los valores por defecto)
producto4 = Producto("Monitor", -150, 5, "Periféricos") # Precio negativo (generará advertencia)
# Mostrar información de los productos
print(producto1.mostrar_info())
print(producto2.mostrar_info())
print(producto3.mostrar_info())
print(producto4.mostrar_info())
# Vender productos
print("\nRealizando ventas:")
print(producto1.vender(2))
print(producto2.vender(25)) # Intentar vender más de lo que hay en stock
print(producto3.vender()) # Usar el valor por defecto (1)
En el ejemplo anterior, vimos cómo definir valores por defecto para parámetros opcionales. Esto es muy útil para hacer que las clases sean flexibles y fáciles de usar.
Los métodos mágicos (o dunder methods, por "double underscore") son métodos especiales que comienzan y terminan con doble guion bajo. Permiten definir cómo se comportan los objetos con operadores y funciones integradas de Python.
__init__
: El constructor__str__
: Representación legible para humanos (llamada por str()
y print()
)__repr__
: Representación oficial del objeto (llamada por repr()
)__len__
: Implementa el comportamiento de len()
__add__, __sub__, __mul__
, etc.: Sobrecarga de operadores (+, -, *, etc.)__eq__, __lt__, __gt__
, etc.: Comparaciones (==, <, >, etc.)__getitem__, __setitem__
: Acceso a índices y asignaciónclass Vector:
"""Clase que representa un vector matemático en 2D."""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# Representación para humanos
def __str__(self):
return f"Vector({self.x}, {self.y})"
# Representación para desarrolladores
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"
# Sobrecarga de operadores
def __add__(self, otro):
"""Vector + Vector"""
if isinstance(otro, Vector):
return Vector(self.x + otro.x, self.y + otro.y)
else:
raise TypeError("Solo se puede sumar con otro Vector")
def __sub__(self, otro):
"""Vector - Vector"""
if isinstance(otro, Vector):
return Vector(self.x - otro.x, self.y - otro.y)
else:
raise TypeError("Solo se puede restar con otro Vector")
def __mul__(self, escalar):
"""Vector * escalar"""
if isinstance(escalar, (int, float)):
return Vector(self.x * escalar, self.y * escalar)
else:
raise TypeError("Solo se puede multiplicar por un número")
# Para permitir: escalar * Vector
def __rmul__(self, escalar):
"""escalar * Vector"""
return self.__mul__(escalar)
# Comparación
def __eq__(self, otro):
"""Vector == Vector"""
if isinstance(otro, Vector):
return self.x == otro.x and self.y == otro.y
return False
# Para obtener la longitud (magnitud) del vector
def __abs__(self):
"""Magnitud del vector: |Vector|"""
return (self.x**2 + self.y**2)**0.5
# Para el operador len()
def __len__(self):
"""Dimensión del vector (siempre 2 para vectores 2D)"""
return 2
# Crear vectores
v1 = Vector(3, 4)
v2 = Vector(1, 1)
v3 = Vector(3, 4)
print("Vectores creados:")
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v3 = {v3}")
# Representación para desarrolladores
print("\nRepresentación para desarrolladores:")
print(f"repr(v1) = {repr(v1)}")
# Operaciones aritméticas
print("\nOperaciones:")
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 - v2 = {v1 - v2}")
print(f"v1 * 2 = {v1 * 2}")
print(f"3 * v2 = {3 * v2}") # Esto usa __rmul__
# Comparaciones
print("\nComparaciones:")
print(f"v1 == v2: {v1 == v2}")
print(f"v1 == v3: {v1 == v3}")
# Funciones integradas
print("\nFunciones integradas:")
print(f"abs(v1) = {abs(v1)}") # Magnitud del vector
print(f"len(v1) = {len(v1)}") # Dimensión del vector
# Usar un método mágico directamente (no recomendado, pero posible)
print("\nLlamada directa a un método mágico:")
print(f"v1.__add__(v2) = {v1.__add__(v2)}")
Los métodos mágicos son una de las características más potentes de Python para la programación orientada a objetos, ya que permiten crear clases que se integran perfectamente con el resto del lenguaje.
Vamos a poner en práctica los conceptos aprendidos creando una clase CuentaBancaria
con las siguientes características:
# Crea aquí tu clase CuentaBancaria
# ...
En esta sección, hemos aprendido los conceptos fundamentales de la Programación Orientada a Objetos en Python:
__init__
que inicializa un objeto cuando se crea.