Code & Development

Object-Oriented Programming in Python: for those who got stuck trying to learn it

Have you ever felt like you understood the words of OOP, but understood nothing? It's not a lack of talent. Someone gave you the key before showing you the lock. This article corrects that, revealing the real problem that Object-Oriented Programming in Python solves. Put syntax aside and understand why classes, inheritance, encapsulation, and polymorphism exist, with practical examples.

Object-Oriented Programming in Python: for those who got stuck trying to learn it

You didn't get stuck because OOP is difficult. You got stuck because no one showed you the problem before giving you the solution. This article fixes that — for real.

First, an honest question

Have you ever reached the "object-oriented programming" part of a tutorial and felt that specific thing — the sensation that you understood the words but understood nothing?

You read: "a class is a blueprint for creating objects". It made sense on the surface. But that unanswered question remained: why would I need this? When would I use it? My code works without it...

This feeling isn't a lack of talent. It's a sign that someone handed you the key before showing you the lock.

A metaphor showing a hand holding a shiny, complex key, but looking confused or frustrated because there's no lock visible. The key looks out of place or useless without its corresponding problem. Emphasize the missing context. flat design, dark background, no text, no labels.

So let's start with the problem. Not the solution.

The problem you'll soon feel

A visual representation of messy, unstructured data. Multiple colored boxes or files are scattered haphazardly, connected by tangled, broken lines, symbolizing a lack of organization and increasing complexity in a system. flat design, dark background, no text, no labels.

Imagine you're building a system for a pet shop. Small, just for learning. You start like this:

TEXT
# Looks ok at first...gato1_nome = "Whiskers"gato1_idade = 3gato1_dono = "Ana"cachorro1_nome = "Rex"cachorro1_idade = 5cachorro1_dono = "Bruno"

It works. For now. But then you add more animals. And more. And you start needing functions. And to update data. And anytime you look at the code, there's a background noise saying: this isn't right.

That noise has a name: lack of structure. And that's exactly where OOP exists to help you.

Not to make the code "more elegant". But to solve this specific pain you just imagined — and which you'll truly feel in your next project.

The class: a blueprint that makes sense

A class is a way of telling Python: "this set of data and behaviors go together, and they have a name."

Think of an animal's registration form. It always has a name, age, and owner. And it can always be updated, queried, printed. The class is that form — and each animal you register is a filled-out copy of it.

PYTHON
class Animal:    def __init__(self, nome, idade, dono):        # __init__ runs automatically when you create the animal        self.nome = nome        self.idade = idade        self.dono = dono    def apresentar(self):        print(f"{self.nome}, {self.idade} anos — dono: {self.dono}")# Now creating 100 animals is simple, clean, and consistentgato = Animal("Whiskers", 3, "Ana")cachorro = Animal("Rex", 5, "Bruno")gato.apresentar()     # Whiskers, 3 anos — dono: Anacachorro.apresentar() # Rex, 5 anos — dono: Bruno
The four pillars — no mystery
01
Inheritance

Avoid rewriting what already exists. Child inherits from parent.

02
Encapsulation

Protect internal data. Expose only what needs to be exposed.

03
Polymorphism

Same call, different results. Flexible code.

04
Abstraction

Hide complexity. Show only the essentials to the user.

OOP has four concepts that appear everywhere. Before seeing the code, understand why each one exists:

Inheritance — because repeating code is a trap

You have Animal. Now you want to create Gato (Cat) and Cachorro (Dog) with specific behaviors — but without rewriting name, age, and owner again. Inheritance solves this:

PYTHON
class Gato(Animal):  # inherits everything from Animal    def falar(self):        print(f"{self.nome} diz: miau 🐱")    def apresentar(self):  # overrides the parent's method        super().apresentar()  # still calls the parent's        print("...I'm very independent, by the way. 😼")class Cachorro(Animal):    def falar(self):        print(f"{self.nome} diz: au au 🐶")whiskers = Gato("Whiskers", 3, "Ana")rex = Cachorro("Rex", 5, "Bruno")whiskers.apresentar()  # uses the overridden apresentarrex.falar()            # Rex diz: au au 🐶

Encapsulation — because not everything should be accessed directly

Think of a bank account. The balance exists, but no one from outside should be able to write account.balance = 999999 directly. Encapsulation creates this protection:

PYTHON
class ContaBancaria:    def __init__(self, titular, saldo):        self.titular = titular        self.__saldo = saldo  # __ = private, no direct access    def depositar(self, valor):        if valor > 0:            self.__saldo += valor            print(f"Deposit of R${valor:.2f} made ✅")    def sacar(self, valor):        if valor > self.__saldo:            print("Insufficient balance ❌")        else:            self.__saldo -= valor            print(f"Withdrawal of R${valor:.2f} made ✅")    def ver_saldo(self):        return f"R${self.__saldo:.2f}"conta = ContaBancaria("Ana", 1000)conta.depositar(500)conta.sacar(200)print(conta.ver_saldo())  # R$1300.00# conta.__saldo → AttributeError. Protected. 🔒

Polymorphism — the same call, each object responding in its own way

An illustration showing a single button labeled 'send' being pressed. From this single action, three different visual outcomes radiate: an email icon, an SMS message bubble, and a push notification bell, each with a distinct visual representation of its message being delivered. This demonstrates multiple responses from a single action. flat design, dark background, no text, no labels.

This is the part that seems magical when you understand it. You call .enviar() (send) on any object in the system — and each one knows what to do:

PYTHON
class Notificacao:    def __init__(self, destino, mensagem):        self.destino = destino        self._mensagem = mensagem    def enviar(self):        raise NotImplementedError("Implement in subclasses")class Email(Notificacao):    def enviar(self):        print(f"📧 Email → {self.destino}: {self._mensagem}")class SMS(Notificacao):    def enviar(self):        print(f"📱 SMS → {self.destino}: {self._mensagem}")class Push(Notificacao):    def enviar(self):        print(f"🔔 Push → {self.destino}: {self._mensagem}")# Polymorphism: same function, three different behaviorsdef disparar(fila):    for n in fila:        n.enviar()disparar([    Email("ana@email.com", "New article on Blueprint Blog!"),    SMS("+55 22 99999-0000", "Check it out 📲"),    Push("user-42", "Don't miss this 🔥"),])

You learned OOP. But what did you really gain?

It wasn't just new syntax. You gained a way of thinking about code that will change how you organize any project from now on.

When you look at a problem now, you start asking: what are the entities here? What do they have in common? What is specific to each one? This reasoning is what separates code that lasts from code you abandon.