{ "cells": [ { "cell_type": "markdown", "id": "336e447e", "metadata": {}, "source": [ "## Introdução a Classes e Objetos em Python\n", "\n", "Neste notebook, vamos explorar o conceito de **Classes** e **Objetos** em Python, que são fundamentais para a programação orientada a objetos (POO)" ] }, { "cell_type": "markdown", "id": "4f03f122", "metadata": {}, "source": [ "### Criando uma nova classe\n", "\n", "Vamos criar uma nova classe que representa um `Cachorro`, essa classe tem como objetivo \"arquitetar\" a estrutura de dados que vamos utilizar em nosso programa.\n", "\n", "Para nosso exemplo, o programa terá que lidar com nomes e cores de diferentes tipos de cães, logo, a classe precisará apresentar duas variáveis para armazenar esses valores, aqui chamadas de **Atributos**.\n", "\n", "Além disso, vamos implementar também uma função dentro da classe, que aqui será chamada de **Método**, que será responsável por imprimir um latino usando a função `print()`." ] }, { "cell_type": "code", "execution_count": 36, "id": "7b386105", "metadata": {}, "outputs": [], "source": [ "class Cachorro:\n", " def __init__(self, nome, cor):\n", " print(\"Construtor da classe Cachorro chamado!\")\n", " self.nome = nome\n", " self.cor = cor\n", " \n", " def latir(self):\n", " print(\"HOUH!\")" ] }, { "cell_type": "markdown", "id": "284d5c07", "metadata": {}, "source": [ "### Gerando um novo cachorro na memória\n", "\n", "Após definir a classe, nada acontece no nosso programa pois a classe é apenas uma referência para nosso programa. Para dar vida a essa classe e criar um novo cachorro na memória, vamos **instanciar** usando o código abaixo:" ] }, { "cell_type": "code", "execution_count": 37, "id": "e9d27bfc", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Construtor da classe Cachorro chamado!\n" ] } ], "source": [ "doguinho = Cachorro(\"Rex\", \"Marrom\")" ] }, { "cell_type": "markdown", "id": "3a4e3494", "metadata": {}, "source": [ "#### Um novo objeto foi criado\n", "\n", "Quando o programa processa a instrução ``Cachorro(\"Rex\", \"Marrom\")``, o sistema armazena em um espaço de memória livre uma estrutura contendo os dois atributos." ] }, { "cell_type": "markdown", "id": "c15d68fa", "metadata": {}, "source": [ "### Acessando os valores do objeto criado\n", "\n", "Para acessar o valor dos atributos do objeto que está na memória, primeiro precisamos acessar a variável ao qual esse objeto foi atribuído, que foi a variável de nome `doguinho`. Em seguida, usamos o operador ponto `.` para acessar os atributos do objeto, como no exemplo abaixo:" ] }, { "cell_type": "code", "execution_count": 38, "id": "293e06e1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rex\n", "Marrom\n" ] } ], "source": [ "print(doguinho.nome)\n", "print(doguinho.cor)" ] }, { "cell_type": "markdown", "id": "1821745d", "metadata": {}, "source": [ "### Chamando métodos do objeto criado\n", "\n", "Lembra do método que criamos dentro da classe `Cachorro` chamado `latir()`? Podemos chamá-lo usando a mesma lógica de acessar os atributos, ou seja, usando o operador ponto `.`.\n", "\n", "Esse método sempre pode ser chamado a partir de objetos que foram criados com a classe `Cachorro`, como no exemplo abaixo:" ] }, { "cell_type": "code", "execution_count": 39, "id": "581dd7de", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HOUH!\n" ] } ], "source": [ "doguinho.latir()" ] }, { "cell_type": "markdown", "id": "ccc0d628", "metadata": {}, "source": [ "### Criando um novo cachorro na memória\n", "\n", "Agora que temos uma classe, podemos criar um novo cachorro na memória, para isso, vamos instanciar a classe `Cachorro` novamente e atribuir o objeto criado a uma variável chamada `segundo_doguinho`, como no exemplo abaixo.\n", "\n", "Repare que o novo cachorro criado na memória tem atributos diferentes do cachorro criado anteriormente, ou seja, cada objeto criado a partir da classe `Cachorro` é independente e possui seus próprios valores de atributos. Porém, ambos os objetos compartilham o mesmo método `latir()`, que foi definido na classe `Cachorro`." ] }, { "cell_type": "code", "execution_count": 40, "id": "48299253", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Construtor da classe Cachorro chamado!\n", "Bob\n", "Caramelo\n", "HOUH!\n" ] } ], "source": [ "doguinho_segundo = Cachorro(\"Bob\", \"Caramelo\")\n", "print(doguinho_segundo.nome)\n", "print(doguinho_segundo.cor)\n", "doguinho_segundo.latir()" ] }, { "cell_type": "markdown", "id": "a46f6506", "metadata": {}, "source": [ "## Atributo `self`\n", "\n", "O atributo `self` é uma referência ao próprio objeto que está sendo criado a partir da classe. Ele é usado para acessar os atributos e métodos do objeto dentro da classe.\n", "\n", "Ao chamar o método `latir()` em um objeto, o Python automaticamente passa o próprio objeto como o primeiro argumento do método, que é representado pelo `self`. Vamos usar esse atributo para acessar os atributos `nome` e `cor` do objeto dentro do método `latir()`." ] }, { "cell_type": "code", "execution_count": 41, "id": "27047cce", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rex está latindo!\n", "HOUH!\n" ] } ], "source": [ "class Cachorro:\n", " def __init__(self, nome, cor):\n", " self.nome = nome\n", " cor = cor\n", " \n", " def latir(self):\n", " print(f\"{self.nome} está latindo!\")\n", " print(\"HOUH!\")\n", "\n", "doguinho = Cachorro(\"Rex\", \"Marrom\")\n", "doguinho.latir()" ] }, { "cell_type": "markdown", "id": "9f13b7ad", "metadata": {}, "source": [ "#### Erro comum: Esquecer de chamar o `self`\n", "\n", "Se esquecermos do `self` na hora de chamar um atributo, o Python não vai conseguir encontrar o atributo e vai gerar um erro do tipo `AttributeError`. No exemplo abaixo, como não existe nenhuma variável chamada `nome` dentro do método `latir()`, o Python vai gerar um erro." ] }, { "cell_type": "code", "execution_count": 42, "id": "6006f38d", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'nome' is not defined", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[42]\u001b[39m\u001b[32m, line 11\u001b[39m\n\u001b[32m 7\u001b[39m print(f\"{nome} está latindo!\")\n\u001b[32m 8\u001b[39m print(\u001b[33m\"HOUH!\"\u001b[39m)\n\u001b[32m 9\u001b[39m \n\u001b[32m 10\u001b[39m doguinho_bugado = CachorroBugado(\u001b[33m\"Rex\"\u001b[39m, \u001b[33m\"Marrom\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m11\u001b[39m doguinho_bugado.latir() \u001b[38;5;66;03m# Isso vai gerar um erro do tipo NameError\u001b[39;00m\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[42]\u001b[39m\u001b[32m, line 7\u001b[39m, in \u001b[36mCachorroBugado.latir\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m latir(self):\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m print(f\"{nome} está latindo!\")\n\u001b[32m 8\u001b[39m print(\u001b[33m\"HOUH!\"\u001b[39m)\n", "\u001b[31mNameError\u001b[39m: name 'nome' is not defined" ] } ], "source": [ "class CachorroBugado:\n", " def __init__(self, nome, cor):\n", " self.nome = nome\n", " cor = cor\n", " \n", " def latir(self):\n", " print(f\"{nome} está latindo!\")\n", " print(\"HOUH!\")\n", "\n", "doguinho_bugado = CachorroBugado(\"Rex\", \"Marrom\")\n", "doguinho_bugado.latir() # Isso vai gerar um erro do tipo NameError" ] }, { "cell_type": "markdown", "id": "001f50c3", "metadata": {}, "source": [ "## Encapsulamento\n", "\n", "O **Encapsulamento** é um dos pilares da programação orientada a objetos (POO) e se refere à prática de restringir o acesso direto aos atributos e métodos de uma classe, permitindo que eles sejam acessados apenas através de métodos públicos. Isso ajuda a proteger os dados internos da classe e a manter a integridade do objeto.\n", "\n", "No python, vamos impedir o acesso direto aos atributos da classe, tornando-os privados, e vamos criar métodos públicos para acessar e modificar esses atributos. Isso é feito utilizando o prefixo `__` (dois underscores) antes do nome do atributo.\n", "\n", "Repare que o atributo privado ainda pode ser exibido, mas só através de métodos que foram criados dentro da classe Cachorro. " ] }, { "cell_type": "code", "execution_count": null, "id": "73c494ee", "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Cachorro' object has no attribute 'nome'", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mAttributeError\u001b[39m Traceback (most recent call last)", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[43]\u001b[39m\u001b[32m, line 11\u001b[39m\n\u001b[32m 7\u001b[39m print(f\"{self.__nome} está latindo!\")\n\u001b[32m 8\u001b[39m print(\u001b[33m\"HOUH!\"\u001b[39m)\n\u001b[32m 9\u001b[39m \n\u001b[32m 10\u001b[39m doguinho = Cachorro(\u001b[33m\"Rex\"\u001b[39m, \u001b[33m\"Marrom\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m11\u001b[39m doguinho.nome \u001b[38;5;66;03m# Isso vai gerar um erro do tipo AttributeError\u001b[39;00m\n\u001b[32m 12\u001b[39m doguinho.latir()\n", "\u001b[31mAttributeError\u001b[39m: 'Cachorro' object has no attribute 'nome'" ] } ], "source": [ "class Cachorro:\n", " def __init__(self, nome, cor):\n", " self.__nome = nome\n", " self.__cor = cor\n", " \n", " def latir(self):\n", " print(f\"{self.__nome} está latindo!\")\n", " print(\"HOUH!\")\n", "\n", "doguinho = Cachorro(\"Rex\", \"Marrom\")\n", "doguinho.latir()" ] }, { "cell_type": "markdown", "id": "17daa6d3", "metadata": {}, "source": [ "### Getters: Acessando atributos privados\n", "\n", "Sem acesso aos atributos, não conseguimos acessar os valores dos atributos `nome` e `cor` diretamente, como fizemos anteriormente. Isso pode ser necessário em alguns casos, então vamos criar métodos públicos para acessar esses atributos privados. Esses métodos são chamados de **Getters**.\n", "\n", "Esses métodos, por padrão, recebem o prefixo `get_` seguido do nome do atributo que queremos acessar. No exemplo abaixo, criamos dois métodos públicos chamados `get_nome()` e `get_cor()`, que retornam os valores dos atributos privados `__nome` e `__cor`, respectivamente." ] }, { "cell_type": "code", "execution_count": 44, "id": "5e8325c6", "metadata": {}, "outputs": [], "source": [ "class Cachorro:\n", " def __init__(self, nome, cor):\n", " self.__nome = nome\n", " self.__cor = cor\n", " \n", " def latir(self):\n", " print(f\"{self.__nome} está latindo!\")\n", " print(\"HOUH!\")\n", "\n", " def get_nome(self):\n", " return self.__nome\n", "\n", " def get_cor(self):\n", " return self.__cor" ] }, { "cell_type": "markdown", "id": "8e878d73", "metadata": {}, "source": [ "#### Chamando os métodos Getters\n", "\n", "Para acessar os valores dos atributos privados, podemos chamar os métodos Getters que criamos anteriormente. Eles funcionam da mesma forma que o método `latir()`, ou seja, precisamos acessar o objeto e usar o operador ponto `.` para chamar o método." ] }, { "cell_type": "code", "execution_count": null, "id": "38a4175e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rex\n", "Marrom\n" ] } ], "source": [ "doguinho = Cachorro(\"Rex\", \"Marrom\")\n", "print(doguinho.get_nome())\n", "print(doguinho.get_cor())" ] }, { "cell_type": "markdown", "id": "fae3da9a", "metadata": {}, "source": [ "### Setters: Modificando atributos privados\n", "\n", "Se precisarmos modificar os valores dos atributos privados, podemos criar métodos públicos chamados **Setters**. Esses métodos, por padrão, recebem o prefixo `set_` seguido do nome do atributo que queremos modificar. No exemplo abaixo, criamos dois métodos públicos chamados `set_nome()` e `set_cor()`, que permitem modificar os valores dos atributos privados `__nome` e `__cor`, respectivamente." ] }, { "cell_type": "code", "execution_count": null, "id": "c4d0b5ff", "metadata": {}, "outputs": [], "source": [ "class Cachorro:\n", " def __init__(self, nome, cor):\n", " print(\"Construtor da classe Cachorro chamado!\")\n", " self.__nome = nome\n", " self.__cor = cor\n", " \n", " def latir(self):\n", " print(\"HOUH!\")\n", "\n", " def get_nome(self):\n", " return self.__nome\n", "\n", " def set_nome(self, nome):\n", " self.__nome = nome\n", "\n", " def get_cor(self):\n", " return self.__cor\n", "\n", " def set_cor(self, cor):\n", " self.__cor = cor" ] }, { "cell_type": "markdown", "id": "e33825e6", "metadata": {}, "source": [ "#### Chamando os métodos Setters\n", "\n", "Para modificar os valores dos atributos privados, podemos chamar os métodos Setters que criamos anteriormente. Ao contrário dos Getters, os Setters recebem um argumento que é o novo valor que queremos atribuir ao atributo privado." ] }, { "cell_type": "code", "execution_count": null, "id": "43a7f3d0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Construtor da classe Cachorro chamado!\n", "Rex\n", "Bob\n" ] } ], "source": [ "doguinho = Cachorro(\"Rex\", \"Marrom\")\n", "print(doguinho.get_nome())\n", "doguinho.set_nome(\"Bob\")\n", "print(doguinho.get_nome())" ] } ], "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 }