Cristhian Villegas
Backend11 min read0 views

POO en Python: Clases, Objetos y Herencia para Principiantes — Curso Python #8

POO en Python: Clases, Objetos y Herencia para Principiantes — Curso Python #8

Introduccion: que es la POO y por que es importante

Logo de Python

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:

📌 Analogia: Piensa en un molde de galletas. El molde es la clase: define la forma, el tamano y las caracteristicas. Cada galleta que haces con ese molde es un objeto. Todas las galletas tienen la misma forma, pero puedes decorar cada una de manera diferente (chocolate, vainilla, fresa). La clase es el plano, el objeto es la cosa real.

¿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).

python
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

python
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.

python
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 hacer Perro("Rex", "Labrador", 3)
  • self es una referencia al objeto que se esta creando (lo explicaremos a fondo en la siguiente seccion)
  • self.nombre = nombre guarda el valor "Rex" como atributo del objeto
💡 Tip: Puedes asignar valores por defecto a los parametros del constructor, igual que con las funciones normales:
python
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".

python
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).

📌 Regla importante: 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.

python
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".

python
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 hereda
  • super().__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
💡 Tip: Usa herencia cuando hay una relacion clara de "es un": un Perro es un Animal, un Estudiante es una Persona. Si la relacion es "tiene un" (un Auto tiene un Motor), usa composicion en su lugar (un atributo que es otro objeto).

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).

ConvencionSignificadoEjemplo
nombrePublico: cualquiera puede accederself.nombre = "Rex"
_nombreProtegido: no deberia accederse desde fuera (convencion)self._saldo = 1000
__nombrePrivado: Python le cambia el nombre internamente (name mangling)self.__password = "123"
python
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
⚠️ Nota: Python no tiene verdaderos atributos privados como Java o C#. El guion bajo es una convencion que dice "por favor, no toques esto desde fuera". Los buenos programadores respetan esa convencion.

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.

python
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:

MetodoSe activa conUso
__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 == obj2Comparar igualdad
__lt__obj1 < obj2Comparar menor que
__add__obj1 + obj2Sumar 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.

python
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}")
💡 Tip: Este ejemplo usa los conceptos de clases, metodos, encapsulamiento (atributos con _) 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!

Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles