Programación Orientada a Objetos
## Objetivos de Aprendizaje
- Entender clases y objetos
- Dominar herencia y polimorfismo
- Aprender encapsulamiento y ocultación de datos
- Trabajar con métodos especiales
## Clases y Objetos
### Definir una Clase
```python
class Perro:
"""Una clase de perro simple."""
def __init__(self, nombre, edad):
self.nombre = nombre # Atributo de instancia
self.edad = edad
def ladrar(self):
return f"¡{self.nombre} dice Guau!"
def obtener_info(self):
return f"{self.nombre} tiene {self.edad} años"
# Creando una instancia
mi_perro = Perro("Buddy", 3)
print(mi_perro.ladrar()) # ¡Buddy dice Guau!
print(mi_perro.obtener_info()) # Buddy tiene 3 años
```
### El Método __init__
```python
class Perro:
def __init__(self, nombre, edad=1):
self.nombre = nombre
self.edad = edad
```
### Atributos de Instancia
```python
perro1 = Perro("Rex", 2)
perro2 = Perro("Max")
print(perro1.nombre) # Rex
print(perro2.edad) # 1 (por defecto)
```
## Atributos de Clase
```python
class Perro:
especie = "Canis familiaris" # Atributo de clase
def __init__(self, nombre, edad):
self.nombre = nombre # Atributo de instancia
self.edad = edad
print(Perro.especie) # Canis familiaris
print(perro1.especie) # Canis familiaris
```
## Métodos de Instancia vs de Clase
### Métodos de Instancia
```python
class Perro:
def __init__(self, nombre):
self.nombre = nombre
def ladrar(self): # Método de instancia - toma self
return f"{self.nombre} ladra!"
```
### Métodos de Clase
```python
class Perro:
especie = "Canis"
@classmethod
def obtener_especie(cls):
return cls.especie
print(Perro.obtener_especie()) # Canis
```
### Métodos Estáticos
```python
class Matematica:
@staticmethod
def sumar(a, b):
return a + b
print(Matematica.sumar(2, 3)) # 5
```
## Herencia
### Herencia Básica
```python
class Animal:
def __init__(self, nombre):
self.nombre = nombre
def hablar(self):
raise NotImplementedError
class Perro(Animal): # Hereda de Animal
def hablar(self):
return f"{self.nombre} dice ¡Guau!"
class Gato(Animal):
def hablar(self):
return f"{self.nombre} dice ¡Miau!"
perro = Perro("Buddy")
gato = Gato("Whiskers")
print(perro.hablar()) # Buddy dice ¡Guau!
print(gato.hablar()) # Whiskers dice ¡Miau!
```
### La Función super()
```python
class Animal:
def __init__(self, nombre):
self.nombre = nombre
class Perro(Animal):
def __init__(self, nombre, raza):
super().__init__(nombre) # Llama al __init__ del padre
self.raza = raza
perro = Perro("Rex", "Golden Retriever")
print(perro.nombre) # Rex
print(perro.raza) # Golden Retriever
```
### Herencia Múltiple
```python
class Volador:
def volar(self):
return "¡Volando!"
class Nadador:
def nadar(self):
return "¡Nadando!"
class Pato(Volador, Nadador):
pass
pato = Pato()
print(pato.volar()) # ¡Volando!
print(pato.nadar()) # ¡Nadando!
```
## Polimorfismo
```python
def hacer_hablar(animal):
print(animal.hablar())
animales = [Perro("Rex"), Gato("Whiskers")]
for animal in animales:
hacer_hablar(animal)
# Rex dice ¡Guau!
# Whiskers dice ¡Miau!
```
## Encapsulamiento
### Atributos Privados
```python
class CuentaBancaria:
def __init__(self, saldo):
self._saldo = saldo # Convención: privado
def obtener_saldo(self):
return self._saldo
def depositar(self, cantidad):
if cantidad > 0:
self._saldo += cantidad
return True
return False
cuenta = CuentaBancaria(100)
print(cuenta.obtener_saldo()) # 100
# cuenta._saldo # Todavía accesible pero no recomendado
```
### Name Mangling
```python
class MiClase:
def __init__(self):
self.__privado = 42 # Se transforma a _MiClase__privado
obj = MiClase()
print(obj._MiClase__privado) # 42 (no hagas esto)
```
### Decorador Property
```python
class CuentaBancaria:
def __init__(self, saldo):
self._saldo = saldo
@property
def saldo(self):
return self._saldo
@saldo.setter
def saldo(self, valor):
if valor >= 0:
self._saldo = valor
else:
raise ValueError("El saldo no puede ser negativo")
cuenta = CuentaBancaria(100)
print(cuenta.saldo) # 100
cuenta.saldo = 200 # Usa el setter
```
## Métodos Especiales (Magic Methods)
### __str__ y __repr__
```python
class Persona:
def __init__(self, nombre, edad):
self.nombre = nombre
self.edad = edad
def __str__(self):
return f"{self.nombre}, {self.edad}"
def __repr__(self):
return f"Persona('{self.nombre}', {self.edad})"
p = Persona("Ana", 30)
print(str(p)) # Ana, 30
print(repr(p)) # Persona('Ana', 30)
```
### __eq__ y __hash__
```python
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, otro):
return self.x == otro.x and self.y == otro.y
def __hash__(self):
return hash((self.x, self.y))
p1 = Punto(1, 2)
p2 = Punto(1, 2)
print(p1 == p2) # True
print(hash(p1) == hash(p2)) # True (puede usar en sets/dicts)
```
### __len__ y __getitem__
```python
class Inventario:
def __init__(self):
self.elementos = []
def agregar(self, elemento):
self.elementos.append(elemento)
def __len__(self):
return len(self.elementos)
def __getitem__(self, indice):
return self.elementos[indice]
inv = Inventario()
inv.agregar("manzana")
inv.agregar("plátano")
print(len(inv)) # 2
print(inv[0]) # manzana
```
### __call__
```python
class Multiplicador:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
doblar = Multiplicador(2)
print(doblar(5)) # 10 (se llama como función)
```
## Clases Abstractas
```python
from abc import ABC, abstractmethod
class Forma(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimetro(self):
pass
class Rectangulo(Forma):
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
def area(self):
return self.ancho * self.alto
def perimetro(self):
return 2 * (self.ancho + self.alto)
# forma = Forma() # TypeError: No se puede instanciar clase abstracta
rect = Rectangulo(5, 3)
print(rect.area()) # 15
```
## Data Classes (Python 3.7+)
```python
from dataclasses import dataclass
@dataclass
class Persona:
nombre: str
edad: int
email: str = "desconocido@ejemplo.com" # Por defecto
persona = Persona("Ana", 30)
print(persona) # Persona(nombre='Ana', edad=30, email='desconocido@ejemplo.com')
```
## Resumen
- Las clases definen objetos con atributos y métodos
- `__init__` inicializa nuevas instancias
- Los atributos de instancia pertenecen a objetos; los de clase a la clase
- `@classmethod` y `@staticmethod` para tipos alternativos de métodos
- Herencia: `class Hijo(Padre):`
- `super().__init__()` llama al constructor padre
- Usa la convención `_attr` para atributos privados
- `@property` crea atributos administrados
- Métodos especiales: `__str__`, `__eq__`, `__init__`, etc.
- `@dataclass` auto-genera código repetitivo
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →