Uma classe é um molde. Define quais atributos e métodos os objetos criados a partir dela vão ter. Cada objeto criado é uma instância independente.
__init__ é o construtor — o método chamado automaticamente quando você faz Personagem(...). É onde você define os atributos iniciais do objeto.
class Personagem:
def __init__(self, nome, classe):
self.nome = nome # atributo de instância
self.nivel = 1 # valor padrão
self.vida = 100 heroi = Personagem("Aria", "Maga")
vilao = Personagem("Krath", "Guerreiro")
heroi.vida = 50
print(vilao.vida) # 100 — não foi afetado Um método é uma função dentro de uma classe. Sempre recebe self como primeiro parâmetro — que é a referência ao próprio objeto.
def atacar(self, alvo): # sem retorno direto
alvo.vida -= 10 * self.nivel
def esta_vivo(self): # com retorno
return self.vida > 0 Um método pode receber outro objeto como argumento. Isso é a base de sistemas mais complexos.
Chamado quando você usa print(objeto) ou str(objeto). Deve retornar uma string legível para humanos.
Chamado no console interativo ou com repr(objeto). Deve retornar uma representação técnica — idealmente código que recrie o objeto.
def __repr__(self):
return f"Personagem(nome='{self.nome}', classe='{self.classe}')" Regra prática:
__str__é para o usuário final.__repr__é para o desenvolvedor.
Para proteger dados e garantir que as regras de negócio sejam respeitadas. Sem encapsulamento, qualquer código pode colocar um valor inválido num atributo.
_atributo — convenção: privado, não acesse de fora (mas Python não impede)__atributo — name mangling: Python renomeia para _Classe__atributo, mais difícil de acessar@property
def vida(self):
return self._vida
@vida.setter
def vida(self, valor):
self._vida = max(0, min(valor, self.VIDA_MAX)) A validação fica dentro da classe. Quem usa o objeto não precisa se preocupar com as regras — elas são automáticas.
Composição é quando um objeto cria e possui outro objeto internamente. O objeto filho não existe sem o pai.
class Personagem:
def __init__(self, nome, classe):
self.inventario = Inventario() # criado aqui dentro O objeto filho é instanciado dentro do __init__ do pai. Não vem de fora.
Agregação é quando um objeto referencia outro que foi criado de forma independente. Ambos existem por conta própria.
guilda = Guilda("Os Eternos") # criada fora
heroi = Personagem("Aria", "Maga") # criado fora
heroi.guilda = guilda # referência passada depois O objeto filho é criado fora e passado ao pai — seja no construtor ou depois.
Se eu deletar o objeto pai, o filho continua existindo?
| Composição | Agregação | |
|---|---|---|
| Filho criado | Dentro do pai | Fora do pai |
| Ciclo de vida | Compartilhado | Independente |
| Relação | "faz parte de" | "usa um" |
| Exemplo | Pedido → Itens | Aluno → Turma |
| Método de instância | Método de classe | |
|---|---|---|
| Decorator | nenhum | @classmethod |
| 1º parâmetro | self (o objeto) | cls (a classe) |
| Acessa | atributos de instância | atributos de classe |
| Chamado em | objeto ou classe | classe ou objeto |
Um padrão clássico: criar métodos de classe que retornam instâncias com configurações específicas, sem precisar passar todos os parâmetros.
# Em vez de:
heroi = Personagem("Brom", "Guerreiro", nivel=1, vida=150, mana=30)
# Usando factory:
heroi = Personagem.criar_guerreiro("Brom") Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
agenda
Construímos um sistema de RPG do zero, aplicando tudo que vimos nas aulas 01 a 05 — passo a passo, conceito por conceito.
Cada dupla recebe um tema e tem a aula pra implementar um sistema usando todos os conceitos. Vale ponto.
revisão · apresentação
Vamos criar um sistema de RPG simples, construído do zero — evoluindo a cada conceito revisado, das aulas 01 a 05.
Atributos de classe e instância, validação no __init__, parâmetros opcionais, encapsulamento com @property
Composição — nasce dentro do Personagem. Métodos de lista: append, remove, join
Agregação — existe de forma independente. @classmethod e factory methods no Personagem
revisão · aula 02 + aula 03 · atributos de classe e de instância
Atributos de classe são compartilhados por todos os objetos. Atributos de instância pertencem a cada objeto individualmente.
__init__, compartilhado por todos os objetosself. no __init__, único por objeto__init__ garante que o objeto nasce sempre em estado válidoclass Personagem:
# ── Atributos de CLASSE ──────────────────────
CLASSES_VALIDAS = ["Guerreiro", "Maga", "Arqueira", "Paladino"]
_total_personagens = 0
def __init__(self, nome, classe, nivel=1, vida=100, mana=50):
# Validação no __init__
if not nome or not nome.strip():
raise ValueError("Nome não pode ser vazio.")
if classe not in Personagem.CLASSES_VALIDAS:
raise ValueError(f"Classe inválida. Escolha: {Personagem.CLASSES_VALIDAS}")
if nivel < 1:
raise ValueError("Nível mínimo é 1.")
# ── Atributos de INSTÂNCIA ───────────────
self._nome = nome.strip()
self._classe = classe
self._nivel = nivel
self._vida = vida
self._mana = mana
self.VIDA_MAX = vida
self.MANA_MAX = mana
self.inventario = Inventario() # composição
self.guilda = None # agregação (opcional)
Personagem._total_personagens += 1
# Atributo de classe: mesmo valor em todos
heroi = Personagem("Aria", "Maga")
vilao = Personagem("Krath", "Guerreiro", nivel=5, vida=200)
print(Personagem._total_personagens) # 2 — compartilhado
print(heroi._total_personagens) # 2 — mesmo valor
# Atributo de instância: cada um tem o seu
print(heroi.VIDA_MAX) # 100
print(vilao.VIDA_MAX) # 200 — diferente! revisão · aula 03 · métodos de classe
Um método de classe recebe cls em vez de self. Age sobre a classe — não sobre uma instância específica.
@classmethod recebe cls — referência à classe, não ao objetoself. Método de classe age sobre clsclass Personagem:
CLASSES_VALIDAS = ["Guerreiro", "Maga", "Arqueira", "Paladino"]
_total_personagens = 0
# ... __init__ ...
# ── Métodos de CLASSE ────────────────────────
@classmethod
def total_criados(cls):
return cls._total_personagens
@classmethod
def criar_guerreiro(cls, nome):
return cls(nome, "Guerreiro", nivel=1, vida=150, mana=30)
@classmethod
def criar_maga(cls, nome):
return cls(nome, "Maga", nivel=1, vida=80, mana=120)
# Método de classe — chamado na classe, não no objeto
heroi = Personagem.criar_maga("Aria") # factory method
tank = Personagem.criar_guerreiro("Brom")
print(Personagem.total_criados()) # 2
# Método de instância — chamado no objeto
heroi.atacar(tank) # self = heroi revisão · aula 02 · métodos de instância
Métodos de instância definem o comportamento do objeto. Sempre recebem self como primeiro parâmetro.
atacar(self, alvo) recebe outro objeto — objetos interagem entre siesta_vivo() retorna booleano — método com retornocurar e subir_nivel usam o setter de vida — encapsulamento já funciona aqui # ── Métodos de INSTÂNCIA ─────────────────────
def atacar(self, alvo):
dano = 10 * self._nivel
alvo.vida -= dano
print(f"{self._nome} causou {dano} de dano em {alvo._nome}!")
def curar(self, quantidade):
self.vida += quantidade
print(f"{self._nome} recuperou {quantidade} de vida!")
def esta_vivo(self):
return self._vida > 0
def subir_nivel(self):
self._nivel += 1
self.VIDA_MAX += 20
self.vida = self.VIDA_MAX
print(f"{self._nome} subiu para o nível {self._nivel}!")
heroi = Personagem.criar_maga("Aria")
vilao = Personagem("Krath", "Guerreiro", nivel=5, vida=200)
heroi.subir_nivel() # nivel=2, vida restaurada ao novo máximo
heroi.atacar(vilao) # causa dano proporcional ao nível
heroi.curar(30) # recupera vida via setter
print(heroi.esta_vivo()) # True revisão · aula 03 · __str__
__str__ define o que aparece quando você faz print(objeto). Deve ser clara e útil para o usuário final.
__str__ é para o usuário final — legível e formatado{self.inventario} chama o __str__ do Inventário — objetos compostos se exibem em cadeia__str__, print(heroi) mostraria <__main__.Personagem object at 0x...> def __str__(self):
status = "⚔️ vivo" if self.esta_vivo() else "💀 derrotado"
barra_vida = "█" * (self._vida // 10) + "░" * ((self.VIDA_MAX - self._vida) // 10)
return (
f"╔═════════════════════════════════════════╗\n"
f"║\n"
f"║ {self._nome:<10} ({self._classe:<10}) \n"
f"║ Nível: {self._nivel:<3} | {status:<12} \n"
f"║ Vida [{barra_vida}] {self._vida}/{self.VIDA_MAX}\n"
f"║ Mana: {self._mana}/{self.MANA_MAX}\n"
f"║\n"
f"╚═════════════════════════════════════════╝"
)
heroi = Personagem.criar_maga("Aria")
heroi.inventario.adicionar("Cajado Arcano")
print(heroi)
# ╔══════════════════════════╗
# ║ Aria (Maga )║
# ║ Nível: 1 | ⚔️ vivo ║
# ║ Vida [████████░░] 80/80
# ║ Mana: 120/120
# ╚══════════════════════════╝ __str__ do Personagem chama o __str__ do Inventário automaticamente via f"{self.inventario}" — composição e __str__ trabalhando juntos.revisão · aula 03 · __repr__
__repr__ é para o desenvolvedor. Aparece no console interativo e deve mostrar como recriar o objeto.
__str__ → para o usuário final: legível e formatado__repr__ → para o desenvolvedor: técnico e reproduzível__repr__ — Python usa ele como fallback para __str__ def __repr__(self):
return (f"Personagem(nome='{self._nome}', classe='{self._classe}', "
f"nivel={self._nivel}, vida={self._vida}, mana={self._mana})")
heroi = Personagem.criar_maga("Aria")
print(str(heroi)) # chama __str__ → ficha formatada
print(repr(heroi)) # chama __repr__ → Personagem(nome='Aria', classe='Maga', nivel=1, vida=80, mana=120)
# No console interativo, digitar o objeto chama __repr__ automaticamente:
# >>> heroi
# Personagem(nome='Aria', classe='Maga', nivel=1, vida=80, mana=120) revisão · aula 04 · @property e @setter
Os atributos _vida, _mana e _nome são privados. @property e @setter controlam como são lidos e escritos.
@property — getter: controla como o valor é lido@vida.setter — valida e limita antes de salvarnome só tem getter — atributo read-only, não pode ser alterado de fora # ── @property e @setter ──────────────────────
@property
def nome(self): return self._nome # read-only
@property
def vida(self): return self._vida
@vida.setter
def vida(self, valor):
self._vida = max(0, min(int(valor), self.VIDA_MAX))
@property
def mana(self): return self._mana
@mana.setter
def mana(self, valor):
self._mana = max(0, min(int(valor), self.MANA_MAX))
heroi = Personagem.criar_maga("Aria")
heroi.vida = -999 # setter: max(0, ...) → _vida = 0
heroi.vida = 9999 # setter: min(..., VIDA_MAX) → _vida = 80
print(heroi.vida) # getter retorna _vida protegido
# heroi.nome = "X" → AttributeError: sem setter, é read-only revisão · aula 05 · composição + métodos de lista
Composição: o Inventário nasce dentro do Personagem e não existe sem ele. Revisamos métodos de lista: append, remove, in, len, join.
Inventario() criado dentro do __init__ do Personagem — nasce e morre com eleappend, remove, in, len, join — métodos de lista em uso realprint(heroi.inventario) chama o __str__ do Inventário — cada classe cuida da própria exibiçãoclass Inventario:
"""Composição: pertence ao Personagem, não existe sem ele."""
def __init__(self, capacidade=10): # parâmetro opcional
self._itens = [] # atributo de instância privado
self._capacidade = capacidade
def adicionar(self, item):
if len(self._itens) >= self._capacidade:
print("Inventário cheio!")
return
self._itens.append(item)
def remover(self, item):
if item in self._itens:
self._itens.remove(item)
else:
print(f"'{item}' não encontrado.")
def __str__(self):
if not self._itens:
return "Inventário vazio."
return f"Itens ({len(self._itens)}/{self._capacidade}): " + " | ".join(self._itens)
# Inventario criado dentro do __init__ do Personagem:
# self.inventario = Inventario() ← composição
heroi = Personagem.criar_maga("Aria")
heroi.inventario.adicionar("Cajado Arcano")
heroi.inventario.adicionar("Poção de Vida")
heroi.inventario.remover("Poção de Vida")
print(heroi.inventario)
# Itens (1/10): Cajado Arcano revisão · aula 05 · agregação
Agregação: a Guilda existe independente do Personagem. Ambos criados separadamente e depois relacionados.
Guilda criada fora do Personagem — ciclo de vida independenteclass Guilda:
"""Agregação: existe independente do Personagem."""
def __init__(self, nome, nivel_minimo=1): # parâmetro opcional
self._nome = nome
self._nivel_minimo = nivel_minimo
self._membros = []
def recrutar(self, personagem):
if personagem._nivel < self._nivel_minimo:
print(f"Nível insuficiente para entrar em {self._nome}.")
return
self._membros.append(personagem)
print(f"{personagem._nome} entrou na guilda {self._nome}!")
def listar_membros(self):
nomes = [p._nome for p in self._membros]
return ", ".join(nomes) if nomes else "Sem membros."
def __str__(self):
return f"Guilda '{self._nome}' — {len(self._membros)} membro(s): {self.listar_membros()}"
# Criados de forma independente — agregação
guilda = Guilda("Os Eternos", nivel_minimo=2)
heroi = Personagem.criar_maga("Aria")
heroi.subir_nivel() # nivel=2, agora pode entrar
guilda.recrutar(heroi) # heroi entra na guilda
heroi.guilda = guilda # referência ao objeto externo
print(guilda)
# Guilda 'Os Eternos' — 1 membro(s): Aria
del heroi
print(guilda) # guilda continua existindo
# Guilda 'Os Eternos' — 1 membro(s): Aria revisão · aula 05
A diferença está em quem controla o ciclo de vida do objeto filho.
revisão · sistema completo
O sistema completo — todas as classes, todos os conceitos das aulas 01 a 05 em um único arquivo.
# ════════════════════════════════════════════════
# Sistema de RPG — código completo
# Conceitos: aulas 01 a 05
# ════════════════════════════════════════════════
class Inventario:
"""Composição: pertence ao Personagem, não existe sem ele."""
def __init__(self, capacidade=10): # parâmetro opcional
self._itens = [] # atributo de instância privado
self._capacidade = capacidade
def adicionar(self, item):
if len(self._itens) >= self._capacidade:
print("Inventário cheio!")
return
self._itens.append(item)
def remover(self, item):
if item in self._itens:
self._itens.remove(item)
else:
print(f"'{item}' não encontrado.")
def __str__(self):
if not self._itens:
return "Inventário vazio."
return f"Itens ({len(self._itens)}/{self._capacidade}): " + " | ".join(self._itens)
class Guilda:
"""Agregação: existe independente do Personagem."""
def __init__(self, nome, nivel_minimo=1): # parâmetro opcional
self._nome = nome
self._nivel_minimo = nivel_minimo
self._membros = []
def recrutar(self, personagem):
if personagem._nivel < self._nivel_minimo:
print(f"Nível insuficiente para entrar em {self._nome}.")
return
self._membros.append(personagem)
print(f"{personagem._nome} entrou na guilda {self._nome}!")
def listar_membros(self):
nomes = [p._nome for p in self._membros]
return ", ".join(nomes) if nomes else "Sem membros."
def __str__(self):
return f"Guilda '{self._nome}' — {len(self._membros)} membro(s): {self.listar_membros()}"
class Personagem:
# ── Atributos de CLASSE ──────────────────────
CLASSES_VALIDAS = ["Guerreiro", "Maga", "Arqueira", "Paladino"]
_total_personagens = 0
def __init__(self, nome, classe, nivel=1, vida=100, mana=50):
# Validação no __init__
if not nome or not nome.strip():
raise ValueError("Nome não pode ser vazio.")
if classe not in Personagem.CLASSES_VALIDAS:
raise ValueError(f"Classe inválida. Escolha: {Personagem.CLASSES_VALIDAS}")
if nivel < 1:
raise ValueError("Nível mínimo é 1.")
# ── Atributos de INSTÂNCIA ───────────────
self._nome = nome.strip()
self._classe = classe
self._nivel = nivel
self._vida = vida
self._mana = mana
self.VIDA_MAX = vida
self.MANA_MAX = mana
self.inventario = Inventario() # composição
self.guilda = None # agregação (opcional)
Personagem._total_personagens += 1
# ── @property e @setter ──────────────────────
@property
def nome(self): return self._nome # read-only
@property
def vida(self): return self._vida
@vida.setter
def vida(self, valor):
self._vida = max(0, min(int(valor), self.VIDA_MAX))
@property
def mana(self): return self._mana
@mana.setter
def mana(self, valor):
self._mana = max(0, min(int(valor), self.MANA_MAX))
# ── Métodos de INSTÂNCIA ─────────────────────
def atacar(self, alvo):
dano = 10 * self._nivel
alvo.vida -= dano
print(f"{self._nome} causou {dano} de dano em {alvo._nome}!")
def curar(self, quantidade):
self.vida += quantidade
print(f"{self._nome} recuperou {quantidade} de vida!")
def esta_vivo(self):
return self._vida > 0
def subir_nivel(self):
self._nivel += 1
self.VIDA_MAX += 20
self.vida = self.VIDA_MAX
print(f"{self._nome} subiu para o nível {self._nivel}!")
# ── Métodos de CLASSE ────────────────────────
@classmethod
def total_criados(cls):
return cls._total_personagens
@classmethod
def criar_guerreiro(cls, nome):
return cls(nome, "Guerreiro", nivel=1, vida=150, mana=30)
@classmethod
def criar_maga(cls, nome):
return cls(nome, "Maga", nivel=1, vida=80, mana=120)
# ── __str__ e __repr__ ───────────────────────
def __str__(self):
status = "⚔️ vivo" if self.esta_vivo() else "💀 derrotado"
barra_vida = "█" * (self._vida // 10) + "░" * ((self.VIDA_MAX - self._vida) // 10)
return (
f"╔═════════════════════════════════════════╗\n"
f"║\n"
f"║ {self._nome:<10} ({self._classe:<10}) \n"
f"║ Nível: {self._nivel:<3} | {status:<12} \n"
f"║ Vida [{barra_vida}] {self._vida}/{self.VIDA_MAX}\n"
f"║ Mana: {self._mana}/{self.MANA_MAX}\n"
f"║\n"
f"╚═════════════════════════════════════════╝"
)
def __repr__(self):
return (f"Personagem(nome='{self._nome}', classe='{self._classe}', "
f"nivel={self._nivel}, vida={self._vida}, mana={self._mana})")
# ════════════════════════════════════════════════
# Usando o sistema
# ════════════════════════════════════════════════
heroi = Personagem.criar_maga("Aria") # factory method
vilao = Personagem("Krath", "Guerreiro", nivel=5, vida=200)
guilda = Guilda("Os Eternos", nivel_minimo=2)
heroi.subir_nivel() # nivel=2, vida restaurada
guilda.recrutar(heroi) # agregação
heroi.guilda = guilda
heroi.inventario.adicionar("Cajado Arcano") # composição
heroi.inventario.adicionar("Poção de Vida")
while vilao.esta_vivo():
heroi.atacar(vilao)
print(heroi) # __str__
print(repr(vilao)) # __repr__
print(guilda) # __str__ da Guilda
print(f"Total de personagens criados: {Personagem.total_criados()}") __init__ ✅ Parâmetros opcionais ✅ Métodos de instância ✅ @classmethod ✅ __str__ ✅ __repr__ ✅ Encapsulamento ✅ Composição ✅ Agregação ✅revisão · fechamento
CLASSES_VALIDAS, _total_personagens) self. no __init__ nivel=1, vida=100) atacar, curar, subir_nivel total_criados(), factory methods _, @property e @setter com validação mini projeto · apresentação
Cada dupla vai receber um tema e tem a aula pra implementar. O projeto vale ponto e precisa usar tudo que revisamos hoje.
O professor define as duplas. Ninguém escolhe o parceiro — é treino pra vida real.
A ordem é sorteada. O primeiro escolhe o tema, depois o segundo, e assim por diante. Cada tema pode ser escolhido por no máximo duas duplas.
O código vai para o GitHub. Repositório criado em aula, commit feito antes de sair.
mini projeto · critérios de avaliação
O sistema roda sem erros. Inputs e outputs fazem sentido.
__init__, atributos de instância, self e métodos bem definidos.
Atributos privados com _, @property e @setter com validação real.
Pelo menos um relacionamento entre objetos aplicado corretamente.
Cada classe tem um __str__ útil — não vale retornar só o nome.
Ideias próprias além do mínimo: métodos extras, validações criativas, uso de listas.
mini projeto · tema 01
Método cancelar() que só funciona se o status for recebido. Depois disso, não dá mais pra cancelar.
mini projeto · tema 02
Método aplicar_desconto(percentual) no carrinho — valida que o desconto não pode ser maior que 50%.
mini projeto · tema 03
Método adicionar_borda(sabor) na pizza — adiciona R$5,00 ao preço e registra o sabor da borda.
mini projeto · tema 04
Método fazer_checkout() na reserva — libera o quarto e calcula o total com base nos dias de estadia.
mini projeto · tema 05
Método classificar_imc() no aluno — retorna Abaixo do peso, Normal, Sobrepeso ou Obesidade com base no IMC.
mini projeto · tema 06
Método ultimo_diagnostico() no pet — retorna o diagnóstico da consulta mais recente ou uma mensagem se não houver histórico.
mini projeto · execução
O professor define as duplas agora. Se a turma for ímpar, um trio é formado.
A ordem de escolha é sorteada. Cada dupla na sua vez escolhe um dos 6 temas. Cada tema pode ser escolhido por no máximo duas duplas.
Criem o repositório no GitHub agora — antes de escrever código. Nome sugerido: mini-projeto-poo.
Implementem o sistema. Usem o slide do tema como guia. O desafio extra é opcional mas conta pontos.
Commit e push no GitHub antes de sair. O link do repositório deve ser enviado no canal da turma.
Atenção: ⚠️ Atenção: O repositório precisa ser criado hoje. Projetos sem commit até o final da próxima aula não serão aceitos.
mini projeto · dicas
5 minutos de planejamento economizam 30 minutos de retrabalho.
Quais objetos existem no seu sistema? Escreva no papel antes de abrir o VS Code.
O que cada objeto sabe? Quais precisam de proteção com @property?
Quais objetos vivem dentro de outros (composição)? Quais são independentes (agregação)?
Com as bases sólidas, chegou a hora de reutilizar código de verdade. Vamos aprender como uma classe pode herdar atributos e comportamentos de outra.