Orientação a Objetos: classes e objetos
1. Introdução à Programação Orientada a Objetos em Python
A Programação Orientada a Objetos (POO) é um paradigma de programação que organiza o código em torno de "objetos" — entidades que combinam dados (atributos) e comportamentos (métodos). Em Python, a POO não é apenas suportada, mas profundamente integrada à linguagem: praticamente tudo em Python é um objeto, de inteiros a funções.
Os conceitos fundamentais são simples: uma classe funciona como um molde ou planta, definindo a estrutura e o comportamento que seus objetos terão. Um objeto (ou instância) é uma concretização desse molde, com valores próprios para cada atributo.
Diferente de linguagens como Java ou C++, Python adota uma abordagem mais flexível. Não existe encapsulamento rígido (atributos privados são uma convenção, não uma imposição) e o sistema de tipos é dinâmico, favorecendo o duck typing: "se anda como um pato e grasna como um pato, então é um pato". Isso significa que o que importa são os métodos e atributos que um objeto possui, não sua classe formal.
2. Definindo Classes e Criando Objetos
A sintaxe para definir uma classe em Python utiliza a palavra-chave class, seguida do nome em PascalCase (primeira letra de cada palavra em maiúsculo):
class Pessoa:
pass # classe vazia, apenas para demonstração
Para criar um objeto, chamamos a classe como se fosse uma função:
pessoa1 = Pessoa()
pessoa2 = Pessoa()
O método especial __init__ é o construtor da classe. Ele é executado automaticamente quando criamos um novo objeto e serve para inicializar seus atributos:
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
p = Pessoa("Alice", 30)
print(p.nome) # Alice
3. Atributos de Instância
Atributos de instância são variáveis que pertencem a cada objeto individualmente. Eles são definidos dentro de __init__ usando o parâmetro self, que representa a própria instância:
class Carro:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
self.ligado = False # valor padrão
meu_carro = Carro("Toyota", "Corolla")
print(meu_carro.marca) # Toyota
print(meu_carro.ligado) # False
Acessamos e modificamos atributos usando a notação ponto:
meu_carro.ligado = True
print(meu_carro.ligado) # True
Uma característica interessante (e controversa) do Python é a possibilidade de adicionar atributos dinamicamente fora da classe:
meu_carro.cor = "Azul" # atributo criado apenas para esta instância
Isso é flexível, mas pode levar a bugs difíceis de rastrear. Por isso, é boa prática declarar todos os atributos esperados no __init__.
4. Métodos de Instância
Métodos de instância são funções definidas dentro da classe que operam sobre os dados do objeto. O primeiro parâmetro de todo método de instância é self:
class Calculadora:
def __init__(self, valor_inicial=0):
self.valor = valor_inicial
def somar(self, numero):
self.valor += numero
return self.valor
def subtrair(self, numero):
self.valor -= numero
return self.valor
calc = Calculadora(10)
print(calc.somar(5)) # 15
print(calc.subtrair(3)) # 12
Métodos podem interagir entre si através de self:
class Contador:
def __init__(self):
self.contagem = 0
def incrementar(self):
self.contagem += 1
def resetar(self):
self.contagem = 0
print("Contador zerado!")
def processar(self, vezes=1):
for _ in range(vezes):
self.incrementar()
self.resetar()
c = Contador()
c.processar(5) # Contador zerado!
5. O Parâmetro self e o Escopo de Objeto
O self é uma referência explícita à própria instância do objeto. Diferente de linguagens como Java ou C++, onde this é implícito, Python exige que self seja sempre o primeiro parâmetro dos métodos de instância.
Isso torna o código mais explícito: quando você vê self.atributo, sabe imediatamente que está acessando um atributo daquele objeto específico.
class Comparador:
def __init__(self, valor):
self.valor = valor
def comparar_com(self, outro_objeto):
if self.valor > outro_objeto.valor:
return f"{self.valor} é maior que {outro_objeto.valor}"
elif self.valor < outro_objeto.valor:
return f"{self.valor} é menor que {outro_objeto.valor}"
else:
return "São iguais"
a = Comparador(10)
b = Comparador(20)
print(a.comparar_com(b)) # 10 é menor que 20
6. Comparação entre Objetos e Identidade
Python oferece duas formas principais de comparar objetos:
==: verifica igualdade de valor (pode ser personalizada)is: verifica identidade de objeto (se duas variáveis referenciam o mesmo objeto na memória)
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (mesmo conteúdo)
print(a is b) # False (objetos diferentes)
print(a is c) # True (mesmo objeto)
Podemos personalizar a comparação com == implementando o método especial __eq__:
class Pessoa:
def __init__(self, nome, cpf):
self.nome = nome
self.cpf = cpf
def __eq__(self, outra):
if not isinstance(outra, Pessoa):
return False
return self.cpf == outra.cpf
p1 = Pessoa("João", "123")
p2 = Pessoa("João", "123")
p3 = Pessoa("Maria", "456")
print(p1 == p2) # True (mesmo CPF)
print(p1 == p3) # False (CPFs diferentes)
Objetos em Python são mutáveis por padrão — podemos alterar seus atributos após a criação. Isso difere de tipos imutáveis como int ou str, onde qualquer "modificação" cria um novo objeto.
7. Boas Práticas com Classes e Objetos
Algumas práticas recomendadas ao trabalhar com classes em Python:
- Nomenclatura: Classes em PascalCase (
MinhaClasse), instâncias e métodos em snake_case (meu_objeto,meu_metodo) - Mantenha o
__init__simples: O construtor deve apenas inicializar atributos. Lógica complexa vai para métodos separados - Documente atributos esperados: Use docstrings ou type hints para deixar claro quais atributos uma instância deve ter
- Evite atributos dinâmicos excessivos: Eles tornam o código imprevisível. Declare tudo no
__init__
class Produto:
"""Representa um produto em estoque."""
def __init__(self, nome: str, preco: float, quantidade: int = 0):
self.nome = nome
self.preco = preco
self.quantidade = quantidade
def aplicar_desconto(self, percentual: float):
"""Aplica um percentual de desconto ao preço."""
if 0 <= percentual <= 100:
self.preco *= (1 - percentual / 100)
8. Exemplo Prático Combinando os Conceitos
Vamos criar uma classe ContaBancaria que demonstra todos os conceitos vistos:
class ContaBancaria:
def __init__(self, titular: str, saldo_inicial: float = 0.0):
self.titular = titular
self.saldo = saldo_inicial
self._transacoes = [] # convenção: _ indica "privado"
def depositar(self, valor: float) -> str:
if valor <= 0:
return "Valor inválido para depósito"
self.saldo += valor
self._transacoes.append(f"Depósito: +R${valor:.2f}")
return f"Depósito de R${valor:.2f} realizado. Saldo: R${self.saldo:.2f}"
def sacar(self, valor: float) -> str:
if valor <= 0:
return "Valor inválido para saque"
if valor > self.saldo:
return "Saldo insuficiente"
self.saldo -= valor
self._transacoes.append(f"Saque: -R${valor:.2f}")
return f"Saque de R${valor:.2f} realizado. Saldo: R${self.saldo:.2f}"
def extrato(self) -> str:
return f"Titular: {self.titular}\nSaldo: R${self.saldo:.2f}"
def __str__(self) -> str:
return f"Conta de {self.titular} - Saldo: R${self.saldo:.2f}"
def __repr__(self) -> str:
return f"ContaBancaria('{self.titular}', {self.saldo})"
# Testando a classe
conta1 = ContaBancaria("Alice", 1000)
conta2 = ContaBancaria("Bob", 500)
print(conta1.depositar(200)) # Depósito de R$200.00 realizado. Saldo: R$1200.00
print(conta1.sacar(300)) # Saque de R$300.00 realizado. Saldo: R$900.00
print(conta2.sacar(600)) # Saldo insuficiente
print(conta1) # Conta de Alice - Saldo: R$900.00 (usando __str__)
print(repr(conta2)) # ContaBancaria('Bob', 500) (usando __repr__)
# Independência entre objetos
print(conta1.saldo) # 900.0
print(conta2.saldo) # 500.0
O método __str__ fornece uma representação amigável para usuários finais (usada por print()), enquanto __repr__ oferece uma representão técnica que idealmente poderia recriar o objeto (usada no console interativo).
A POO em Python é poderosa e flexível. Dominar classes e objetos é o primeiro passo para escrever código mais organizado, reutilizável e alinhado com o espírito da linguagem.
Referências
- Documentação Oficial: Classes em Python — Tutorial oficial do Python abordando classes, objetos, métodos especiais e herança
- Python 3 Object-Oriented Programming (Real Python) — Guia completo sobre POO em Python com exemplos práticos e boas práticas
- PEP 8 — Style Guide for Python Code — Convenções de nomenclatura para classes (PascalCase) e métodos (snake_case)
- Duck Typing in Python (GeeksforGeeks) — Explicação detalhada sobre duck typing e sua relação com a POO em Python
- Understanding
selfin Python (Programiz) — Artigo aprofundado sobre o parâmetroselfe seu papel nos métodos de instância