# Objetos, Herencia y Polimorfismo en Python

* [Objetos, Herencia y Polimorfismo en Python](#objetos-herencia-y-polimorfismo-en-python)
  * [Objetos y Clases](#objetos-y-clases)
  * [Atributos y Métodos](#atributos-y-métodos)
  * [Constructores](#constructores)
  * [Herencia](#herencia)
  * [Sobrescritura de Métodos](#sobrescritura-de-métodos)
  * [Polimorfismo](#polimorfismo)
  * [Ejemplos Prácticos](#ejemplos-prácticos)
    * [1. Sistema de Vehículos](#1-sistema-de-vehículos)
    * [2. Sistema de Figuras Geométricas](#2-sistema-de-figuras-geométricas)

## Objetos y Clases

En Python, **todo es un objeto**. Las clases son plantillas para crear objetos con atributos y métodos específicos.

```py
# Definición de una clase
class Persona:
    pass

# Creación de un objeto
persona1 = Persona()
```

## Atributos y Métodos

Los **atributos** son características del objeto y los **métodos** son funciones que definen el comportamiento del objeto.

```py
class Persona:
    # Atributos
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad
    
    # Método
    def saludar(self):
        return f"Hola, mi nombre es {self.nombre}."

# Crear un objeto
persona1 = Persona("Juan", 25)
print(persona1.saludar())  # Salida: Hola, mi nombre es Juan.
```

## Constructores

El método `__init__` se utiliza como constructor para inicializar atributos.

```py
class Coche:
    def __init__(self, marca, modelo):
        self.marca = marca
        self.modelo = modelo
```

## Herencia

Permite que una clase derive de otra, reutilizando atributos y métodos.

```py
# Clase base
class Animal:
    def __init__(self, nombre):
        self.nombre = nombre

    def hablar(self):
        pass

# Clase derivada
class Perro(Animal):
    def hablar(self):
        return "Guau"

class Gato(Animal):
    def hablar(self):
        return "Miau"

# Crear instancias
perro = Perro("Fido")
gato = Gato("Whiskers")
print(perro.hablar())  # Salida: Guau
print(gato.hablar())   # Salida: Miau
```

## Sobrescritura de Métodos

Las clases hijas pueden sobrescribir métodos de la clase padre.

```py
class Animal:
    def hablar(self):
        return "Sonido genérico"

class Pajaro(Animal):
    def hablar(self):
        return "Pío Pío"

pajaro = Pajaro()
print(pajaro.hablar())  # Salida: Pío Pío
```

## Polimorfismo

Permite usar un método de forma genérica, independientemente de la clase específica.

```py
animales = [Perro("Fido"), Gato("Whiskers"), Pajaro()]
for animal in animales:
    print(animal.hablar())
# Salida:
# Guau
# Miau
# Pío Pío
```

## Ejemplos Prácticos

### 1. Sistema de Vehículos

```py
class Vehiculo:
    def __init__(self, tipo):
        self.tipo = tipo
    
    def moverse(self):
        pass

class Coche(Vehiculo):
    def moverse(self):
        return "El coche se mueve en carretera."

class Barco(Vehiculo):
    def moverse(self):
        return "El barco navega en el agua."

vehiculos = [Coche("Coche"), Barco("Barco")]
for vehiculo in vehiculos:
    print(vehiculo.moverse())
```

### 2. Sistema de Figuras Geométricas

```py
class Figura:
    def area(self):
        pass

class Rectangulo(Figura):
    def __init__(self, ancho, alto):
        self.ancho = ancho
        self.alto = alto
    
    def area(self):
        return self.ancho * self.alto

class Circulo(Figura):
    def __init__(self, radio):
        self.radio = radio
    
    def area(self):
        import math
        return math.pi * self.radio ** 2

figuras = [Rectangulo(4, 5), Circulo(3)]
for figura in figuras:
    print(figura.area())
```
