Classes são plantas arquitetônicas para criar objetos.
Objetos são instâncias concretas que ocupam memória.
Primeiramente, uma classe define atributos (dados) e métodos (comportamentos).
Por exemplo, uma classe Cachorro pode ter nome e latir().
Além disso, cada objeto tem seus próprios valores de atributos.
A voz passiva é usada aqui: “os objetos são criados a partir da classe”.
Quando utilizar classes e objetos? Em sistemas complexos e reutilizáveis.
Também quando você precisa modelar entidades do mundo real.
Python é uma linguagem orientada a objetos desde o início.
Vamos explorar sintaxe, construtores e encapsulamento.
Três subtítulos guiarão você pelos fundamentos da POO.
Ao final, você criará suas próprias classes com confiança.
Sintaxe básica: criando classes e instâncias
Use a palavra-chave class seguida do nome da classe.
O método __init__ é o construtor especial.
Ele inicializa os atributos do novo objeto.
O parâmetro self refere-se à própria instância.
Quando usar classes? Sempre que você agrupar dados e comportamentos.
A voz passiva é aplicada: “os atributos são definidos com self”.
Exemplo de classe básica:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# Definindo uma classe simples class Pessoa: """Classe que representa uma pessoa.""" # Construtor (inicializador) def __init__(self, nome, idade): self.nome = nome # Atributo público self.idade = idade # Método de instância def saudacao(self): return f"Olá, meu nome é {self.nome}" def aniversario(self): self.idade += 1 return f"{self.nome} agora tem {self.idade} anos" def __str__(self): """Representação amigável do objeto.""" return f"Pessoa(nome={self.nome}, idade={self.idade})" # Criando objetos (instâncias) pessoa1 = Pessoa("Ana", 25) pessoa2 = Pessoa("Carlos", 30) # Acessando atributos e métodos print(pessoa1.saudacao()) print(f"Idade da Ana: {pessoa1.idade}") pessoa1.aniversario() print(f"Após aniversário: {pessoa1.idade}") print(pessoa2.saudacao()) print(pessoa2) # Usa __str__ automaticamente # Classe com atributo de classe (compartilhado) class ContaBancaria: # Atributo de classe (compartilhado entre todas as instâncias) taxa_juros = 0.02 def __init__(self, titular, saldo_inicial=0): self.titular = titular self.saldo = saldo_inicial def depositar(self, valor): self.saldo += valor return self.saldo def sacar(self, valor): if valor <= self.saldo: self.saldo -= valor return True return False def aplicar_juros(self): juros = self.saldo * ContaBancaria.taxa_juros self.saldo += juros return juros # Criando contas conta1 = ContaBancaria("João", 1000) conta2 = ContaBancaria("Maria", 500) print(f"\nConta de {conta1.titular}: saldo R${conta1.saldo}") conta1.depositar(200) print(f"Após depósito: R${conta1.saldo}") print(f"Taxa de juros: {ContaBancaria.taxa_juros * 100}%") juros = conta1.aplicar_juros() print(f"Juros aplicados: R${juros:.2f}") print(f"Novo saldo: R${conta1.saldo:.2f}") |
Cada objeto mantém seus próprios valores de atributos. Atributos de classe são compartilhados por todas as instâncias.
Encapsulamento: protegendo dados internos
Encapsulamento esconde detalhes internos da classe.
Em Python, usamos convenção de nomes para indicar proteção.
Um underscore _atributo significa “protegido”.
Dois underscores __atributo causam name mangling.
Quando usar encapsulamento? Quando um atributo não deve ser alterado diretamente.
Por exemplo, um saldo bancário só muda via métodos.
A voz passiva é aplicada: “as propriedades são usadas para controle de acesso”.
Exemplo de encapsulamento:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
class Produto: """Classe com atributos encapsulados.""" def __init__(self, nome, preco, estoque): self.nome = nome # Público self._preco = preco # Protegido (convenção) self.__estoque = estoque # Privado (name mangling) # Getter para preço (propriedade) @property def preco(self): return self._preco # Setter com validação @preco.setter def preco(self, novo_preco): if novo_preco < 0: raise ValueError("Preço não pode ser negativo") self._preco = novo_preco # Getter para estoque @property def estoque(self): return self.__estoque # Método para alterar estoque com validação def remover_estoque(self, quantidade): if quantidade <= 0: raise ValueError("Quantidade deve ser positiva") if quantidade > self.__estoque: raise ValueError("Estoque insuficiente") self.__estoque -= quantidade return self.__estoque def adicionar_estoque(self, quantidade): if quantidade <= 0: raise ValueError("Quantidade deve ser positiva") self.__estoque += quantidade return self.__estoque def __str__(self): return f"Produto: {self.nome}, Preço: R${self.preco:.2f}, Estoque: {self.estoque}" # Usando a classe produto = Produto("Notebook", 2500.00, 10) print(produto) # Acessando via propriedade (getter) print(f"Preço do {produto.nome}: R${produto.preco:.2f}") # Alterando via setter com validação produto.preco = 2400.00 print(f"Novo preço: R${produto.preco:.2f}") # Tentativa de acesso direto ao atributo privado (falha) try: print(produto.__estoque) except AttributeError as e: print(f"Erro: {e} (name mangling)") # Acesso correto via métodos produto.remover_estoque(3) print(f"Estoque após remoção: {produto.estoque}") produto.adicionar_estoque(5) print(f"Estoque após adição: {produto.estoque}") # Demonstração do name mangling print(f"\nNome real do atributo privado: {dir(produto)[-1]}") |
Propriedades com @property permitem getters e setters elegantes.
Name mangling transforma __estoque em _Produto__estoque.
Métodos especiais e boas práticas
Python oferece métodos especiais (dunder methods) para personalização.
__init__ constrói, __str__ exibe, __repr__ depura.
__len__ define o tamanho, __add__ sobrecarrega +.
Quando usar métodos especiais? Para tornar objetos mais “pythônicos”.
Por exemplo, permitir len(obj) ou obj1 + obj2.
A voz passiva é aplicada: “as operações são definidas pelos métodos mágicos”.
Exemplo de classe com métodos especiais:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
class Vetor: """Representa um vetor matemático 2D.""" def __init__(self, x, y): self.x = x self.y = y def __str__(self): """String amigável para usuários.""" return f"Vetor({self.x}, {self.y})" def __repr__(self): """String para depuração (precisa recriar objeto).""" return f"Vetor({self.x}, {self.y})" def __add__(self, outro): """Soma de vetores (sobrecarga do operador +).""" if not isinstance(outro, Vetor): raise TypeError("Só é possível somar Vetor com Vetor") return Vetor(self.x + outro.x, self.y + outro.y) def __sub__(self, outro): """Subtração de vetores (sobrecarga do operador -).""" return Vetor(self.x - outro.x, self.y - outro.y) def __mul__(self, escalar): """Multiplicação por escalar.""" if isinstance(escalar, (int, float)): return Vetor(self.x * escalar, self.y * escalar) raise TypeError("Multiplicação apenas por número") def __rmul__(self, escalar): """Multiplicação com escalar à esquerda (escalar * vetor).""" return self.__mul__(escalar) def __eq__(self, outro): """Comparação de igualdade.""" if not isinstance(outro, Vetor): return False return self.x == outro.x and self.y == outro.y def __len__(self): """Retorna o número de componentes (sempre 2).""" return 2 def __abs__(self): """Módulo (norma) do vetor.""" return (self.x ** 2 + self.y ** 2) ** 0.5 def __getitem__(self, indice): """Permite acesso via índice: v[0] e v[1].""" if indice == 0: return self.x elif indice == 1: return self.y raise IndexError("Índice inválido") def __call__(self, fator): """Permite chamar o objeto como função: v(2).""" return Vetor(self.x * fator, self.y * fator) # Demonstração v1 = Vetor(3, 4) v2 = Vetor(1, 2) print(f"v1: {v1}") print(f"v2: {v2}") print(f"v1 + v2 = {v1 + v2}") print(f"v1 - v2 = {v1 - v2}") print(f"v1 * 3 = {v1 * 3}") print(f"3 * v1 = {3 * v1}") print(f"v1 == Vetor(3,4)? {v1 == Vetor(3,4)}") print(f"Tamanho (len): {len(v1)}") print(f"Módulo de v1: {abs(v1):.2f}") print(f"v1[0] = {v1[0]}, v1[1] = {v1[1]}") print(f"v1(2) = {v1(2)}") # Exemplo de classe com __enter__ e __exit__ (context manager) class ArquivoGerenciado: """Context manager para arquivos.""" def __init__(self, nome, modo): self.nome = nome self.modo = modo self.arquivo = None def __enter__(self): self.arquivo = open(self.nome, self.modo) return self.arquivo def __exit__(self, exc_type, exc_val, exc_tb): if self.arquivo: self.arquivo.close() return False # Não suprime exceções # Uso do context manager with ArquivoGerenciado("teste.txt", "w") as f: f.write("Olá, mundo!") print("\nArquivo escrito com sucesso usando context manager") |
Métodos especiais tornam suas classes mais integradas à linguagem. A fórmula da orientação a objetos é clara: \(POO = \text{classes} + \text{objetos} + \text{encapsulamento} + \text{herança}\) Classes e objetos são o alicerce de qualquer sistema Python. Comece com classes simples e adicione complexidade gradualmente. Seu código será mais organizado, reutilizável e intuitivo.