{ "cells": [ { "cell_type": "markdown", "id": "c8f2bcba", "metadata": {}, "source": [ "## Exercício 1.1" ] }, { "cell_type": "code", "execution_count": null, "id": "ba65148c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mario pilota o kart vermelho.\n", "Luigi pilota o kart verde.\n", "Peach pilota o kart rosa.\n", "Mario acelerou o kart vermelho!\n", "Peach derrapou na curva!\n" ] } ], "source": [ "class Kart:\n", " def __init__(self, piloto, cor):\n", " self.piloto = piloto\n", " self.cor = cor\n", "\n", " def acelerar(self):\n", " print(f\"{self.piloto} acelerou o kart {self.cor}!\")\n", "\n", " def derrapar(self):\n", " print(f\"{self.piloto} derrapou na curva!\")\n", "\n", "kart_mario = Kart(\"Mario\", \"vermelho\")\n", "kart_luigi = Kart(\"Luigi\", \"verde\")\n", "kart_peach = Kart(\"Peach\", \"rosa\")\n", "\n", "print(f\"{kart_mario.piloto} pilota o kart {kart_mario.cor}.\")\n", "print(f\"{kart_luigi.piloto} pilota o kart {kart_luigi.cor}.\")\n", "print(f\"{kart_peach.piloto} pilota o kart {kart_peach.cor}.\")\n", "\n", "kart_mario.acelerar()\n", "kart_peach.derrapar()" ] }, { "cell_type": "markdown", "id": "9ce80e41", "metadata": {}, "source": [ "## Exercício 1.2\n", "\n", "**Resposta: (A)** `inimigo = Inimigo(\"Zumbi\", 100)`\n", "\n", "- **(A)** Correta: chama o construtor com os dois argumentos esperados (`nome` e `vida`), na ordem definida no `__init__`.\n", "- **(B)** Incorreta: falta o argumento `vida`, o que gera `TypeError`.\n", "- **(C)** Incorreta: `nome.Inimigo(...)` tenta acessar um atributo `Inimigo` dentro do valor `nome`, que nem é um objeto válido.\n", "- **(D)** Incorreta: `new` não existe em Python (é de Java/C#), gera `SyntaxError`." ] }, { "cell_type": "code", "execution_count": null, "id": "bca341f3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Zumbi 100\n", "Zumbi ataca você!\n" ] } ], "source": [ "class Inimigo:\n", " def __init__(self, nome, vida):\n", " self.nome = nome\n", " self.vida = vida\n", "\n", " def atacar(self):\n", " print(f\"{self.nome} ataca você!\")\n", "\n", "inimigo = Inimigo(\"Zumbi\", 100)\n", "print(inimigo.nome, inimigo.vida)\n", "inimigo.atacar()" ] }, { "cell_type": "markdown", "id": "f8caa12a", "metadata": {}, "source": [ "## Exercício 1.3\n", "\n", "**Classe** é o *molde*: define a estrutura (atributos) e o comportamento (métodos) que os objetos terão, mas ela em si não ocupa um espaço concreto na memória. **Objeto** é a *peça produzida*: uma instância concreta criada a partir do molde, com valores próprios nos atributos. Assim como o mesmo molde produz vários karts, uma mesma classe produz vários objetos independentes.\n", "\n", "**Exemplo fora dos jogos:** na engenharia da computação, a classe `ContaBancaria` (com `titular`, `saldo` e métodos de depósito/saque) é o molde; cada conta de cada cliente é um objeto, com saldos diferentes, criados a partir do mesmo molde. O mesmo vale para a classe `Aluno` e cada aluno matriculado com nome e matrícula próprios." ] }, { "cell_type": "markdown", "id": "a3ca719e", "metadata": {}, "source": [ "## Exercício 2.1" ] }, { "cell_type": "code", "execution_count": null, "id": "e82b0ece", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Aric tem 100 de vida e 50 de mana.\n", "Luna tem 80 de vida e 100 de mana.\n" ] } ], "source": [ "class Personagem:\n", " def __init__(self, nome, vida, mana):\n", " self.nome = nome\n", " self.vida = vida\n", " self.mana = mana\n", "\n", "aric = Personagem(\"Aric\", 100, 50)\n", "luna = Personagem(\"Luna\", 80, 100)\n", "\n", "print(f\"{aric.nome} tem {aric.vida} de vida e {aric.mana} de mana.\")\n", "print(f\"{luna.nome} tem {luna.vida} de vida e {luna.mana} de mana.\")" ] }, { "cell_type": "markdown", "id": "082595a2", "metadata": {}, "source": [ "## Exercício 2.2\n", "\n", "**Resposta: (A)**\n", "\n", "- **(A)** Correta: o construtor recebe `genero` e `quantidade` e os armazena nos atributos; a chamada `Playlist(\"Rock\", 15)` funciona como esperado.\n", "- **(B)** Incorreta: o construtor não aceita argumentos, então `Playlist(\"Rock\", 15)` gera `TypeError`.\n", "- **(C)** Incorreta: falta o parâmetro `self` no `__init__`, gerando erro na criação do objeto.\n", "- **(D)** Incorreta: os parâmetros estão invertidos — `genero` receberia `15` e `quantidade` receberia `\"Rock\"`." ] }, { "cell_type": "code", "execution_count": null, "id": "eae5da6f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rock 15\n" ] } ], "source": [ "class Playlist:\n", " def __init__(self, genero, quantidade):\n", " self.genero = genero\n", " self.quantidade = quantidade\n", "\n", "playlist = Playlist(\"Rock\", 15)\n", "print(playlist.genero, playlist.quantidade)" ] }, { "cell_type": "markdown", "id": "0d2af027", "metadata": {}, "source": [ "## Exercício 2.3\n", "\n", "O `__init__` é o **método construtor** da classe. Ele é executado **automaticamente no momento exato da instanciação**, ou seja, quando o objeto é criado (na chamada `Classe(...)`). É nele que os atributos do objeto são inicializados com os valores recebidos.\n", "\n", "Se a classe **não** definir `__init__`, o Python usa o construtor padrão (herdado de `object`), que não inicializa nenhum atributo: o objeto nasce \"vazio\" e os atributos só passam a existir se forem criados manualmente depois, fora do construtor.\n", "\n", "Por isso a inicialização deve acontecer no construtor: ela fica centralizada em um único ponto, todo objeto já nasce com estado válido e completo, e nenhum código externo precisa \"lembrar\" de configurar os atributos — o que reduz erros e garante consistência." ] }, { "cell_type": "markdown", "id": "f2c3be1d", "metadata": {}, "source": [ "## Exercício 3.1" ] }, { "cell_type": "code", "execution_count": null, "id": "cf9ecd0b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rafa causou 25 de dano em Hydra!\n", "Hydra causou 40 de dano em Rafa!\n", "Rafa recuperou 15 de vida!\n", "75\n", "175\n" ] } ], "source": [ "class Jogador:\n", " def __init__(self, nome, vida, dano):\n", " self.nome = nome\n", " self.vida = vida\n", " self.dano = dano\n", "\n", " def receber_dano(self, quantidade):\n", " self.vida -= quantidade\n", "\n", " def atacar(self, inimigo):\n", " inimigo.receber_dano(self.dano)\n", " print(f\"{self.nome} causou {self.dano} de dano em {inimigo.nome}!\")\n", "\n", " def curar(self, quantidade):\n", " self.vida += quantidade\n", " print(f\"{self.nome} recuperou {quantidade} de vida!\")\n", "\n", "player = Jogador(\"Rafa\", 100, 25)\n", "chefe = Jogador(\"Hydra\", 200, 40)\n", "\n", "player.atacar(chefe)\n", "chefe.atacar(player)\n", "player.curar(15)\n", "print(player.vida)\n", "print(chefe.vida)" ] }, { "cell_type": "markdown", "id": "a6abfd06", "metadata": {}, "source": [ "## Exercício 3.2\n", "\n", "**Resposta: (A)** `6 12`\n", "\n", "Cada objeto tem seu próprio atributo `valor`. `contador_a` começa em `5` e é incrementado **1 vez** → `6`. `contador_b` começa em `10` e é incrementado **2 vezes** → `12`. O método `incrementar` sempre age sobre o atributo do objeto que o chamou (o `self`), então os objetos não interferem um no outro." ] }, { "cell_type": "code", "execution_count": null, "id": "cfca0bdf", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6 12\n" ] } ], "source": [ "class Contador:\n", " def __init__(self, valor):\n", " self.valor = valor\n", "\n", " def incrementar(self):\n", " self.valor += 1\n", "\n", "contador_a = Contador(5)\n", "contador_b = Contador(10)\n", "\n", "contador_a.incrementar()\n", "contador_b.incrementar()\n", "contador_b.incrementar()\n", "\n", "print(contador_a.valor, contador_b.valor)" ] }, { "cell_type": "markdown", "id": "56019915", "metadata": {}, "source": [ "## Exercício 3.3\n", "\n", "`self` é a **referência ao objeto atual**, ou seja, à instância sobre a qual o método foi chamado. Ele aparece como primeiro parâmetro de todo método de instância porque é por meio dele que o método acessa os atributos e chama outros métodos do próprio objeto — sem ele, o método não saberia de qual objeto está falando.\n", "\n", "Se você definir o método sem `self` (ex.: `def incrementar():`) e chamar `contador.incrementar()`, o Python passa o objeto automaticamente como argumento e o método recebe mais argumentos do que espera, gerando um erro como `TypeError: incrementar() takes 0 positional arguments but 1 was given`.\n", "\n", "`self` é essencial na interação entre objetos porque **cada objeto mantém seu próprio estado**. Quando um herói ataca outro, `self` é o atacante (carrega seu próprio `__vida` e `__ataque`) e `oponente` é o alvo (com os seus). Alterar a vida do oponente não afeta o atacante: `self` garante que cada método manipule os dados do objeto certo." ] }, { "cell_type": "markdown", "id": "c6fff12d", "metadata": {}, "source": [ "## Exercício 4.1" ] }, { "cell_type": "code", "execution_count": null, "id": "ea196e4e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Saldo de Ana: R$ 100.0\n", "Saldo de Ana: R$ 350.0\n", "Erro: saldo não pode ser negativo.\n", "Saldo de Ana: R$ 350.0\n" ] } ], "source": [ "class CarteiraDigital:\n", " def __init__(self, titular, saldo_inicial):\n", " self.__titular = titular\n", " self.__saldo = saldo_inicial\n", "\n", " def get_titular(self):\n", " return self.__titular\n", "\n", " def get_saldo(self):\n", " return self.__saldo\n", "\n", " def set_saldo(self, valor):\n", " if valor < 0:\n", " print(\"Erro: saldo não pode ser negativo.\")\n", " else:\n", " self.__saldo = valor\n", "\n", "carteira = CarteiraDigital(\"Ana\", 100.0)\n", "\n", "print(f\"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}\")\n", "carteira.set_saldo(carteira.get_saldo() + 250.0)\n", "print(f\"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}\")\n", "carteira.set_saldo(-50.0)\n", "print(f\"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}\")" ] }, { "cell_type": "markdown", "id": "bdc8e1ce", "metadata": {}, "source": [ "## Exercício 4.2\n", "\n", "**Resposta: (B)** `print(carrinho.__itens)`\n", "\n", "Atributos com dois underscores são \"name mangled\": o Python os renomeia internamente para `_Carrinho__itens`. Acessar `carrinho.__itens` fora da classe gera `AttributeError`. As alternativas (A) e (C) usam o getter e funcionam; a (D) imprime a referência ao método (sem erro), não o valor." ] }, { "cell_type": "code", "execution_count": null, "id": "8ccbddca", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "AttributeError: 'Carrinho' object has no attribute '__itens'\n" ] } ], "source": [ "class Carrinho:\n", " def __init__(self):\n", " self.__itens = 0\n", "\n", " def get_itens(self):\n", " return self.__itens\n", "\n", "carrinho = Carrinho()\n", "\n", "print(carrinho.get_itens())\n", "\n", "try:\n", " print(carrinho.__itens)\n", "except AttributeError as erro:\n", " print(f\"AttributeError: {erro}\")" ] }, { "cell_type": "markdown", "id": "0abb751d", "metadata": {}, "source": [ "## Exercício 4.3\n", "\n", "**Encapsulamento** é o princípio de esconder os detalhes internos do objeto (os atributos) e controlar todo acesso por meio de métodos públicos. O roteiro tem **3 passos**: (1) tornar os atributos **privados** (prefixo `__`); (2) criar **getters** para leitura controlada; (3) criar **setters** para escrita com validação.\n", "\n", "Não basta tornar os atributos privados porque o próprio código da classe precisa continuar acessando-os — os métodos internos ainda usam os atributos diretamente, e o mundo externo precisa de portas (getters/setters) para interagir. Os **setters** são essenciais quando o dado tem regras (como um saldo que não pode ser negativo) porque centralizam a validação em um único ponto: qualquer tentativa de alteração passa pela regra.\n", "\n", "**Problema com atributos públicos:** se `saldo` fosse público, qualquer código poderia fazer `conta.saldo = -500`, deixando o sistema financeiro inconsistente sem nenhum aviso. Bugs ficam difíceis de rastrear porque o atributo pode ser modificado de qualquer lugar do programa, sem passar por validação nenhuma." ] }, { "cell_type": "markdown", "id": "eebf5edd", "metadata": {}, "source": [ "## Exercício 5.1" ] }, { "cell_type": "code", "execution_count": null, "id": "b9fdc11d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Alfa ataca Beta (Beta: 55)\n", "Beta ataca Alfa (Alfa: 80)\n", "Alfa ataca Beta (Beta: 20)\n", "Beta ataca Alfa (Alfa: 60)\n", "Alfa ataca Beta (Beta: 0)\n", "Alfa venceu o duelo!\n" ] } ], "source": [ "class Heroi:\n", " def __init__(self, nome, vida, ataque):\n", " self.__nome = nome\n", " self.set_vida(vida)\n", " self.set_ataque(ataque)\n", "\n", " def get_nome(self):\n", " return self.__nome\n", "\n", " def get_vida(self):\n", " return self.__vida\n", "\n", " def get_ataque(self):\n", " return self.__ataque\n", "\n", " def set_nome(self, nome):\n", " self.__nome = nome\n", "\n", " def set_vida(self, vida):\n", " if vida < 0:\n", " self.__vida = 0\n", " elif vida > 100:\n", " self.__vida = 100\n", " else:\n", " self.__vida = vida\n", "\n", " def set_ataque(self, ataque):\n", " if ataque < 0:\n", " self.__ataque = 0\n", " elif ataque > 99:\n", " self.__ataque = 99\n", " else:\n", " self.__ataque = ataque\n", "\n", " def atacar(self, oponente):\n", " oponente.set_vida(oponente.get_vida() - self.get_ataque())\n", " print(f\"{self.get_nome()} ataca {oponente.get_nome()} ({oponente.get_nome()}: {oponente.get_vida()})\")\n", "\n", " def esta_vivo(self):\n", " return self.get_vida() > 0\n", "\n", "alfa = Heroi(\"Alfa\", 100, 35)\n", "beta = Heroi(\"Beta\", 90, 20)\n", "\n", "while alfa.esta_vivo() and beta.esta_vivo():\n", " alfa.atacar(beta)\n", " if beta.esta_vivo():\n", " beta.atacar(alfa)\n", "\n", "if alfa.esta_vivo():\n", " print(\"Alfa venceu o duelo!\")\n", "else:\n", " print(\"Beta venceu o duelo!\")" ] }, { "cell_type": "markdown", "id": "ae96f74e", "metadata": {}, "source": [ "## Exercício 5.2\n", "\n", "**Resposta: (A)** `x: 55, y: 10`\n", "\n", "`x` começa com vida `80` e ataque `30`; `y` com vida `70` e ataque `25`:\n", "\n", "1. `x.atacar(y)` → vida de `y`: `70 - 30 = 40`\n", "2. `y.atacar(x)` → vida de `x`: `80 - 25 = 55`\n", "3. `x.atacar(y)` → vida de `y`: `40 - 30 = 10`\n", "\n", "Ao final: `x: 55, y: 10`." ] }, { "cell_type": "code", "execution_count": null, "id": "b2b772b4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "X ataca Y (Y: 40)\n", "Y ataca X (X: 55)\n", "X ataca Y (Y: 10)\n", "55 10\n" ] } ], "source": [ "class Heroi:\n", " def __init__(self, nome, vida, ataque):\n", " self.__nome = nome\n", " self.set_vida(vida)\n", " self.set_ataque(ataque)\n", "\n", " def get_nome(self):\n", " return self.__nome\n", "\n", " def get_vida(self):\n", " return self.__vida\n", "\n", " def get_ataque(self):\n", " return self.__ataque\n", "\n", " def set_nome(self, nome):\n", " self.__nome = nome\n", "\n", " def set_vida(self, vida):\n", " if vida < 0:\n", " self.__vida = 0\n", " elif vida > 100:\n", " self.__vida = 100\n", " else:\n", " self.__vida = vida\n", "\n", " def set_ataque(self, ataque):\n", " if ataque < 0:\n", " self.__ataque = 0\n", " elif ataque > 99:\n", " self.__ataque = 99\n", " else:\n", " self.__ataque = ataque\n", "\n", " def atacar(self, oponente):\n", " oponente.set_vida(oponente.get_vida() - self.get_ataque())\n", " print(f\"{self.get_nome()} ataca {oponente.get_nome()} ({oponente.get_nome()}: {oponente.get_vida()})\")\n", "\n", " def esta_vivo(self):\n", " return self.get_vida() > 0\n", "\n", "x = Heroi(\"X\", 80, 30)\n", "y = Heroi(\"Y\", 70, 25)\n", "\n", "x.atacar(y)\n", "y.atacar(x)\n", "x.atacar(y)\n", "\n", "print(x.get_vida(), y.get_vida())" ] }, { "cell_type": "markdown", "id": "0bd766b0", "metadata": {}, "source": [ "## Exercício 5.3\n", "\n", "**1. Por que `__vida` deve ser privado?** Porque é o dado mais crítico do herói. Torná-lo privado impede que qualquer código externo altere a vida diretamente para valores absurdos (ex.: `alfa.__vida = 999`). Toda alteração passa pelos métodos controlados (`atacar` e o setter), que aplicam regras como o limite de `0` a `100`, garantindo que o estado do objeto permaneça sempre válido.\n", "\n", "**2. `atacar(oponente)` como método vs. função solta `atacar(alfa, beta)`?** Técnicamente daria para usar uma função solta, mas ela precisaria acessar os atributos dos heróis por fora — o que quebraria o encapsulamento (obrigando a expor os dados) ou exigiria getters/setters soltos e espalhados. Como método da classe, a lógica de ataque fica **junto dos dados** que manipula: o código fica mais organizado, e qualquer novo herói já nasce com `atacar` disponível, sem duplicar a lógica. É a diferença entre o comportamento pertencer ao objeto ou viver solto no programa.\n", "\n", "**3. O que `self` representa dentro de `atacar`?** `self` é o herói **atacante** (quando `alfa.atacar(beta)` é executado, `self` é `alfa`), e `oponente` é o alvo. Ao criar `alfa` e `beta`, o Python reserva memória para dois objetos distintos, cada um com **sua própria cópia** dos atributos: `alfa.__vida = 100` e `alfa.__ataque = 35`; `beta.__vida = 90` e `beta.__ataque = 20`. Por isso `alfa.atacar(beta)` lê o ataque de `alfa` e reduz a vida de `beta` — cada objeto mantém seu próprio estado, e a alteração em um não afeta o outro." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.5" } }, "nbformat": 4, "nbformat_minor": 5 }