Cargando...

Programación Orientada a Objetos en Python

Publicación: Febrero 27, 2026

La Programación Orientada a Objetos (POO) es la base de la arquitectura de cualquier gran sistema. Una buena implementación puede establecer procesos claros y fácilmente escalables. Sin embargo, es común que un mal primer acercamiento a esta filosofía de diseño genere confusión de por vida entre los desarrolladores. Evitemos el trauma y aprendamos bien desde el inicio.

Hasta el momento hemos programado en Programación Funcional (Functional ProgrammingFP). Es decir, trabajamos con funciones para realizar nuestros procesos. Por ejemplo, cuando hacíamos nuestro juego de blackjack, creamos una función para pedir una carta para agregarla a nuestro mazo. Sin embargo, siempre teníamos que pasarle como parámetro la lista correspondiente a la cuál agregarle esta nueva carta. En cambio, si por ejemplo programáramos este comportamiento orientado a objetos, crearíamos un objeto llamado “jugador” que posee un atributo llamado “mazo” y un método para “agregar carta” que modifica el atributo mazo. En breve entenderemos cómo esta forma de asociar la información y sus procesos es ampliamente utilizada.

Clases vs Objetos

Una clase es un modelo para crear objetos. Piénsalo como el plano para construir la casa. En cambio, la casa ya construida es tu objeto. Es decir, un objeto es una instancia de una clase. Las clases establecen los valores de inicio y métodos (procesos o funciones) de un objeto.

Illustration Illustration
Illustration Illustration

Los pilares de la Programación Orientada a Objetos

La base para crear código bien estructurado, reusable y mantenible a largo plazo

  • AbstracciónDescomponer procesos complejos en pequeños bloques con procesos más simples
  • HerenciaUna clase hija adquiere atributos y métodos de una clase padre. Nos permite rehusar código
  • PolimorfismoMisma operación, diferente comportamiento. Métodos con el mismo nombre funcionan diferente dependiendo del objeto
  • EncapsulaciónMantener una relación bien establecida entre los métodos (procesos) y los atributos (datos) de una clase

Tipos de atributos y métodos

PúblicosSe pueden acceder desde fuera del objetoTodos son públicos por default

PrivadosSólo se pueden acceder desde el objeto en el que están contenidosself.__atribute

EstáticosSe puede acceder sin haber instanciado el objeto@staticmethod

Illustration Illustration

Para saber más sobre la diferencia entre estos métodos, GeeksForGeeks explica muy bien aquí: https://www.geeksforgeeks.org/python/class-method-vs-static-method-vs-instance-method-in-python/

¿Cómo se usa esto en código?

Uno de mis ejemplos favoritos de una clase real que uso bastante es mi clase para manejar las conexiones a la base de datos. La case tiene funciones específicas sobre cómo debe comportarse una conexión a una base X, pero cambiará cuando nos intentemos conectar a una diferente.

Illustration Illustration

Sin embargo, van a compartir ciertos atributos. Tenemos por seguro que sin importar el manejador necesitamos:

  • Conocer el usuario y la contraseña para conectarse
  • Un método para conectarse iniciar la conexión
  • Un método para conectarse ejecutar queries
Illustration Illustration
Go punk!

El número de conexiones a una base de datos es limitado (4000 en MariaDB por ejemplo). Esto quiere decir que por ejemplo, si para cargar una página web para comprar boletos para el concierto de tu artista favorito es necesario hacer 4 queries y hacemos una conexión por cada query, sólo podremos sostener a 1000 usuarios (o menos) que se quieran conectar al mismo tiempo. Esa es una de las razones por la que los sitios se caen…

Entonces, ¿cómo optimizamos esto? Podemos por ejemplo hacer una sola conexión por usuario. Entonces podríamos crear una clase que sólo pueda instanciarse una vez por usuario. Algo así como decir “¿Existo? Si existo, ya no me crees. No no existo, crea una nueva instancia de mi”. Este comportamiento se puede replicar mediante un patrón: Singleton.

Si deseas aprender más sobre patrones te recomiendo: Freeman, E., Robson, E., Freeman, E., Sierra, K., & Bates, B. (2004). Head first design patterns. «O’Reilly Media, Inc.»

Ejemplo práctico: Heladería

Código completo

Imaginemos que tenemos una tienda de helado y queremos llevar un control sobre la relación entre los sabores y precio respecto a las bolitas que se sirven. Creamos una clase llamada IceCream con estos tres atributos (también llamados variables de clase):

class IceCream:
    def __init__(self, flavor, scoops, price):
        # Attributes
        self.flavor = flavor # string 
        self.scoops = scoops # int
        self.price = price # float

__init__: Método de ConstructorEl método que el lenguaje siempre ejecutará al instanciar (crear) un objeto de la clase

self: Elementos relacionados al propio objetoNos permite guardar y acceder a datos del objeto

Ahora, este helado lo vamos a vender, necesitamos servirlo; este es un proceso relacionado con los atributos de la clase. Le mencionaremos las características del producto al cliente y luego le pediremos que nos page. Agregamos un método para “servir” el helado.

class IceCream:
    def __init__(self, flavor, scoops, price):
        # Attributes
        self.flavor = flavor # string
        self.scoops = scoops # int
        self.price = price # float

    def serve(self):
        print(f"This is your {self.scoops} scoop {self.flavor} ice cream. That will be ${self.price}, please")

Por último, ¿qué productos ofrece nuestra pequeña heladería? Vamos a ofrecer PerryThePistachio, MangoTango y MarvelousMint. Son tres variedades diferentes de helado, pero a final de cuentas todos son helado, todos se sirven en bolitas y todos poseen un precio. De nuestra idea sobre las características que debe poseer un helado, crearemos tres diferentes. Es decir, crearemos tres instancias de la clase IceCream.

PerryThePistachio = IceCream('Pistachio', 2, 45.00)
MangoTango = IceCream('Mango', 2, 37.00)
MarvelousMint = IceCream('Mint chocolate chip', 1, 25.00)

Ya tenemos nuestros tres productos. Es conveniente tenerlos juntos, por ejemplo en un menu. Como anteriormente habíamos mencionado, python trata cualquier tipo de estructura de dato como un objeto (espero que esto tenga sentido ahora); así que hagamos una lista que contenga estos objetos:

menu = [PerryThePistachio, MangoTango, MarvelousMint]

Sirvamos un PerryThePistachio:

PerryThePistachio.serve()
This is your 2 scoop Pistachio ice cream. That will be $45.0, please

¿Qué pasa si un cliente quiere toppings en su helado? En esta heladería por +$10 se le agregarán toppings. Entonces podemos agregar un parámetro a nuestro método para servir en el cual se modifique el precio si el cliente desea complementos.

def serve(self, toppings=False):
    total = self.price
    print(f"This is your {self.scoops} scoop {self.flavor} ice cream.")

    if toppings == True:
        total += 10
        print("It includes toppings.")

    print(f"That will be ${total}, please")

Ahora, cuando volvamos a invocar al método para servir, le podremos indicar si esta porción incluye toppings o no:

PerryThePistachio.serve(toppings=True)
This is your 2 scoop Pistachio ice cream.
It includes toppings.
That will be $55.0, please

Por último, también queremos vender paletas que por +$5 se les puede agregar cubierta de chocolate. Sin embargo, las paletas se venden por pieza, no por bolita. Entonces es conveniente crear una clase padre que contenga las características comunes de ambos productos para que luego sus clases hijas IceCream y IcePop hereden pero agreguen sus diferencias particulares (ambas formas son correctas):

class Product:
    def __init__(self, flavor, price):
        self.flavor = flavor # string
        self.price = price # float

    def serve(self):
        total = self.price
        print(f"That will be ${total}, please\n")

Ahora, para poder heredar los atributos de la clase padre, tenemos dos formas en python:

Llamada explícita al objeto padre Product: Product.__init__(self, flavor, price)

class IceCream(Product):
    def __init__(self, flavor, price, scoops):
        Product.__init__(self, flavor, price)
        self.flavor = flavor # string
        self.price = price # float
        self.scoops = scoops  # int

    def serve(self, toppings=False):
        total = self.price
        print(f"This is your {self.scoops} scoop {self.flavor} ice cream.")

        if toppings:
            total += 10
            print("It includes toppings.")

        print(f"That will be ${total}, please\n")

Función super(): super().__init__(flavor, price)

  • Aquí ya no es obligatorio pasar el argumento de self
  • Si el nombre de la clase principal cambia, ya no hay necesidad de hacer una refactorización
  • Es menos explícitoPuedes agregar confusión innecesaria cuando existen múltiples clases
class IcePop(Product):
    def __init__(self, flavor, price):
        super().__init__(flavor, price)
        self.flavor = flavor # string
        self.price = price # float

    def serve(self, chocolate_coat=False):
        total = self.price
        print(f"This is your {self.flavor} ice pop.")

        if chocolate_coat:
            total += 10
            print("It includes a chocolate coat")

        print(f"That will be ${total}, please\n")

Observa cómo es que sobreescribimos (override) el método serve() original que sólo imprimía el total a ahora contemplar toppings o cobertura de chocolate.

Con dos tipos de producto, ahora resulta más conveniente que nuestro menú sea un diccionario

PerryThePistachio = IceCream('Pistachio', 45.00,2)
MangoTango = IceCream('Mango', 37.00, 2,)
MarvelousMint = IceCream('Mint chocolate chip', 25.00,1)

PoppyBerryMix = IcePop('BerryMix', 32.00)
VanillaPop = IcePop('Vanilla', 21.00)

menu = {
    'Ice Cream': [PerryThePistachio, MangoTango, MarvelousMint],
    'Ice Pops': [PoppyBerryMix, VanillaPop]
}

Probemos las nuevas clases

PerryThePistachio.serve(toppings=True)
PoppyBerryMix.serve(chocolate_coat=True)
MarvelousMint.serve(toppings=False)
This is your 2 scoop Pistachio ice cream.
It includes toppings.
That will be $55.0, please

This is your BerryMix ice pop.
It includes a chocolate coat
That will be $42.0, please

This is your 1 scoop Mint chocolate chip ice cream.
That will be $25.0, please

Etiquetas relacionadas:
Arriba