Programação Orientada a Objetos (POO) em Python

Laboratório de Programação — Ciência da Computação • UFPI

Conceitos, sintaxe de classes, herança, polimorfismo, encapsulamento, propriedades, dataclasses — com syntax highlighting

O que é POO?

  • Paradigma que organiza o código em objetos, instâncias de classes.
  • Foca em abstração, encapsulamento, herança e polimorfismo.
  • Python oferece POO de forma flexível e intuitiva.

Por que usar POO?

  • Reutilização (herança, composição)
  • Organização (modularidade)
  • Manutenção (encapsulamento)
  • Modelagem mais próxima do mundo real

Vocabulário

TermoDefinição
ClasseMolde com atributos e métodos
ObjetoInstância de uma classe
AtributoDado do objeto (estado)
MétodoComportamento do objeto (função)

Definindo classes

class Pessoa:
    def __init__(self, nome, idade):  # construtor
        self.nome = nome      # atributo de instância (público)
        self.__idade = idade  # "privado" (name mangling)

    def apresentar(self):     # método de instância
        print(f"Olá, eu sou {self.nome}!")

p = Pessoa("Maria", 25)
p.apresentar()  # Olá, eu sou Maria!

Sobre self

  • Primeiro parâmetro dos métodos de instância.
  • Referencia o objeto atual.
  • Nome é convenção; poderia ser outro, mas self é padrão.

Encapsulamento

  • _protegido (convenção)
  • __privado (name mangling: _Classe__nome)

Atributos de classe × instância

class Contador:
    instancias = 0  # atributo de CLASSE (compartilhado)

    def __init__(self):
        Contador.instancias += 1
        self.valor = 0        # atributo de INSTÂNCIA

a = Contador(); b = Contador()
print(Contador.instancias)  # 2
print(a.valor, b.valor)     # 0 0

Métodos estáticos e de classe

class Util:
    @staticmethod
    def soma(a, b):
        return a + b

    @classmethod
    def from_str(cls, s):
        a, b = map(int, s.split(","))
        return cls.soma(a, b)

print(Util.soma(2,3))     # 5
print(Util.from_str("4,6"))  # 10

Propriedades (@property)

class Conta:
    def __init__(self, saldo=0.0):
        self._saldo = saldo

    @property
    def saldo(self):         # getter
        return self._saldo

    @saldo.setter
    def saldo(self, valor):  # setter com validação
        if valor < 0:
            raise ValueError("Saldo não pode ser negativo")
        self._saldo = valor

Métodos especiais (dunder)

class Vetor2D:
    def __init__(self, x, y): self.x, self.y = x, y
    def __repr__(self): return f"Vetor2D({self.x}, {self.y})"
    def __add__(self, other): return Vetor2D(self.x+other.x, self.y+other.y)
    def __eq__(self, other): return self.x==other.x and self.y==other.y

a = Vetor2D(1,2); b = Vetor2D(3,4)
print(a + b)  # Vetor2D(4, 6)

Ordenação e hashing

class Pessoa:
    def __init__(self, nome): self.nome = nome
    def __repr__(self): return f"Pessoa({self.nome!r})"
    def __lt__(self, o): return self.nome < o.nome  # ordenar por nome
    def __hash__(self): return hash(self.nome)      # permite usar em set/dict

dataclasses

from dataclasses import dataclass

@dataclass(order=True, frozen=False)
class Produto:
    id: int
    nome: str
    preco: float = 0.0

p = Produto(1, "Café", 12.5)
print(p)

Herança

class Pessoa:
    def __init__(self, nome): self.nome = nome
    def apresentar(self): print(f"Olá, sou {self.nome}.")

class Aluno(Pessoa):
    def __init__(self, nome, matricula):
        super().__init__(nome)
        self.matricula = matricula

    def apresentar(self):
        super().apresentar()
        print(f"Matrícula: {self.matricula}")

Polimorfismo

class Forma:
    def area(self): raise NotImplementedError

class Circulo(Forma):
    def __init__(self, r): self.r = r
    def area(self): return 3.14159 * self.r**2

class Quadrado(Forma):
    def __init__(self, l): self.l = l
    def area(self): return self.l**2

def total_area(formas):
    return sum(f.area() for f in formas)

print(total_area([Circulo(2), Quadrado(3)]))

Classes abstratas (ABC)

from abc import ABC, abstractmethod

class Repositorio(ABC):
    @abstractmethod
    def salvar(self, obj): ...
    @abstractmethod
    def buscar(self, id): ...

MRO & herança múltipla (cuidado)

  • Ordem de resolução de métodos (MRO) define qual método é chamado.
  • Use super() e evite diamantes complexos.
  • Prefira composição a herança múltipla quando possível.

Composição (preferida)

  • Objetos contêm outros objetos para delegar responsabilidades.
  • Reduz acoplamento e tende a ser mais simples de manter.

Exemplo

class Logger:
    def log(self, msg): print("[LOG]", msg)

class ServicoEmail:
    def __init__(self, logger): self.logger = logger
    def enviar(self, para, msg):
        self.logger.log(f"Enviando email para {para}")
        # ... envio ...
        self.logger.log("OK")

Caso de uso (mini-sistema)

Cadastro e persistência simples em arquivo (POO + I/O).

class Pessoa:
    def __init__(self, cpf, nome, telefone):
        self.cpf, self.nome, self.telefone = cpf, nome, telefone
    def __str__(self):
        return f"{self.cpf},{self.nome},{self.telefone}"

class GerenciadorPessoas:
    def __init__(self, arquivo="pessoas.txt"):
        self.pessoas = {}    # dict[cpf] = Pessoa
        self.arquivo = arquivo
        self.carregar()

    def cadastrar(self, cpf, nome, telefone):
        self.pessoas[cpf] = Pessoa(cpf, nome, telefone)

    def listar(self):
        return list(self.pessoas.values())

    def salvar(self):
        with open(self.arquivo, "w", encoding="utf-8") as f:
            for p in self.pessoas.values():
                f.write(str(p) + "\\n")

    def carregar(self):
        try:
            with open(self.arquivo, "r", encoding="utf-8") as f:
                for linha in f:
                    cpf, nome, tel = linha.strip().split(",")
                    self.pessoas[cpf] = Pessoa(cpf, nome, tel)
        except FileNotFoundError:
            pass

Boas práticas em POO (Python)

  • Use nomes claros e docstrings em classes/métodos.
  • Evite herança desnecessária; prefira composição.
  • Encapsule com propriedades para validação.
  • Pequenos métodos coesos; aplique SRP/SOLID.
  • Use dataclasses para objetos de dados.
  • Adote type hints para legibilidade e ferramentas.

Cheat Sheet

# Classe + construtor
class C: 
    def __init__(self, x): self.x = x

# Métodos: instância, classe, estático
class C:
    def m(self): ...
    @classmethod
    def cm(cls): ...
    @staticmethod
    def sm(): ...

# Propriedade
class C:
    @property
    def x(self): ...
    @x.setter
    def x(self, v): ...

# Herança + super
class B(A):
    def __init__(self):
        super().__init__()

# ABC
from abc import ABC, abstractmethod
class Repo(ABC):
    @abstractmethod
    def salvar(self, obj): ...

Conclusões

  • POO modela problemas como objetos com estado e comportamento.
  • Python facilita encapsulamento, herança e polimorfismo.
  • Propriedades, dunder methods e dataclasses elevam a expressividade.