Este guia é um material de estudo focado na aplicação prática e teórica dos conceitos de Programação Orientada a Objetos em Python.
- Como Instalar e Executar o Python
- Conceitos Fundamentais
- 1. Classe Básica e Construtor (
__init__) - 2. Métodos Mágicos / Dunder (
__str__,__len__,__eq__) - 3. Herança e
super() - 4. Polimorfismo
- 5. Encapsulamento e Propriedades (
@property) - 6. Métodos de Classe (
@classmethod) e Estáticos (@staticmethod) - 7. Classes Abstratas (
abc) - Resumo dos Recursos
- 8. Diagramas UML Nativos (com Mermaid.js)
- 9. Tópicos Modernos de POO em Python
Para executar os scripts deste repositório, é necessário ter o Python instalado no seu computador (versão 3.8 ou superior recomendada).
- Acesse o site oficial: python.org/downloads.
- Faça o download do instalador da versão mais recente.
- Atenção: Na primeira tela do instalador, marque obrigatoriamente a opção "Add python.exe to PATH" antes de clicar em Install Now.
Abra o terminal e execute os comandos:
sudo apt update
sudo apt install python3 python3-pip
No terminal, navegue até a pasta do repositório e execute o arquivo desejado:
# No Windows
python arquivo.py
# No Linux ou macOS
python3 arquivo.py- Classe: É o "molde" ou blueprint que define a estrutura de dados e os comportamentos que um determinado conceito deve ter.
- Objeto (Instância): É um elemento real criado a partir da classe, contendo seus próprios valores para os atributos definidos.
Todas as classes possuem uma função embutida chamada init(), que é executada automaticamente quando a classe é instanciada. Ela serve para inicializar os atributos do objeto.
O parâmetro self é uma referência à instância atual da classe. Ele é utilizado para acessar variáveis e métodos que pertencem àquela classe específica.
O método __init__() é o construtor executado automaticamente ao instanciar um objeto. O parâmetro self representa a própria instância da classe.
class Carro:
# Construtor da classe
def __init__(self, marca: str, modelo: str, ano: int):
self.marca = marca # Atributo de instância
self.modelo = modelo # Atributo de instância
self.ano = ano # Atributo de instância
self.ligado = False # Atributo com valor padrão
# Método de instância
def ligar(self):
self.ligado = True
print(f"O {self.modelo} está ligado.")
def exibir_info(self):
status = "Ligado" if self.ligado else "Desligado"
print(f"{self.marca} {self.modelo} ({self.ano}) - Status: {status}")
# Instanciando objetos
meu_carro = Carro("Toyota", "Corolla", 2022)
meu_carro.exibir_info() # Toyota Corolla (2022) - Status: Desligado
meu_carro.ligar() # O Corolla está ligado.Métodos que começam e terminam com dois sublinhados (__) alteram como objetos se comportam em operações nativas do Python (impressão, comparação, tamanho, etc.).
class Livro:
def __init__(self, titulo: str, autor: str, paginas: int):
self.titulo = titulo
self.autor = autor
self.paginas = paginas
# Define o que é retornado quando usamos print(objeto) ou str(objeto)
def __str__(self):
return f"'{self.titulo}' por {self.autor}"
# Define o retorno quando usamos len(objeto)
def __len__(self):
return self.paginas
# Define o comportamento do operador de igualdade (==)
def __eq__(self, outro_livro):
if isinstance(outro_livro, Livro):
return self.titulo == outro_livro.titulo and self.autor == outro_livro.autor
return False
livro1 = Livro("Aventuras no Código", "Ana Silva", 320)
livro2 = Livro("Aventuras no Código", "Ana Silva", 320)
print(livro1) # Imprime: 'Aventuras no Código' por Ana Silva
print(len(livro1)) # Imprime: 320
print(livro1 == livro2) # Imprime: TrueHerança permite que uma classe filha reutilize atributos e métodos de uma classe pai, evitando duplicação de código.
# Classe Pai (Superclasse)
class Funcionario:
def __init__(self, nome: str, salario: float):
self.nome = nome
self.salario = salario
def calcular_bonus(self):
return self.salario * 0.10
# Classe Filha (Subclasse)
class Gerente(Funcionario):
def __init__(self, nome: str, salario: float, departamento: str):
# Chama o construtor da classe Pai
super().__init__(nome, salario)
self.departamento = departamento
# Sobrescrita de método (Override)
def calcular_bonus(self):
return self.salario * 0.20
func = Funcionario("Carlos", 3000.0)
gerente = Gerente("Mariana", 8000.0, "TI")
print(f"Bônus {func.nome}: R$ {func.calcular_bonus():.2f}") # R$ 300.00
print(f"Bônus {gerente.nome}: R$ {gerente.calcular_bonus():.2f}") # R$ 1600.00Polimorfismo significa "muitas formas". Permite tratar objetos de classes diferentes usando a mesma interface ou nome de método.
class Cachorro:
def emitir_som(self):
return "Au Au!"
class Gato:
def emitir_som(self):
return "Miau!"
class Pato:
def emitir_som(self):
return "Quack!"
# Função polimórfica que aceita qualquer objeto com o método emitir_som()
def fazer_animal_falar(animal):
print(animal.emitir_som())
animais = [Cachorro(), Gato(), Pato()]
for animal in animais:
fazer_animal_falar(animal)Protege atributos contra alterações indevidas. Em Python:
_atributo (um underline) = Protegido (convenção para uso interno).
__atributo (dois underlines) = Privado (name mangling).
Usa-se o decorador @property para criar getters e setters de forma elegante.
class ContaBancaria:
def __init__(self, titular: str, saldo_inicial: float = 0.0):
self.titular = titular
self.__saldo = saldo_inicial # Atributo privado
# Getter para ler o saldo
@property
def saldo(self):
return self.__saldo
# Setter para alterar o saldo com validações
@saldo.setter
def saldo(self, valor: float):
if valor < 0:
print("Erro: O saldo não pode ser negativo!")
else:
self.__saldo = valor
def depositar(self, valor: float):
if valor > 0:
self.__saldo += valor
conta = ContaBancaria("João", 1000.0)
print(f"Saldo atual: R$ {conta.saldo:.2f}")
conta.saldo = 1500.0 # Usa o setter
print(f"Novo saldo: R$ {conta.saldo:.2f}")
conta.saldo = -500.0 # Retorna mensagem de erro e bloqueia a alteração@classmethod: Recebe a classe (cls) como primeiro argumento. Ideal para métodos fabris (construtores alternativos).
@staticmethod: Não recebe self nem cls. Funciona como uma função comum agrupada dentro do contexto da classe.
class Data:
def __init__(self, dia: int, mes: int, ano: int):
self.dia = dia
self.mes = mes
self.ano = ano
# Class Method: cria uma instância a partir de uma string "DD/MM/AAAA"
@classmethod
def de_string(cls, data_str: str):
dia, mes, ano = map(int, data_str.split("/"))
return cls(dia, mes, ano)
# Static Method: função utilitária independente de instâncias
@staticmethod
def eh_ano_bissexto(ano: int) -> bool:
return (ano % 4 == 0 and ano % 100 != 0) or (ano % 400 == 0)
# Usando o método de classe para criar o objeto
d1 = Data.de_string("25/12/2024")
print(f"Data criada: {d1.dia}/{d1.mes}/{d1.ano}")
# Usando o método estático
print("2024 é bissexto?", Data.eh_ano_bissexto(2024)) # TrueClasses abstratas não podem ser instanciadas diretamente e exigem que suas subclasses implementem os métodos decorados com @abstractmethod.
from abc import ABC, abstractmethod
class FormaGeometrica(ABC):
@abstractmethod
def calcular_area(self) -> float:
pass
@abstractmethod
def calcular_perimetro(self) -> float:
pass
class Retangulo(FormaGeometrica):
def __init__(self, largura: float, altura: float):
self.largura = largura
self.altura = altura
def calcular_area(self) -> float:
return self.largura * self.altura
def calcular_perimetro(self) -> float:
return 2 * (self.largura + self.altura)
# forma = FormaGeometrica() # ERRO! Não é possível instanciar classes abstratas.
r = Retangulo(5.0, 3.0)
print(f"Área: {r.calcular_area()}") # 15.0
print(f"Perímetro: {r.calcular_perimetro()}") # 16.0| Conceito | Elemento / Decorador | Objetivo |
|---|---|---|
| Construtor | def __init__(self) |
Inicializa os atributos do objeto |
| Representação em Texto | def __str__(self) |
Define a exibição do objeto ao usar print() |
| Herança | class Filha(Pai): |
Herda métodos e atributos de outra classe |
| Encapsulamento | @property / @setter |
Protege variáveis e valida alterações de valor |
| Método de Classe | @classmethod |
Manipula dados da classe / Construtores alternativos |
| Método Estático | @staticmethod |
Funções utilitárias sem dependência de estado |
| Classe Abstrata | ABC / @abstractmethod |
Garante uma estrutura obrigatória em subclasses |
classDiagram
class FormaGeometrica {
<<Abstract>>
+calcular_area()* float
+calcular_perimetro()* float
}
class Retangulo {
+float largura
+float altura
+calcular_area() float
+calcular_perimetro() float
}
FormaGeometrica <|-- Retangulo : Implementa
classDiagram
class Motor {
+int cv
+bool ligado
+ligar() void
}
class Carro {
+String marca
+String modelo
+Motor motor
+ligar_carro() void
}
Carro *-- Motor : Composição (Tem-um)
Introduzidas no Python 3.7, as @dataclass eliminam o código repetitivo (boilerplate) necessário para criar classes focadas em armazenar dados, gerando automaticamente os métodos init(), repr(), eq() e outros.
from dataclasses import dataclass, field
@dataclass
class ItemPedido:
nome: str
preco: float
quantidade: int = 1 # Valor padrão
# Método para calcular o valor total do item
def total(self) -> float:
return self.preco * self.quantidade
item1 = ItemPedido("Teclado Mecânico", 250.0, 2)
item2 = ItemPedido("Mousepad", 50.0)
print(item1) # Retorno automático formatado: ItemPedido(nome='Teclado Mecânico', preco=250.0, quantidade=2)
print(item1.total()) # 500.0
print(item1 == item2) # False (compara os valores dos atributos automaticamente)Criar classes de exceções próprias herdando da classe base Exception é uma boa prática para domínios de negócios específicos, tornando a tratativa de erros mais clara e profissional.
# Classe de exceção personalizada
class SaldoInsuficienteError(Exception):
"""Exceção lançada quando uma operação excede o saldo disponível."""
def __init__(self, saldo_atual: float, valor_saque: float):
self.saldo_atual = saldo_atual
self.valor_saque = valor_saque
super().__init__(f"Tentativa de sacar R$ {valor_saque:.2f}, mas o saldo atual é de apenas R$ {saldo_atual:.2f}.")
class ContaCorrente:
def __init__(self, saldo: float):
self.saldo = saldo
def sacar(self, valor: float):
if valor > self.saldo:
raise SaldoInsuficienteError(self.saldo, valor)
self.saldo -= valor
print(f"Saque de R$ {valor:.2f} realizado com sucesso!")
conta = ContaCorrente(100.0)
try:
conta.sacar(250.0)
except SaldoInsuficienteError as e:
print(f"Erro na Operação: {e}")Um dos princípios do design de software orientado a objetos é "Preferir composição a herança".
Herança: Define uma relação de "É UM" (ex: Gerente é um Funcionario).
Composição: Define uma relação de "TEM UM" (ex: Carro tem um Motor).
A composição evita hierarquias de herança rígidas e difíceis de manter.
class Motor:
def __init__(self, potencia_cv: int):
self.potencia_cv = potencia_cv
self.ligado = False
def ligar(self):
self.ligado = True
def desligar(self):
self.ligado = False
class Carro:
def __init__(self, modelo: str, potencia_motor: int):
self.modelo = modelo
# Composição: O objeto Carro contém um objeto Motor
self.motor = Motor(potencia_motor)
def dar_partida(self):
self.motor.ligar()
print(f"O {self.modelo} ligou o motor de {self.motor.potencia_cv} CV!")
meu_carro = Carro("Golf GTI", 230)
meu_carro.dar_partida() # O Golf GTI ligou o motor de 230 CV!