O terror de muita gente, menos para você: introduzindo a Programação Orientada a Objetos#
O que você saberá se você ler todo este capítulo?#
Saberá que Programação Orientada a Objetos é uma forma de criarmos algoritmos mais complexos ao estruturá-los de forma parecida com o jeito que pensamos e organizamos as coisas no mundo real.
Que muita gente tem medo desse assunto, mas pode ser mais simples do que parece.
E que é fácil trabalharmos com classes, objetos, atributos e métodos em Python.
O terror das universidades#
Você sabia que programação orientada a objetos tira o sono de muita gente? Pois é. É um conceito que muita gente tem medo porque parece bem complexo. E, para ser bem sincero, pode ser explicado de um jeito bem difícil. Logo, o meu desafio aqui é que você entenda sem maiores dificuldades o que isso é e como isso funciona. Espero que você realmente entenda, e ficarei bem feliz em saber se cumpri (ou não) esta missão. Bora lá?

Programação Orientada a Objetos (POO)#
POO é um jeito de escrevermos e organizarmos algoritmos de um jeito que é mais parecido com o mundo real. E, sabe qual outra coisa é parecida (às vezes) com o mundo real? Um filme. Neste exemplo, imagine que você é um produtor de cinema e precisa criar uma lista de filmes para serem produzidos. Cada filme tem uma história única, com personagens e enredos diferentes, e todos os elementos estão relacionados entre si para contar essa história. Por exemplo, em um filme de super-herói, o personagem principal pode ter habilidades especiais, um vilão pode ter uma fraqueza específica, e o objetivo final do herói pode ser salvar uma cidade inteira. Tudo isso faz parte de um único filme.
Vamos então usar esses exemplos para demonstrar como funciona POO? Primeiro, precisamos definir alguns termos para falarmos na mesma língua.
Classe#
Agora, pensemos que você precisa fazer um “molde” contendo tudo o que um filme, um personagem ou qualquer outra coisa precisa ter. Você ainda não pensou em nada em específico, mas só está tentando montar um esqueleto das coisas que são necessárias para você começar a produzir. Vamos pensar em alguns exemplos:
Classe
filme
: bom, todo filme deveria ter um nome, o(a) protagonista, o(a) antagonista e o ano em que ele seria lançado, não é? Imagine que estaríamos fazendo um cadastro de todos os filmes - consegue imaginar que outras informações todo filme deveria ter?Classe
superheroi
: agora, o que todo super-herói deveria ter? Talvez um nome, sua fraqueza, a lista do seu principal rival, e o seu superpoder. Também imagine fazendo um cadastro de vários super-heróis: que características todo super-herói deveria ter?
Objeto#
Até agora não pensamos em nenhum filme ou super-herói em específico, né? Até agora, só pensamos nas classes (moldes). Agora que temos um molde em mãos, vamos pensar em alguns exemplos únicos a partir dessas classes. Vamos lá:
Classe
filme
:Filme 1:
Nome: O Herói sem Nome
Protagonista: Tom
Antagonista: John
Ano: 2015
Filme 2:
Nome: A Busca pelo Tesouro Perdido
Protagonista: Michael
Antagonista: Robert
Ano: 2012
Filme 3:
Nome: O Mistério da Ilha Deserta
Protagonista: Emily
Antagonista: Jake
Ano: 2022
Classe
superheroi
:Super-herói 1:
Nome: Tom
Fraqueza: Eletricidade
Principal Rival: John
Superpoder: Velocidade extrema
Super-herói 2:
Nome: Emily
Fraqueza: Intolerância à Lactose
Principal Rival: Jake
Superpoder: Força sobre-humana
Super-herói 3:
Nome: Michael
Fraqueza: Chuva
Principal Rival: Robert
Superpoder: Controle de fogo
Consegue perceber que cada um desses exemplos são diferentes uns dos outros? São únicos. O nome desses “indivíduos” (cada filme, ou cada super-herói) é um objeto. Cada objeto que você cria a partir dessa classe é como um filme ou super-herói único, com as suas próprias características. Podemos até ter dois filmes chamados “O Herói sem Nome”, mas podem ter sido encenados por atores diferentes, em anos diferentes ou diretores diferentes. Ou dois super-heróis chamados Tom, mas cada um deles seria único em suas características e imperfeições.
Em outras palavras, veja que cada objeto que você cria a partir de uma classe seria como um super-herói (ou filme) diferente, com a sua individualidade.
Atributo#
Atributos em POO são como características dos objetos. Eles permitem que os objetos tenham propriedades específicas que os diferenciem de outros objetos da mesma classe. Nos exemplos acima, os atributos que temos são:
Classe
filme
:nome
,protagonista
,antagonista
, eano
.Classe
superheroi
:nome
,fraqueza
,principal rival
, esuperpoder
.
Simples, não é?
Métodos#
Você pode ter visto nos capítulos anteriores algumas menções de métodos e ficou meio sem saber o que aquilo significava, né? Agora é o momento de explicar. Se os filmes, super-heróis ou qualquer outra classe possui os seus atributos (características) que os definem, os mesmos também fazem algo, não é?
Quer dizer, vamos pensar um pouco: um filme tem ações que acontecem como, por exemplo, as cenas de ação ou diálogos que ocorrem ao longo da história. Cada cena é uma ação específica que os personagens realizam para avançar a trama. Um super-herói também pode fazer coisas, certo? Ele pode falar, correr, lutar, gritar, entre outras ações. Até outros objetos que a princípio seriam inanimados também possuem as suas ações na vida real: uma bola pode ficar parada ou rolar, não é?
Todas essas ações são os métodos em POO. Os métodos são funções que executam uma ação ou operação específica dentro de uma classe, alterando seus atributos ou retornando um valor.
Vamos listar estes exemplos?
Classe
filme
:trocar de cena
iniciar diálogo
terminar diálogo
mudar câmera
Classe
superheroi
:falar
correr
lutar
gritar
rastejar
dormir
comer
Acesso público e privado#
Agora, vamos supor que você está assistindo a um filme e pode ver tudo o que acontece na tela. Só que não. Alguns detalhes da história, como as emoções e pensamentos dos personagens, não são mostrados na tela. Esses detalhes são como os atributos privados de uma classe na POO. Agora, pense em um filme em que um personagem tem um objeto muito importante que ele não quer que ninguém mais saiba. Por exemplo, uma chave que abre um cofre. Ele não quer que nenhum outro personagem saiba sobre a chave. Isso é como um atributo privado em POO - algo que só pode ser acessado dentro da classe ou objeto.
Já os atributos públicos são como informações mostradas na tela do filme. Por exemplo, a cor do cabelo do personagem principal ou o modelo do carro que ele está dirigindo. Esses são detalhes que todos podem ver e acessar, assim como os atributos públicos em POO.
O mesmo se aplica aos métodos. Os métodos privados e públicos são como ações realizadas pelos personagens. Alguns desses movimentos são importantes apenas para a trama do filme e não precisam ser compartilhados com o público. Esses são como os métodos privados em programação orientada a objetos. Já os métodos públicos são como as ações dos personagens que são mostradas na tela, como lutar contra um vilão ou salvar o mundo. Ou seja: o fato de ser “público” ou “privado” serve para controlar a visibilidade e acesso às informações e ações dentro de uma classe ou objeto, entende?
Herança#
Agora, vamos imaginar que existam vários filmes que compartilham algumas características em comum. Filmes que se passam no espaço, por exemplo, possuem ações exclusivas desses filmes, como cenas que se passam dentro de uma espaçonave, cenas que se passam ao chegar e sair de um planeta, e cenas de batalhas espaciais. Também podem ter características que sejam só deles, como a época do futuro em que o filme se passa e se existem alienígenas ou não. Ao criar um novo filme assim, em vez de começar do zero e recriar todos esses atributos e métodos do zero, é possível herdar essas características de um “filme pai” e, em seguida, modificar as caraterísticas específicas do novo filme. Além disso, o jeito de se fazer um filme pode ser um pouco diferente de outros filmes. O método "mudar câmera"
, por exemplo, pode ser bem diferente porque teríamos que adicionar vários efeitos especiais do espaço na cena.
Esse exemplo é a descrição de uma herança. Em POO, ela é uma forma de criar novas classes baseadas em classes existentes, reutilizando assim o código já existente e evitando a redundância. Uma classe pode ser criada com atributos e métodos específicos, e outra classe pode ser criada para “herdar” esses atributos e métodos e adicionar mais recursos a eles. A classe que é herdada é chamada de classe pai ou superclasse, enquanto a nova classe é chamada de classe filho ou subclasse. A subclasse pode adicionar novos atributos e métodos, ou pode substituir ou modificar os existentes.
Vamos imaginar como seria no caso dos filmes:
Superclasse
filme
:Atributos:
nome
protagonista
antagonista
ano
Métodos:
trocar de cena
iniciar diálogo
terminar diálogo
mudar câmera
Subclasse
filme no espaço
:Atributos:
ano em que se passa
tem alienígenas?
E mais todos os outros atributos de
filme
Métodos
iniciar batalha espacial
terminar batalha espacial
E mais todos os outros métodos de
filme
Quer outro exemplo? Este é bem usado em livros e tutoriais por aí, e funciona muito bem. Imagine que temos uma classe Animal
com atributos como peso
, tamanho
e cor
, bem como métodos como comer
e dormir
. Uma subclasse, como a classe Cachorro
, pode herdar os atributos e métodos da classe Animal
(já que um cachorro não deixa de ser um animal) e adicionar atributos e métodos específicos de cachorro, como latir
e buscar objetos
.
Também podemos modificar os métodos já existentes para que sejam específicos de um cachorro. Por exemplo: o jeito que um cachorro dorme pode ser diferente do jeito que outro animal (como uma galinha) dorme.
Polimorfismo#
Agora, vamos imaginar um filme de ação. Em uma determinada cena, dois personagens diferentes precisam atravessar um rio. O primeiro personagem é um nadador profissional e decide nadar pelo rio. O segundo personagem é um especialista em parkour e escolhe pular entre pedras para atravessar o rio. Mesmo que eles tenham habilidades diferentes, ambos conseguem atravessar o rio e cumprir a tarefa.
Esse exemplo é um caso de polimorfismo. Em POO, polimorfismo trata da capacidade de objetos de diferentes classes serem tratados de forma semelhante. Isso pode tornar o código mais flexível e reutilizável.
Quer outro exemplo? Por exemplo, imagine que temos uma classe Animal
que contém o método fazer som
. Um objeto Cachorro
pode responder a esse método latindo, enquanto um objeto Gato
pode responder com um miado.
🖐 Resumo#
Classes: moldes para criar objetos.
Objetos: instâncias de uma classe.
Atributos: características de um objeto.
Métodos: ações que um objeto pode executar (funções de um objeto).
Acesso público: é quando qualquer objeto de fora pode acessar um atributo ou método.
Acesso privado: é quando somente o objeto que possui o atributo ou método pode acessá-lo.
Herança: é quando uma classe herda atributos e métodos de outra.
Polimorfismo: é quando um objeto pode assumir diferentes formas e comportamentos.
Analogias com personagens de filmes:
Classes: um molde de tudo o que um personagem deve ter.
Objetos: os personagens.
Atributos: características de um personagem como o nome, ator/atriz que o interpreta, e cor do cabelo.
Métodos: ações que o personagem pode fazer em um filme, como lutar, falar, correr, nadar e pular.
Acesso público: atributos e métodos que podem ser acessados por outros personagens no filme, como conversar com o personagem.
Acesso privado: atributos e métodos que só o próprio personagem pode ter acesso, como entrar em uma sala secreta.
Herança: a habilidade de um personagem ter características de outro personagem, como se fossem da mesma família em um filme.
Polimorfismo: quando um mesmo método pode ser usado por diferentes personagens de forma diferente em um filme, como lutar.
Analogias, agora (novamente) com bolos:
Classes: receitas de bolo
Objetos: os bolos que você pode comer
Atributos: ingredientes
Métodos: as etapas de preparo
Acesso público: um ingrediente que qualquer pessoa pode adicionar ao bolo
Acesso privado: é um ingrediente secreto que somente o dono da receita pode usar
Herança: é quando uma receita é adaptada de outra
Polimorfismo: é um bolo que pode ser decorado de diferentes formas.
👩💻 Usamos POO para desenvolver games, software de gerenciamento, aplicativos para celulares e tablets ou, ainda, para a criação de interfaces gráficas de usuário. A POO é especialmente útil quando há necessidade de reutilização de código e para construção de programas complexos que precisam ser facilmente modificados e mantidos.
POO em Python#
Agora você já conhece alguns dos principais termos de POO, certo? É claro que existem mais terminologias como interfaces e classes abstratas, mas como isso aqui é uma introdução eu entendo que é importante deixar de forma clara esses conceitos-base de POO. Dito isso, vamos ao Python!
Classes, Construtores, Atributos e Métodos#
Teste o código abaixo no PyCharm. Você verá que não acontecerá muita coisa. Pode ficar de boa - em breve você já começará a ver os resultados. Olha só:
class Pessoa:
def __init__(self, nome_informado, idade_informada):
self.nome = nome_informado
self.idade = idade_informada
Esse código define uma classe chamada Pessoa
. Veja que na primeira linha definimos uma classe com a palavra-chave class
e, logo em seguida, o nome daquela classe (que, aqui, é Pessoa
). Veja que não usamos aspas nem nada nesta definição da classe.
Depois disso, veja que temos uma função chamada __init__
. Quer dizer: o nome em POO para isso não é mais função, mas sim método. Em Python, todo método chamado __init__
dentro de uma classe é chamado de construtor. É um método especial que é executado toda vez que um novo objeto é criado a partir dessa classe.
Este método define dois atributos, nome
e idade
, que são especificados durante a criação do objeto. Esses atributos são referenciados através da palavra-chave self
, que se refere ao objeto atual sendo criado. Ou seja: essa classe permite criar objetos que representam pessoas com nome e idade.
O
self
é bastante usado para se referir aos atributos que estão dentro daquela mesma classe.
Está mais ou menos claro, não é? Esse código criou uma classe com dois atributos, e é uma classe que representa algo do mundo real (no caso, pessoas). Ainda não criamos nenhuma pessoa em si, este é apenas um molde com tudo o que qualquer pessoa deve ter.
Objetos#
Agora, e para criar objetos? Vamos supor que precisamos criar 3 pessoas utilizando a classe Pessoa
. Como faríamos isto? Teste este código:
pessoa1 = Pessoa("Maria", 25)
pessoa2 = Pessoa("João", 30)
pessoa3 = Pessoa("Pedro", 20)
print(pessoa1.nome)
print(pessoa3.idade)
A saída será isto:
Maria
20
Lembra que a classe Pessoa
tinha um construtor que recebia dois parâmetros: nome
e idade
, certo? Ao criar os objetos, precisamos passar os valores correspondentes a estes dois parâmetros e seguindo exatamente a mesma ordem. Os objetos são armazenados nas variáveis pessoa1
, pessoa2
e pessoa3
, respectivamente.
Veja que também conseguimos ler os valores dentro dos atributos de cada pessoa. Conseguimos ler o nome da primeira pessoa ao usar pessoa1.nome
, e a idade da terceira pessoa com pessoa3.idade
. Perceba que sempre existe um ponto (.
) entre o objeto que queremos acessar e o atributo que está dentro deste objeto.
Métodos#
Até agora você viu um exemplo de classe, atributo e objetos em Python. O que acha de pensarmos em um caso com métodos? Vamos imaginar a implementação de uma classe chamada Circulo
que possui 3 atributos (raio, cor da borda e cor do preenchimento do círculo) e 4 métodos (calcular área, calcular perímetro, mudar as cores e redimensionar o raio):
import math
class Circulo:
def __init__(self, raio_circulo, cor_borda, cor_preenchimento):
self.raio = raio_circulo
self.cor_borda = cor_borda
self.cor_preenchimento = cor_preenchimento
def calcular_area(self):
return math.pi * self.raio ** 2
def calcular_perimetro(self):
return 2 * math.pi * self.raio
def mudar_cor(self, nova_cor_borda, nova_cor_preenchimento):
self.cor_borda = nova_cor_borda
self.cor_preenchimento = nova_cor_preenchimento
def redimensionar_raio(self, novo_raio):
self.raio = novo_raio
Primeiro: aquele import math
não tem nenhuma relação com POO. Usamos ele somente para que possamos pegar o valor de \(\pi\), e este valor está dentro do módulo math
do Python. Isso é bem mais assertivo do que digitar na mão \(3.1415...\). Aqui, a classe Circulo
recebe três atributos em seu construtor: o raio
, a cor_borda
e a cor_preenchimento
.
Veja que o
self
sempre é o primeiro parâmetro. Isso permite que o método acesse e modifique os atributos do próprio objeto. Oself
também é usado para diferenciar as variáveis locais da classe e os atributos do objeto.
Os métodos calcular_area()
e calcular_perimetro()
são responsáveis por calcular a área e o perímetro do círculo, respectivamente. O método mudar_cor()
permite que o usuário mude as cores da borda e do preenchimento se quiser. Por fim, o método redimensionar_raio()
permite que o usuário altere o raio do círculo para um novo valor (novo_raio
).
Vamos criar 3 objetos de Circulo
? Olha só:
circulo1 = Circulo(3, "amarelo", "azul")
circulo2 = Circulo(5, "verde", "verde")
circulo3 = Circulo(2, "azul", "roxo")
Criamos 3 círculos diferentes, e cada um deles com um raio e cores diferentes, certo? Agora podemos chamar os métodos da classe para realizar cálculos ou alterar os atributos dos objetos:
print(f'O raio do círculo 3 é {circulo3.raio}')
print(f'A área do círculo 3 é {circulo3.calcular_area()}')
circulo3.redimensionar_raio(4)
print(f'O novo raio do círculo 3 é {circulo3.raio}')
print(f'A nova área do círculo 3 é {circulo3.calcular_area()}')
print(circulo1.cor_borda, circulo1.cor_preenchimento)
print(circulo2.calcular_perimetro())
O resultado deve ser o seguinte:
O raio do círculo 3 é 2
A área do círculo 3 é 12.566370614359172
O novo raio do círculo 3 é 4
A nova área do círculo 3 é 50.26548245743669
amarelo azul
31.41592653589793
Perceba algumas coisas no exemplo acima:
Acessamos os atributos sempre seguindo o padrão
$OBJETO.$ATRIBUTO
e os métodos usando$OBJETO.$MÉTODO($PARÂMETROS)
. Veja que para acessar oraio
docirculo3
faríamoscirculo3.raio
. Se fosse docirculo1
, seriacirculo1.raio
.Para chamar os atributos não usamos parênteses. Para chamar método usamos parênteses. Veja que sempre usamos
circulo1.raio
, e nãocirculo1.raio()
. Isto acontece porque oraio
é um atributo, e não uma função (método).Métodos podem ou não ter retorno e parâmetros, igual como aprendemos em funções, anteriormente. No exemplo acima,
redimensionar_raio()
tem um parâmetro de entrada (que é o novo raio) e não tem retorno. Jácalcular_area()
não tem parâmetro de entrada, mas tem um retorno.
Veja que sempre “salvamos” um ajuste no atributo dentro do objeto antes de usá-lo. Pegue, por exemplo, o
calcular_area()
. Em nenhum momento informamos o raio para este método porque ele precisa obrigatoriamente calcular isso usando todos os atributos que já estão dentro daquele objeto. Se precisarmos alterar por qualquer motivo que seja o raio ou outros atributos precisaremos, obrigatoriamente, chamar outro método somente para fazer isto.
Acesso público e privado#
Os exemplos acima só tiveram atributos e métodos públicos. E se quiséssemos também incluir atributos e métodos privados? Veja só:
import math
class Circulo:
def __init__(self, raio_circulo, cor_borda, cor_preenchimento):
self.__raio = raio_circulo
self.cor_borda = cor_borda
self.cor_preenchimento = cor_preenchimento
def calcular_area(self):
return math.pi * self.__raio ** 2
def calcular_perimetro(self):
return 2 * math.pi * self.__raio
def mudar_cor(self, nova_cor_borda, nova_cor_preenchimento):
self.cor_borda = nova_cor_borda
self.cor_preenchimento = nova_cor_preenchimento
def redimensionar_raio(self, novo_raio):
self.__raio = novo_raio
def __metodo_privado(self):
print("Este é um método privado.")
Veja que o atributo raio foi modificado para __raio
e que o método privado foi criado com o prefixo de dois underscores (__metodo_privado
). Isso indica que esses elementos são privados e só devem ser acessados internamente na classe. Simples assim. Isto não significa que o acesso externo será proibido: da mesma forma que as pessoas ainda podem pisar na grama mesmo que tenha uma placa escrito “Proibido pisar na grama”, outros métodos poderão acessar estes atributos e métodos privados. Os dois underscores (__
) servem para recomendar que não se acesse externamente, somente. Por isso, não entenda que isto funciona como algum tipo de trava de segurança neste sentido.
Herança e Polimorfismo#
Beleza, e para testarmos heranças? Bom, o círculo não deixa de ser uma forma, não é? Assim como o retângulo também é. Então, vamos mudar um pouco as coisas. Teste o código abaixo no PyCharm:
import math
class Forma:
def calcular_area(self):
return "Ainda não tenho uma lógica implementada para calcular esta área."
def calcular_perimetro(self):
return "Ainda não tenho uma lógica implementada para calcular este perímetro."
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def calcular_perimetro(self):
return 2 * math.pi * self.raio
def redimensionar_raio(self, novo_raio):
self.raio = novo_raio
class Retangulo(Forma):
def __init__(self, base, altura):
self.base = base
self.altura = altura
def redimensionar_base(self, nova_base):
self.base = nova_base
def redimensionar_altura(self, nova_altura):
self.altura = nova_altura
circulo1 = Circulo(3)
retangulo1 = Retangulo(10, 2)
print(circulo1.calcular_area())
print(circulo1.calcular_perimetro())
print(retangulo1.calcular_area())
print(retangulo1.calcular_perimetro())
Herança#
A classe Forma
é uma classe base que contém os métodos que todas as outras classes irão herdar. A partir dela, foram criadas as subclasses Circulo
e Retangulo
, cada uma com seus próprios métodos e atributos. Perceba que temos agora class Circulo(Forma)
e class Retangulo(Forma)
. Este (Forma)
presente em ambos os casos é o que diz que estas duas classes são herdadas de Forma
.
Veja também que cada classe herdada possui um construtor diferente: o círculo possui somente um parâmetro no construtor (raio
) e o retângulo possui dois (base
e altura
). Além disso, cada classe herdada pode ter os seus próprios métodos.
A classe Circulo
, por exemplo, possui o método redimensionar_raio()
, que permite alterar o valor do raio do círculo. A classe Retangulo
possui a redimensionar_base()
e redimensionar_altura()
.
Ao rodar o código, o seguinte resultado aparecerá:
Ainda não tenho uma lógica implementada para calcular esta área.
18.84955592153876
Ainda não tenho uma lógica implementada para calcular esta área.
Ainda não tenho uma lógica implementada para calcular este perímetro.
Veja que somente a segunda linha retornou um valor numérico. Se olharmos o código, verá que a classe Forma
retorna somente uma mensagem para os métodos calcular_area()
e calcular_perimetro()
, e somente a classe Circulo
reimplementou o método calcular_perimetro()
para que ela tenha a sua própria lógica.
👉 Ou seja: não precisamos implementar todos os métodos da superclasse para todas as classes herdadas. Se não implementarmos, a classe herdada usará a mesma lógica implementada pela superclasse. Como não implementamos a nossa própria lógica de
calcular_area()
paraCirculo
eRetangulo
e nem implementamos a nossa lógica decalcular_perimetro()
para oRetangulo
, o algoritmo usou a lógica que existia lá emForma
.
Polimorfismo#
Ah, e o polimorfismo! Achou que eu ia me esquecer? Vamos alterar o código acima para implementar a lógica de calcular_perimetro()
e calcular_area()
para as duas classes herdadas:
import math
class Forma:
def calcular_area(self):
return "Ainda não tenho uma lógica implementada para calcular esta área."
def calcular_perimetro(self):
return "Ainda não tenho uma lógica implementada para calcular este perímetro."
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def calcular_area(self):
return math.pi * self.raio ** 2
def calcular_perimetro(self):
return 2 * math.pi * self.raio
def redimensionar_raio(self, novo_raio):
self.raio = novo_raio
class Retangulo(Forma):
def __init__(self, base, altura):
self.base = base
self.altura = altura
def calcular_area(self):
return self.base * self.altura
def calcular_perimetro(self):
return 2 * (self.base + self.altura)
def redimensionar_base(self, nova_base):
self.base = nova_base
def redimensionar_altura(self, nova_altura):
self.altura = nova_altura
circulo1 = Circulo(3)
retangulo1 = Retangulo(10, 2)
print(circulo1.calcular_area())
print(circulo1.calcular_perimetro())
print(retangulo1.calcular_area())
print(retangulo1.calcular_perimetro())
Se você rodar este código verá que teremos o seguinte resultado:
28.274333882308138
18.84955592153876
20
24
Consegue ver que tanto para o círculo quanto para o retângulo pedimos pelo mesmo nome do método? Ou seja: para ambos, chamamos o calcular_area()
e calcular_retangulo()
. Por outro lado, os dois possuem lógicas de implementação totalmente diferentes. Isto é um exemplo prático de polimorfismo.
Quer outro exemplo de polimorfismo que já existe no Python? O operador +
. Veja que ele consegue somar números (2 + 2
será igual a 4
), mas ele também funciona para string ("Teste" + "Chinelo"
será igual a TesteChinelo
). Perceba que o mesmo operador tem comportamentos diferentes para tipos de dados diferentes: isto também é polimorfismo.
Exercícios#
Crie uma classe chamada
Animal
com atributosnome
eespecie
. Crie um método para mostrar na tela o som que o animal faz. Crie um objeto para demonstrar o seu uso.Crie uma classe
Calculadora
com os métodossomar
,subtrair
,multiplicar
edividir
. Crie um objeto dessa classe e utilize os métodos para realizar operações aritméticas simples.Crie uma classe
Funcionario
com os atributosnome
esalario
. Em seguida, crie um método para calcular o salário anual do funcionário, incluindo o décimo-terceiro salário. Crie um objeto dessa classe com salário definido e exiba na tela o salário anual.Crie uma classe
Estudante
com os atributosnome
,idade
enota
. Em seguida, crie um método para calcular a média das notas de dois alunos. Crie dois objetos dessa classe com notas definidas e exiba na tela a média calculada.Crie uma classe chamada
Livro
com atributostitulo
,autor
eano_publicacao
. Crie um método para imprimir as informações do livro. Crie um objeto para demonstrar o seu uso.
Respostas#
1. Crie uma classe chamada Animal
com atributos nome
e especie
. Crie um método para mostrar na tela o som que o animal faz. Crie um objeto para demonstrar o seu uso.
class Animal:
def __init__(self, nome, especie):
self.nome = nome
self.especie = especie
def fazer_som(self):
print("{} está fazendo um som de {}.".format(self.nome, self.especie))
# Cria um objeto da classe Animal
meu_animal = Animal("Kora", "cachorro")
# Chama o método som do objeto criado
meu_animal.fazer_som()
Este código define uma classe chamada Animal
com dois atributos: nome
e especie
. Além disso, há um método chamado fazer_som()
que imprime na tela o som que o animal está fazendo, utilizando as informações dos atributos nome
e especie
. O self
é uma referência para a instância da classe, permitindo acessar os atributos e métodos da mesma.
Para exemplificar, criamos objeto chamado meu_animal
a partir da classe Animal
, passando "Kora"
como o nome do animal e "cão"
como a espécie. Para testar isso, usamos o método fazer_som()
do objeto meu_animal
, que imprime na tela o som que o animal está fazendo. Neste caso, a saída será "Kora está fazendo um som de cachorro."
.
2. Crie uma classe Calculadora
com os métodos somar
, subtrair
, multiplicar
e dividir
. Crie um objeto dessa classe e utilize os métodos para realizar operações aritméticas simples.
class Calculadora:
def somar(self, a, b):
return a + b
def subtrair(self, a, b):
return a - b
def multiplicar(self, a, b):
return a * b
def dividir(self, a, b):
if b == 0:
return "Não é possível dividir por zero"
else:
return a / b
# Cria um objeto da classe Calculadora
minha_calculadora = Calculadora()
# Realiza algumas operações aritméticas simples
print(minha_calculadora.somar(2, 3)) # Imprime 5
print(minha_calculadora.subtrair(5, 2)) # Imprime 3
print(minha_calculadora.multiplicar(4, 6)) # Imprime 24
print(minha_calculadora.dividir(10, 5)) # Imprime 2.0
print(minha_calculadora.dividir(10, 0)) # Imprime "Não é possível dividir por zero"
Percebeu que este código não tem o construtor (def __init__
)? O código não tem construtor porque ele não precisa de nenhum atributo específico para funcionar corretamente. Todas as operações aritméticas podem ser realizadas apenas com os parâmetros de entrada dos métodos. Por isso, não é necessário criar um construtor que inicialize algum atributo da classe.
Veja que este é um caso específico em que não precisamos guardar nenhum atributo antes para calcular. Como uma calculadora é usada para testar diferentes combinações de valores, o propósito dela realmente não precisa de nenhum construtor neste cenário.
3. Crie uma classe Funcionario
com os atributos nome
e salario
. Em seguida, crie um método para calcular o salário anual do funcionário, incluindo o décimo-terceiro salário. Crie um objeto dessa classe com salário definido e exiba na tela o salário anual.
class Funcionario:
def __init__(self, nome, salario):
self.nome = nome
self.salario = salario
def calcular_salario_anual(self):
return self.salario * 13
# Cria um objeto da classe Funcionario
funcionario1 = Funcionario("João", 3000)
# Calcula o salário anual do funcionário
salario_anual = funcionario1.calcular_salario_anual()
# Exibe na tela o salário anual do funcionário
print("O salário anual do funcionário {} é R$ {:.2f}".format(funcionario1.nome, salario_anual))
Veja que este código possui uma classe chamada Funcionario
, um método construtor que recebe o nome
e o salario
de um funcionário e, ainda, um método especializado em calcular o salário anual levando em consideração o décimo-terceiro salário (calcular_salario_anual()
). Se testarmos um objeto da classe Funcionario com nome "João"
e R$ 3000,00 (3000
), o método calcular_salario_anual()
retornará o valor 39000
. Este resultado é mostrado no final em uma frase assim: "O salário anual do funcionário João é R$ 39000.00"
.
4. Crie uma classe Estudante
com os atributos nome
, idade
e nota
. Em seguida, crie um método para calcular a média das notas de dois alunos. Crie dois objetos dessa classe com notas definidas e exiba na tela a média calculada.
class Estudante:
def __init__(self, nome, idade, nota):
self.nome = nome
self.idade = idade
self.nota = nota
def calcular_media(self, outro_estudante):
media = (self.nota + outro_estudante.nota) / 2
return media
# Cria os objetos da classe Estudante
estudante1 = Estudante("Ana", 20, 8.5)
estudante2 = Estudante("João", 19, 7.5)
# Chama o método calcular_media para calcular a média das notas dos dois estudantes
media_notas = estudante1.calcular_media(estudante2)
# Exibe a média calculada na tela
print("A média das notas dos estudantes {} e {} é: {}".format(estudante1.nome, estudante2.nome, media_notas))
Aqui, criamos uma classe Estudante
com os atributos nome
, idade
e nota
. Em seguida, criamos o método calcular_media()
que recebe como parâmetro outro objeto da classe Estudante
, e que calcula a média das notas do objeto atual e do objeto passado como parâmetro (percebe o outro_estudante
?).
São criados dois objetos da classe Estudante
com notas definidas e é chamado o método calcular_media()
para calcular a média das notas desses dos dois estudantes. Neste caso em específico, a seguinte mensagem seria mostrada na tela: "A média das notas dos estudantes Ana e João é: 8.0"
.
5. Crie uma classe chamada Livro
com atributos titulo
, autor
e ano_publicacao
. Crie um método para imprimir as informações do livro. Crie um objeto para demonstrar o seu uso.
class Livro:
def __init__(self, titulo_informado, autor_informado, ano_publicacao_informado):
self.titulo = titulo_informado
self.autor = autor_informado
self.ano_publicacao = ano_publicacao_informado
def mostrar_informacoes(self):
print("Título: {}".format(self.titulo))
print("Autor: {}".format(self.autor))
print("Ano de publicação: {}".format(self.ano_publicacao))
# Cria um objeto da classe Livro
meu_livro = Livro("O Senhor dos Anéis: A Sociedade do Anel", "J. R. R. Tolkien", 1954)
# Chama o método imprimir_info do objeto criado
meu_livro.mostrar_informacoes()
Veja que criamos a classe Livro
com três atributos no construtor: titulo
, autor
e ano_publicacao
. Temos também um método: mostrar_informacoes()
, responsável por mostrar as informações do livro na tela (nome sugestivo, não?). Depois, criamos um objeto da classe Livro
com as informações do livro “O Senhor dos Anéis: A Sociedade do Anel”, e testamos com o método mostrar_informacoes()
.
Aqui, usei no construtor uns nomes um pouco modificados (titulo_informado
, autor_informado
, e ano_publicacao_informado
) para que você perceba que essas variáveis são coisas diferentes em relação a self.titulo
, self.autor
e self.ano_publicacao
.
Referências bibliográficas#
DOWNEY, A. B. Think Python: How to Think Like a Computer Scientist (2ª ed.). O’Reilly Media, 2015.
MICROSOFT. Herança em C#. Disponível em: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/inheritance. Acesso em 14 mar. 2023.
ORACLE. Site da Oracle sobre herança em Java. Disponível em: https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html. Acesso em: 14 mar. 2023.
PYTHON. Documentação oficial. Classes. Disponível em: https://docs.python.org/3/tutorial/classes.html. Acesso em 14 mar. 2023.
PYTHON. Documentação oficial. Polimorfismo. Disponível em: https://docs.python.org/3/library/functions.html#callable. Acesso em 14 mar. 2023.
PROGRAMIZ. Class and Objects. Disponível em: https://www.programiz.com/python-programming/class. Acesso em 14 mar. 2023.
PROGRAMIZ. Object-Oriented Programming. Disponível em: https://www.programiz.com/python-programming/object-oriented-programming. Acesso em 14 mar. 2023.
SCHILD, H. Java: A Beginner’s Guide, Seventh Edition. McGraw Hill Professional, 2018.
SIERRA, K.; BATES, B. Head First Java: A Brain-Friendly Guide. O’Reilly Media, Inc., 2005.
VANDERPLAS, J. Python Data Science Handbook. Disponível em: https://jakevdp.github.io/PythonDataScienceHandbook/. Acesso em: 14 mar. 2023
W3SCHOOLS. Python Classes. Disponível em: https://www.w3schools.com/python/python_classes.asp. Acesso em 14 mar. 2023.