POO en Python: Clases, Objetos y Herencia para Principiantes — Curso Python #8
Introduccion: que es la POO y por que es importante

Bienvenido a la parte 8 de 10 de nuestro curso de Python para principiantes. Hasta ahora hemos usado funciones y modulos para organizar nuestro codigo. Ahora vamos a dar un salto importante: la Programacion Orientada a Objetos (POO).
La POO es una forma de pensar y estructurar tu codigo basada en objetos. Pero no te preocupes, es mas simple de lo que parece. Usemos una analogia:
¿Por que es importante la POO?
- Organiza mejor el codigo: agrupa datos y funciones relacionadas en un solo lugar
- Reutiliza codigo: puedes crear nuevas clases basandote en otras que ya existen
- Modela el mundo real: puedes representar cosas como usuarios, productos, vehiculos
- Es un estandar: casi todos los lenguajes modernos usan POO (Java, C#, JavaScript, etc.)
Crear tu primera clase con class
Una clase se crea con la palabra clave class. Por convencion, los nombres de clases se escriben en PascalCase (cada palabra empieza con mayuscula).
1# La clase mas simple posible
2class Perro:
3 pass # "pass" significa "no hacer nada por ahora"
4
5# Crear un objeto (una instancia de la clase)
6mi_perro = Perro()
7print(mi_perro) # <__main__.Perro object at 0x...>
8print(type(mi_perro)) # <class '__main__.Perro'>
Acabamos de crear una clase Perro vacia y un objeto mi_perro. Ahora vamos a darle caracteristicas y comportamientos.
Agregar atributos directamente
1class Perro:
2 pass
3
4mi_perro = Perro()
5
6# Podemos agregar atributos sobre la marcha
7mi_perro.nombre = "Rex"
8mi_perro.raza = "Labrador"
9mi_perro.edad = 3
10
11print(f"{mi_perro.nombre} es un {mi_perro.raza} de {mi_perro.edad} anos")
12# Rex es un Labrador de 3 anos
Esto funciona, pero no es la forma correcta de hacerlo. Para eso existe el metodo __init__.
El metodo __init__ (constructor): inicializar atributos
El metodo __init__ es el constructor de la clase. Se ejecuta automaticamente cada vez que creas un nuevo objeto. Aqui es donde defines los atributos iniciales.
1class Perro:
2 def __init__(self, nombre, raza, edad):
3 self.nombre = nombre
4 self.raza = raza
5 self.edad = edad
6
7# Ahora al crear el objeto, pasamos los datos
8mi_perro = Perro("Rex", "Labrador", 3)
9
10print(f"{mi_perro.nombre} es un {mi_perro.raza} de {mi_perro.edad} anos")
11# Rex es un Labrador de 3 anos
12
13# Podemos crear muchos objetos con la misma clase
14otro_perro = Perro("Luna", "Golden Retriever", 5)
15print(f"{otro_perro.nombre} es un {otro_perro.raza} de {otro_perro.edad} anos")
16# Luna es un Golden Retriever de 5 anos
Desglosemos lo que paso:
__init__se ejecuta automaticamente al hacerPerro("Rex", "Labrador", 3)selfes una referencia al objeto que se esta creando (lo explicaremos a fondo en la siguiente seccion)self.nombre = nombreguarda el valor "Rex" como atributo del objeto
1class Perro:
2 def __init__(self, nombre, raza="Mestizo", edad=1):
3 self.nombre = nombre
4 self.raza = raza
5 self.edad = edad
6
7# Solo con el nombre
8perro1 = Perro("Firulais")
9print(f"{perro1.nombre}, {perro1.raza}, {perro1.edad} ano")
10# Firulais, Mestizo, 1 ano
self: la referencia al objeto actual
El parametro self es probablemente lo que mas confunde a los principiantes. Pero es simple:
self es una referencia al objeto que esta usando el metodo. Cuando escribes self.nombre, estas diciendo "el atributo nombre de este objeto en particular".
1class Gato:
2 def __init__(self, nombre):
3 self.nombre = nombre # self.nombre = atributo del objeto
4 # nombre = parametro que pasamos
5
6 def presentarse(self):
7 # Aqui self se refiere al gato especifico que llama al metodo
8 print(f"Miau, soy {self.nombre}")
9
10# Creamos dos gatos
11gato1 = Gato("Michi")
12gato2 = Gato("Pelusa")
13
14gato1.presentarse() # Miau, soy Michi
15gato2.presentarse() # Miau, soy Pelusa
Cuando llamas gato1.presentarse(), Python automaticamente pasa gato1 como self. Por eso no necesitas escribir gato1.presentarse(gato1).
self siempre debe ser el primer parametro de todos los metodos de una clase. Python lo pasa automaticamente, tu nunca lo escribes al llamar el metodo.
Metodos: funciones dentro de una clase
Los metodos son funciones que pertenecen a una clase. Definen lo que un objeto puede hacer.
1class CuentaBancaria:
2 def __init__(self, titular, saldo=0):
3 self.titular = titular
4 self.saldo = saldo
5
6 def depositar(self, cantidad):
7 """Agrega dinero a la cuenta."""
8 if cantidad > 0:
9 self.saldo += cantidad
10 print(f"Deposito de {cantidad}. Nuevo saldo: {self.saldo}")
11 else:
12 print("La cantidad debe ser positiva")
13
14 def retirar(self, cantidad):
15 """Retira dinero de la cuenta."""
16 if cantidad > self.saldo:
17 print(f"Saldo insuficiente. Tienes {self.saldo}")
18 elif cantidad <= 0:
19 print("La cantidad debe ser positiva")
20 else:
21 self.saldo -= cantidad
22 print(f"Retiro de {cantidad}. Nuevo saldo: {self.saldo}")
23
24 def consultar_saldo(self):
25 """Muestra el saldo actual."""
26 print(f"Titular: {self.titular}")
27 print(f"Saldo actual: {self.saldo}")
28
29# Crear una cuenta y usarla
30cuenta = CuentaBancaria("Ana Lopez", 1000)
31cuenta.consultar_saldo()
32# Titular: Ana Lopez
33# Saldo actual: $1000
34
35cuenta.depositar(500)
36# Deposito de $500. Nuevo saldo: $1500
37
38cuenta.retirar(200)
39# Retiro de $200. Nuevo saldo: $1300
40
41cuenta.retirar(5000)
42# Saldo insuficiente. Tienes $1300
Observa como cada metodo usa self.saldo para acceder y modificar el saldo del objeto especifico. Si creas otra cuenta, tendra su propio saldo independiente.
Herencia: crear una clase a partir de otra
La herencia te permite crear una clase nueva que hereda todos los atributos y metodos de otra clase. Es como decir: "Un Perro es un Animal, pero con caracteristicas adicionales".
1class Animal:
2 """Clase base (padre) para todos los animales."""
3
4 def __init__(self, nombre, especie):
5 self.nombre = nombre
6 self.especie = especie
7 self.energia = 100
8
9 def comer(self):
10 self.energia += 10
11 print(f"{self.nombre} esta comiendo. Energia: {self.energia}")
12
13 def dormir(self):
14 self.energia += 20
15 print(f"{self.nombre} esta durmiendo. Energia: {self.energia}")
16
17 def info(self):
18 print(f"{self.nombre} ({self.especie}) - Energia: {self.energia}")
19
20
21class Perro(Animal):
22 """Clase hija que hereda de Animal."""
23
24 def __init__(self, nombre, raza):
25 # super() llama al __init__ de la clase padre (Animal)
26 super().__init__(nombre, especie="Canino")
27 self.raza = raza
28
29 def ladrar(self):
30 self.energia -= 5
31 print(f"{self.nombre} dice: ¡Guau guau!")
32
33 def traer_pelota(self):
34 self.energia -= 15
35 print(f"{self.nombre} trae la pelota. Energia: {self.energia}")
36
37
38class Gato(Animal):
39 """Otra clase hija que hereda de Animal."""
40
41 def __init__(self, nombre, color):
42 super().__init__(nombre, especie="Felino")
43 self.color = color
44
45 def maullar(self):
46 self.energia -= 3
47 print(f"{self.nombre} dice: ¡Miau!")
48
49 def ronronear(self):
50 print(f"{self.nombre} esta ronroneando... prrr")
51
52
53# Usar las clases
54rex = Perro("Rex", "Labrador")
55rex.info() # Rex (Canino) - Energia: 100 (heredado de Animal)
56rex.comer() # Rex esta comiendo. Energia: 110 (heredado de Animal)
57rex.ladrar() # Rex dice: ¡Guau guau! (propio de Perro)
58rex.traer_pelota() # Rex trae la pelota. Energia: 90
59
60michi = Gato("Michi", "naranja")
61michi.info() # Michi (Felino) - Energia: 100
62michi.dormir() # Michi esta durmiendo. Energia: 120
63michi.maullar() # Michi dice: ¡Miau!
64michi.ronronear() # Michi esta ronroneando... prrr
Puntos clave de la herencia:
class Perro(Animal)— los parentesis indican de quien heredasuper().__init__(...)— llama al constructor del padre para inicializar los atributos heredados- La clase hija tiene todo lo del padre mas sus propios atributos y metodos
- La clase hija puede sobreescribir metodos del padre si necesita un comportamiento diferente
Encapsulamiento basico: atributos publicos y privados
El encapsulamiento es la idea de proteger los datos internos de un objeto para que no se modifiquen accidentalmente desde fuera. En Python, esto se hace por convencion (no por restriccion del lenguaje).
| Convencion | Significado | Ejemplo |
|---|---|---|
nombre | Publico: cualquiera puede acceder | self.nombre = "Rex" |
_nombre | Protegido: no deberia accederse desde fuera (convencion) | self._saldo = 1000 |
__nombre | Privado: Python le cambia el nombre internamente (name mangling) | self.__password = "123" |
1class Usuario:
2 def __init__(self, nombre, email, password):
3 self.nombre = nombre # Publico
4 self._email = email # Protegido (por convencion)
5 self.__password = password # Privado (name mangling)
6
7 def verificar_password(self, intento):
8 """Verifica la contrasena sin exponer el valor real."""
9 return self.__password == intento
10
11 def mostrar_info(self):
12 # Dentro de la clase SI podemos acceder a atributos privados
13 print(f"Nombre: {self.nombre}")
14 print(f"Email: {self._email}")
15 print(f"Password: {'*' * len(self.__password)}")
16
17
18usuario = Usuario("Ana", "[email protected]", "miClave123")
19
20# Publico: acceso libre
21print(usuario.nombre) # Ana
22
23# Protegido: funciona, pero NO deberias usarlo fuera de la clase
24print(usuario._email) # [email protected]
25
26# Privado: da error si intentas acceder directamente
27# print(usuario.__password) # AttributeError!
28
29# La forma correcta es usar un metodo
30print(usuario.verificar_password("miClave123")) # True
31print(usuario.verificar_password("otraClave")) # False
Metodos especiales: __str__, __repr__, __len__
Python tiene metodos especiales (tambien llamados "metodos magicos" o "dunder methods") que puedes definir para personalizar el comportamiento de tus objetos.
1class Producto:
2 def __init__(self, nombre, precio, stock):
3 self.nombre = nombre
4 self.precio = precio
5 self.stock = stock
6
7 def __str__(self):
8 """Se llama cuando usas print() o str()."""
9 return f"{self.nombre} - {self.precio} ({self.stock} unidades)"
10
11 def __repr__(self):
12 """Se llama en la consola interactiva o al depurar."""
13 return f"Producto('{self.nombre}', {self.precio}, {self.stock})"
14
15 def __len__(self):
16 """Se llama cuando usas len()."""
17 return self.stock
18
19 def __eq__(self, otro):
20 """Se llama cuando comparas con ==."""
21 if not isinstance(otro, Producto):
22 return False
23 return self.nombre == otro.nombre and self.precio == otro.precio
24
25
26# Sin __str__, print mostraria algo como: <__main__.Producto object at 0x...>
27producto = Producto("Laptop", 999.99, 15)
28
29print(producto) # Laptop - $999.99 (15 unidades) (__str__)
30print(repr(producto)) # Producto('Laptop', 999.99, 15) (__repr__)
31print(len(producto)) # 15 (__len__)
32
33# Comparar productos
34p1 = Producto("Mouse", 25.00, 100)
35p2 = Producto("Mouse", 25.00, 50)
36p3 = Producto("Teclado", 45.00, 30)
37
38print(p1 == p2) # True (mismo nombre y precio)
39print(p1 == p3) # False (diferente nombre)
Los metodos especiales mas comunes:
| Metodo | Se activa con | Uso |
|---|---|---|
__str__ | print(obj), str(obj) | Representacion legible para humanos |
__repr__ | Consola interactiva, repr(obj) | Representacion tecnica para developers |
__len__ | len(obj) | Retornar un largo/tamano |
__eq__ | obj1 == obj2 | Comparar igualdad |
__lt__ | obj1 < obj2 | Comparar menor que |
__add__ | obj1 + obj2 | Sumar objetos |
Ejemplo practico: sistema de gestion de una biblioteca
Vamos a combinar todo lo aprendido en un ejemplo completo: un sistema para gestionar libros y prestamos de una biblioteca.
1from datetime import date, timedelta
2
3
4class Libro:
5 """Representa un libro de la biblioteca."""
6
7 def __init__(self, titulo, autor, isbn, copias=1):
8 self.titulo = titulo
9 self.autor = autor
10 self.isbn = isbn
11 self.copias = copias
12 self._disponibles = copias
13
14 def esta_disponible(self):
15 """Verifica si hay copias disponibles."""
16 return self._disponibles > 0
17
18 def prestar(self):
19 """Presta una copia del libro."""
20 if self.esta_disponible():
21 self._disponibles -= 1
22 return True
23 return False
24
25 def devolver(self):
26 """Devuelve una copia del libro."""
27 if self._disponibles < self.copias:
28 self._disponibles += 1
29 return True
30 return False
31
32 def __str__(self):
33 return f"'{self.titulo}' por {self.autor} ({self._disponibles}/{self.copias} disponibles)"
34
35 def __repr__(self):
36 return f"Libro('{self.titulo}', '{self.autor}', '{self.isbn}')"
37
38
39class Usuario:
40 """Representa un usuario de la biblioteca."""
41
42 def __init__(self, nombre, id_usuario):
43 self.nombre = nombre
44 self.id_usuario = id_usuario
45 self.libros_prestados = []
46
47 def tiene_libro(self, libro):
48 """Verifica si el usuario tiene un libro prestado."""
49 return any(p["libro"].isbn == libro.isbn for p in self.libros_prestados)
50
51 def __str__(self):
52 return f"Usuario: {self.nombre} (ID: {self.id_usuario}) - {len(self.libros_prestados)} libros prestados"
53
54
55class Biblioteca:
56 """Sistema de gestion de la biblioteca."""
57
58 def __init__(self, nombre):
59 self.nombre = nombre
60 self._libros = []
61 self._usuarios = []
62 self._historial = []
63
64 def agregar_libro(self, libro):
65 """Agrega un libro al catalogo."""
66 self._libros.append(libro)
67 print(f"Libro agregado: {libro.titulo}")
68
69 def registrar_usuario(self, usuario):
70 """Registra un nuevo usuario."""
71 self._usuarios.append(usuario)
72 print(f"Usuario registrado: {usuario.nombre}")
73
74 def prestar_libro(self, usuario, libro):
75 """Presta un libro a un usuario."""
76 if not libro.esta_disponible():
77 print(f"'{libro.titulo}' no esta disponible")
78 return False
79
80 if usuario.tiene_libro(libro):
81 print(f"{usuario.nombre} ya tiene '{libro.titulo}'")
82 return False
83
84 libro.prestar()
85 fecha_devolucion = date.today() + timedelta(days=14)
86 prestamo = {
87 "libro": libro,
88 "fecha_prestamo": date.today(),
89 "fecha_devolucion": fecha_devolucion,
90 }
91 usuario.libros_prestados.append(prestamo)
92 self._historial.append({
93 "tipo": "prestamo",
94 "usuario": usuario.nombre,
95 "libro": libro.titulo,
96 "fecha": date.today(),
97 })
98 print(f"Prestamo exitoso: '{libro.titulo}' a {usuario.nombre}")
99 print(f" Devolver antes de: {fecha_devolucion}")
100 return True
101
102 def devolver_libro(self, usuario, libro):
103 """Devuelve un libro prestado."""
104 for i, prestamo in enumerate(usuario.libros_prestados):
105 if prestamo["libro"].isbn == libro.isbn:
106 libro.devolver()
107 usuario.libros_prestados.pop(i)
108 self._historial.append({
109 "tipo": "devolucion",
110 "usuario": usuario.nombre,
111 "libro": libro.titulo,
112 "fecha": date.today(),
113 })
114 print(f"Devolucion exitosa: '{libro.titulo}' de {usuario.nombre}")
115 return True
116
117 print(f"{usuario.nombre} no tiene '{libro.titulo}'")
118 return False
119
120 def buscar_libro(self, termino):
121 """Busca libros por titulo o autor."""
122 resultados = [
123 libro for libro in self._libros
124 if termino.lower() in libro.titulo.lower()
125 or termino.lower() in libro.autor.lower()
126 ]
127 return resultados
128
129 def mostrar_catalogo(self):
130 """Muestra todos los libros del catalogo."""
131 print(f"\n=== Catalogo de {self.nombre} ===")
132 for libro in self._libros:
133 estado = "Disponible" if libro.esta_disponible() else "No disponible"
134 print(f" {libro} - {estado}")
135 print()
136
137
138# === Probando el sistema ===
139
140# Crear la biblioteca
141biblio = Biblioteca("Biblioteca Central")
142
143# Agregar libros
144libro1 = Libro("Cien Anos de Soledad", "Gabriel Garcia Marquez", "978-0307474728", copias=3)
145libro2 = Libro("El Principito", "Antoine de Saint-Exupery", "978-0156012195", copias=2)
146libro3 = Libro("Don Quijote", "Miguel de Cervantes", "978-0060934347", copias=1)
147
148biblio.agregar_libro(libro1)
149biblio.agregar_libro(libro2)
150biblio.agregar_libro(libro3)
151
152# Registrar usuarios
153usuario1 = Usuario("Maria Garcia", "U001")
154usuario2 = Usuario("Juan Lopez", "U002")
155
156biblio.registrar_usuario(usuario1)
157biblio.registrar_usuario(usuario2)
158
159# Mostrar catalogo
160biblio.mostrar_catalogo()
161
162# Prestar libros
163biblio.prestar_libro(usuario1, libro1)
164biblio.prestar_libro(usuario2, libro1)
165biblio.prestar_libro(usuario1, libro2)
166
167# Ver estado
168biblio.mostrar_catalogo()
169print(usuario1)
170print(usuario2)
171
172# Devolver un libro
173biblio.devolver_libro(usuario1, libro1)
174biblio.mostrar_catalogo()
175
176# Buscar libros
177print("Busqueda 'quijote':")
178for libro in biblio.buscar_libro("quijote"):
179 print(f" {libro}")
_) y relaciones entre objetos. En un proyecto real, tambien guardarias los datos en una base de datos, pero la estructura de clases seria muy similar.
Resumen y proximo articulo
En este articulo aprendiste los fundamentos de la Programacion Orientada a Objetos en Python:
- Clases: planos para crear objetos, definidas con
class - __init__: el constructor que inicializa los atributos del objeto
- self: la referencia al objeto actual dentro de los metodos
- Metodos: funciones que pertenecen a una clase y definen su comportamiento
- Herencia: crear clases nuevas basandose en clases existentes
- Encapsulamiento: proteger datos internos con la convencion
_nombre - Metodos especiales:
__str__,__repr__,__len__para personalizar comportamiento
En el proximo articulo (parte 9 de 10) aprenderemos sobre manejo de archivos y excepciones: como leer y escribir archivos, manejar errores de forma elegante con try/except, y crear programas mas robustos. ¡No te lo pierdas!
Comments
Sign in to leave a comment
No comments yet. Be the first!