U3 - Programación Orientada a Objetos
1 Contador
class Contador:
def __init__(self, valor_inicial=0):
self._inicial = valor_inicial # se guarda para poder reiniciar
self._valor = valor_inicial # estado actual del contador
def incrementar(self, paso=1):
self._valor += paso
return self._valor
def decrementar(self, paso=1):
self._valor -= paso
return self._valor
def valor(self):
return self._valor
def reiniciar(self):
self._valor = self._inicial
return self._valor
def __str__(self):
return f"Contador(valor={self._valor})"
c = Contador()
c.incrementar(5) # 5
c.decrementar(2) # 3
c.reiniciar()
print(c.valor())
2 Magia para programadores
import math
class Pocion:
def __init__(self, color, volumen):
self.color = color
self.volumen = volumen
def mezclar(self, other):
# NOTE: Se puede hacer de manera más sencilla
volumen_total = self.volumen + other.volumen
w1 = self.volumen / volumen_total
w2 = other.volumen / volumen_total
color = [math.ceil(c1 * w1 + c2 * w2) for c1, c2 in zip(self.color, other.color)]
return Pocion(color=color, volumen=volumen_total)
felix_felicis = Pocion([255, 255, 255], 7)
pocion_multijugos = Pocion([51, 102, 51], 12)
nueva_pocion = felix_felicis.mezclar(pocion_multijugos)
nueva_pocion.color # Devuelve [127, 159, 127]
nueva_pocion.volumen # Devuelve 193 Mensaje secreto
class Cifrado:
def __init__(self, original, alternativo):
if len(original) != len(alternativo):
raise ValueError(
f"len(original) != len(alternativo) ({len(original)} vs {len(alternativo)})"
)
# Se usan tuplas para poder usar .index
self.original = tuple(original)
self.alternativo = tuple(alternativo)
def codificar(self, texto):
# Version corta:
caracteres_codificados = [
self.alternativo[self.original.index(caracter)] for caracter in texto
]
# Version larga
caracteres_codificados = []
for caracter in texto:
indice = self.original.index(caracter)
caracteres_codificados.append(self.alternativo[indice])
return "".join(caracteres_codificados)
def decodificar(self, texto):
# Análogo al método anterior (pero usando generador)
return "".join(self.original[self.alternativo.index(caracter)] for caracter in texto)
alfabeto = "abcdefghijklmnopqrstuvwxyz"
alfabeto_mezclado = "etaoinshrdlucmfwypvbgkjqxz"
mi_cifrado = Cifrado(alfabeto, alfabeto_mezclado)
mi_cifrado.codificar("abc") # => "eta"
mi_cifrado.codificar("xyz") # => "qxz"
mi_cifrado.codificar("aeiou") # => "eirfg"
mi_cifrado.decodificar("eta") # => "abc"
mi_cifrado.decodificar("qxz") # => "xyz"
mi_cifrado.decodificar("eirfg") # => "aeiou"4 Real envido
class ManoDeTruco:
def __init__(self, cartas):
self.cartas = cartas
def comparar_con(self, other):
if self.puntos() >= other.puntos():
return self
return other
def puntos(self):
n_altas = len([carta for carta in self.cartas if carta >= 10])
if n_altas == 3:
# Si solo se tienen cartas altas, los puntos son 20.
return 20
elif n_altas == 2:
# Si se tienen 2 cartas altas los puntos son 20 + la carta que no esta en ese conjunto.
return 20 + min(self.cartas)
elif n_altas == 1:
# Si 1 cartas alta, los puntos son 20 + la suma de las dos cartas bajas
return 20 + sum(sorted(self.cartas)[:2])
# Caso contrario, es la suma de las dos cartas mas altas + 20
return 20 + sum(sorted(self.cartas)[-2:])
mano1 = ManoDeTruco([7, 5, 6])
mano2 = ManoDeTruco([4, 11, 2])
mano1.comparar_con(mano2)
mano1.puntos()
mano2.puntos()5 La muestra infinita
class Muestra:
def __init__(self, iterable):
self._datos = list(iterable) # guardo el iterable en una lista
def agregar(self, x):
self._datos.append(x)
def n(self):
return len(self._datos)
def suma(self):
return sum(self._datos)
def media(self):
n = self.n()
return self.suma() / n
def varianza(self, muestral=False):
"""Varianza de la muestra
- Poblacional (por defecto): sum((xi - μ)^2) / n
- Muestral (muestral=True): sum((xi - x̄)^2) / (n - 1)
"""
n = self.n()
mu = self.media()
ssd = sum((x - mu) ** 2 for x in self._datos) # suma de cuadrados
if muestral:
denom = n - 1
else:
denom = n
# otra opción
# denom = n - 1 if muestral else n
return ssd / denom
@property
def valores(self):
"""Copia inmutable de los datos (evita exponer el interno)."""
return tuple(self._datos)
if __name__ == "__main__":
m = Muestra([10, 12, 13, 15])
m.agregar(20)
print(m.n()) # 5
print(m.suma()) # 70
print(m.media()) # 14.0
print(m.varianza()) # 11.6
print(m.varianza(muestral=True)) # 14.5
print(m.valores) # (10, 12, 13, 15, 20)6 ¡Orden en el laboratorio!
class Experimento:
total_creados = 0 # atributo de clase compartido por **todas** las instancias
def __init__(self, nombre, responsable=None):
# Actualizamos el contador global y usamos ese valor como id
Experimento.total_creados += 1
self.id = Experimento.total_creados
self.nombre = nombre
self.responsable = responsable
@classmethod
def desde_dict(cls, datos):
# crea experimento a partir de diccionario
return cls(datos.get("nombre"), responsable=datos.get("responsable"))
def __repr__(self):
argumentos = (
f"id={self.id}",
f"nombre={self.nombre}",
f"responsable={self.responsable}",
)
return f"Experimento({', '.join(argumentos)})"
e1 = Experimento("Piloto A", responsable="Dolores")
e2 = Experimento.desde_dict({"nombre": "Piloto B", "responsable": "Ana"})
print(Experimento.total_creados) # 2
print(repr(e1)) # Experimento(id=1, nombre='Piloto A', responsable='Dolores')
print(e2) # usa __repr__7 Sensores descalibrados
import random
class Sensor:
def __init__(self, nombre):
self.nombre = nombre
self._offset = 0.0 # por defecto, sin corrección
def calibrar(self, offset):
self._offset = float(offset)
def leer(self):
raise NotImplementedError("Esta clase no implementa 'leer'. Use una subclase.")
class SensorTemperatura(Sensor):
# simula un sensor de temperatura (°C)
def leer(self):
base = random.uniform(18, 28) # simulación de medición
return base + self._offset # corrección por calibración
class SensorHumedad(Sensor):
# Simula un sensor de humedad (%)
def leer(self):
base = random.uniform(30, 70) # simulación de medición
return base + self._offset # corrección por calibración
def promedio_lecturas(sensores, n=5):
"""
Toma n lecturas de cada sensor y devuelve un dict {nombre: promedio}.
Funciona con cualquier subclase de Sensor que implemente 'leer' (polimorfismo).
"""
if n <= 0:
print("n debe ser un entero positivo (>= 1).")
resultados = {}
# para cada sensor
for sensor in sensores:
# n lecturas por sensor
acumulado = 0
for _ in range(n):
acumulado += sensor.leer() # sumamos los resultados de las lecturas
resultados[sensor.nombre] = acumulado / n
return resultados
# --- Ejemplo de uso ---
if __name__ == "__main__":
random.seed(0) # reproducibilidad
t = SensorTemperatura("T1")
h = SensorHumedad("H1")
t.calibrar(0.5)
promedios = promedio_lecturas([t, h], n=3)
print(promedios)8 Python para matemáticos
def mcd(a, b):
# Puede ser math.gcd
while b != 0:
a, b = b, a % b
return abs(a)
class Fraccion:
def __init__(self, numerador, denominador):
if denominador == 0:
raise ValueError("El denominador no puede ser cero")
self.numerador = numerador
self.denominador = denominador
self._simplificar()
def _simplificar(self):
divisor = mcd(self.numerador, self.denominador)
self.numerador = self.numerador // divisor
self.denominador = self.denominador // divisor
# Normalizamos: denominador siempre positivo
if self.denominador < 0:
self.numerador *= -1
self.denominador *= -1
def __str__(self):
return f"{self.numerador}/{self.denominador}"
def __repr__(self):
return f"Fraccion({self.numerador}, {self.denominador})"
def __add__(self, other):
if not isinstance(other, Fraccion):
return NotImplemented
num = self.numerador * other.denominador + other.numerador * self.denominador
den = self.denominador * other.denominador
return Fraccion(num, den)
def __sub__(self, other):
if not isinstance(other, Fraccion):
return NotImplemented
num = self.numerador * other.denominador - other.numerador * self.denominador
den = self.denominador * other.denominador
return Fraccion(num, den)
def __mul__(self, other):
if not isinstance(other, Fraccion):
return NotImplemented
num = self.numerador * other.numerador
den = self.denominador * other.denominador
return Fraccion(num, den)
def __truediv__(self, other):
if not isinstance(other, Fraccion):
return NotImplemented
if other.numerador == 0:
raise ZeroDivisionError("No se puede dividir por cero")
num = self.numerador * other.denominador
den = self.denominador * other.numerador
return Fraccion(num, den)
f1 = Fraccion(4, 5)
f2 = Fraccion(1, 8)
print(f1 + f2) # 37/40
print(f1 - f2) # 27/40
print(f1 * f2) # 1/10
print(f1 / f2) # 32/5
# Operandos no soportados:
f1 + 2
f1 - 2
f1 * 2
f1 / 2
f1 / Fraccion(2, 1) # Pero esto sí :')
f1 * Fraccion(5, 1) # Pero esto sí :')9 Tiempo al tiempo ⏳
DOM = {
1: 31, # Enero
2: 28, # Febrero (29 en año bisiesto)
3: 31, # Marzo
4: 30, # Abril
5: 31, # Mayo
6: 30, # Junio
7: 31, # Julio
8: 31, # Agosto
9: 30, # Septiembre
10: 31, # Octubre
11: 30, # Noviembre
12: 31, # Diciembre
}
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@property
def year(self):
return self._year
@year.setter
def year(self, value):
assert isinstance(value, int)
assert value > 0
self._year = value
@property
def month(self):
return self._month
@month.setter
def month(self, value):
assert isinstance(value, int)
assert 1 <= value <= 12
self._month = value
@property
def day(self):
return self._day
@day.setter
def day(self, value):
assert isinstance(value, int)
assert 1 <= value <= Date._dias_del_mes(self.year, self.month)
self._day = value
@classmethod
def from_str(cls, value):
parts = value.split("-")
assert len(parts) == 3
year, month, day = map(int, map(lambda x: x.lstrip("0"), parts))
assert year > 0
assert 1 <= month <= 12
assert 1 <= day <= Date._dias_del_mes(year, month)
return cls(year=year, month=month, day=day)
def __eq__(self, other):
if isinstance(other, type(self)):
return self.year == other.year and self.month == other.month and self.day == other.day
return False
def __neq__(self, other):
return not self == other
def __gt__(self, other):
if self.year > other.year:
return True
elif self.year == other.year:
if self.month > other.month:
return True
elif self.month == other.month:
return self.day > other.day
return False
def __ge__(self, other):
return (self > other) or (self == other)
def __lt__(self, other):
if self.year < other.year:
return True
elif self.year == other.year:
if self.month < other.month:
return True
elif self.month == other.month:
return self.day < other.day
return False
def __le__(self, other):
return (self < other) or (self == other)
def __str__(self):
year = str(self.year).rjust(4, "0")
month = str(self.month).rjust(2, "0")
day = str(self.day).rjust(2, "0")
return f"{year}-{month}-{day}"
def __repr__(self):
return f"Date(year={self.year}, month={self.month}, day={self.day})"
@staticmethod
def _es_bisiesto(year):
# Divisible por 4,
# excepto que sea divisible por 100,
# salvo que tambien sea divisibles por 400
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
@staticmethod
def _dias_del_mes(year, month):
if month == 2 and Date._es_bisiesto(year):
return 29
return DOM[month]
d = Date(1995, 11, 6)
d
str(d)
Date.from_str("2025-05-06")
d = Date(2025, 11, 12)
d2 = Date(2025, 11, 12)
d3 = Date(2025, 11, 13)
d == d3
d > d3
d3 > d
d < d3
Date.from_str("2025-11-28")
Date(2000, 2, 29)
# Como 'Date' soporta la operación de comparación, podemos usar sorted
fechas = (Date(2022, 2, 28), Date(2025, 1, 31), Date(1816, 7, 9), Date(1810, 5, 25))
sorted(fechas)
# O max ... o min
max(fechas)
min(fechas)
# NOTA: Los métodos estáticos podrían ser tranquilamente funciones auxiliares.