Aula 07: Classes e Objetos
Aula 07: Classes e objetos¶
Nessa aula vamos introduzir o conceito de classes e objetos e os fundamentos de programação orientada por objetos.
Esse é um assunto complexo e extenso e a idéia aqui é passar os fundamentos em Python. A idéia é entender a diferença entre método e função e como usar classes e objetos em Python mais do que criá-las você mesmo.
Esta aula está baseadano capítulo 10 do livro Introdução à Progamação com PYTHON de Nilo Ney Coutinho Menezes, 3a edição, editora Novatec.
Começamos o curso usando o Python como uma calculadora. Depois introduzimos variáveis, estruturas de dados e funções. Á medida que o curso avançou reutilizamos todas os conceitos anteriores.
De certa maneira, qualquer programa, por maior e mais complexo ele seja, é formado de elementos básicos como os que vimos até o momento. A dificuldade na programação é enxergar a floresta sem se perder com as árvores. Qualquer linha é facilmente compreensível se a linguagem de programação for minimamente conhecida.
Programação orientada por objetos (POO)¶
Entender o que um pedaço de código faz é bem mais complexo, assim como escrever o código. Essa dificuldade permeia toda a história da ciência da computação. Uma das "soluções" para este problema foi a orientação por objetos.
Neste paradigma, objetos representam o problema e um programa é feito através da comunicação entre os objetos através de métodos. Em algumas linguagens como Smalltalk, esse conceito é levado literalmente.
Não existe definição simples de OOP. Mas alguns conceitos são recorrentes:
- Objetos
- Classes
- Encapsulamento
- Herança
Veremos brevemente esses conceitos aplicados a Python
Objetos como representação do mundo real¶
Um objeto é uma representação de algo no mundo real, uma representação idealizada e simplificada do mundo real.
Imagine uma televisão. Ela tem algumas características:
- Pode ser ligada ou desligada
- Ela está sintonizada em um canal
- Pode aumentar ou diminuir o som
- Muitas outras coisas (numa tv moderna)
Então vamos usar uma classe Televisão
para caracterizar uma televisão:
class Televisão: # Definição de uma classe
def __init__(self): # Método especial - construtor
self.ligada = False # atributo
self.canal = 2 # outro atributo
Agora podemos criar um objeto Televisão
que pode representar uma TV:
tv = Televisão() # Criar um objeto `Televisão`
tv.ligada
tv.canal
tv_sala = Televisão() # Outro objeto da classe televisão
tv_sala.ligada = True # Estou ligando a televisão
tv_sala.canal = 4 # Mudei o canal
tv.ligada
tv_sala.ligada
tv.canal
tv_sala.canal
O método __init__
é um método especial usado na construção de objetos de uma classe. Ele é conhecido como construtor.
Exercício 1¶
Adicione atributos tamanho e marca à classe Televisão
. Crie dois objetos Televisão
e atribua tamanhos e marcas diferentes. Aí imprima os atribuitos para ver a diferença
Vamos dar alguma funcionalidade à class Televisão
:
class Televisão:
def __init__(self):
self.ligada = False
self.canal = 2
def muda_canal_para_baixo(self):
self.canal -= 1
def muda_canal_para_cima(self):
self.canal += 1
tv = Televisão()
tv.canal
tv.muda_canal_para_cima()
tv.muda_canal_para_cima()
tv.canal
tv.muda_canal_para_baixo()
tv.canal
Passagem de parâmetros¶
Será que pode ter canal negativo? Ou o canal 8723245?
class Televisão:
def __init__(self, min, max):
self.ligada = False
self.canal = 2
self.cmin = min
self.cmax = max
def muda_canal_para_baixo(self):
if self.canal -1 >= self.cmin:
self.canal -= 1
def muda_canal_para_cima(self):
if self.canal +1 <= self.cmax:
self.canal += 1
tv = Televisão(2,13)
for i in range(100):
tv.muda_canal_para_cima()
print(tv.canal)
for i in range(100):
tv.muda_canal_para_baixo()
print(tv.canal)
Exercício 2¶
Atualmente a classe Televisão
inicializa o canal com 2. Modifique a classe de forma a receber o canal inicial em seu construtor.
Exercício 3¶
Modifique a classe Televisão
de modo que se pedirmos para mudar o canal para baixo, além do mínimo, ele vái para o máximo e vice e versa
Exercício 4¶
Utilizando o que aprendemos com funções modifique o construtor de classe Telefisão
de forma que min
e max
sejam parâmetros opcionais em que min
vale 2 e max
vale 13 caso outros valores não sejam passados
Exercício 5¶
Utilizando a classe modificada no exercício anterior (4), crie duas instâncias (objetos) especificando min
e max
por nome.
Exemplo de um Banco¶
Vamos tentar modelar contas correntes de um banco
class Cliente:
def __init__(self, nome, telefone):
self.nome = nome
self.telefone = telefone
joão = Cliente("João da Silva", "555-0178")
bozo = Cliente("Bozo Palhaço", "236-0873")
joão.nome
type(joão)
joão.telefone
bozo.nome
bozo.telefone
Em geral vamos armazenar isso num módulo. Para isso criamos o arquivo clientes.py
e importamos:
from clientes import Cliente
joão = Cliente("João da Silva", "555-0178")
bozo = Cliente("Bozo Palhaço", "236-0873")
type(joão)
Agora precisamos de um outro módulo para armazenar a class Conta
que representa uma conta corrente.
# Código para recarregar automaticamente os módulos que
%load_ext autoreload
%autoreload 2
from contas import Conta
conta = Conta(joão, "234.567-01", 1000)
conta.resumo()
conta.saque(100)
conta.resumo()
conta.saque(358)
conta.resumo()
conta.deposito(674)
conta.resumo()
conta.saque(2000)
conta.resumo()
conta.saque(900)
conta.resumo()
conta.extrato()
Exercício 6¶
Altere o programa de forma que a mensagem saldo insuficiente seja exibida caso haja tentativa de sacar mais dinheiro que o saldo disponível
Exercício 7¶
Modifique o método resumo
da class Conta
para exibir o nome e o telefone de cada cliente
from clientes import Cliente
from bancos import Banco
from contas import Conta
bozo = Cliente("Bozo Palhaço", "236-0873")
vovó = Cliente("Vovó Mafalda", "236-0873")
josé = Cliente("José Vargas", "3866-1234")
contaBV = Conta([bozo, vovó], 1, 100)
contaJ = Conta([josé], 2, 50)
tatu = Banco("Tatú")
tatu.abre_conta(contaBV)
tatu.abre_conta(contaJ)
tatu.lista_contas()
Herança¶
Herança é um recurso da programação orientada por objetos que permite reutilizar código, modificando sua característica
from contas import ContaEspecial
bozo = Cliente("Bozo Palhaço", "236-0873")
vovó = Cliente("Vovó Mafalda", "236-0873")
conta1 = Conta(["bozo"], 1, 1000)
conta2 = ContaEspecial([vovó, bozo], 2, 500, 1000)
conta1.saque(50)
conta1.resumo()
conta2.deposito(300)
conta2.resumo()
conta1.saque(190)
conta1.resumo()
conta2.deposito(95.15)
conta2.resumo()
conta2.saque(1500)
conta2.resumo()
conta1.extrato()
conta2.extrato()
Exercício 8¶
Modofique as classes Conta
e ContaEspecial
para que a operação de saque retorne verdadeiro se o saque foi realizado e false caso contrário
Exercício 9¶
Modifique a class ContaEspecial
de forma que seu extrato exiba o limite e o total disponível para saque.
Desenvolvendo uma classe para controlar listas¶
class ListaUnica:
def __init__(self, elem_class):
self.lista = []
self.elem_class = elem_class
def __len__(self):
return len(self.lista)
def __iter__(self):
return iter(self.lista)
def __getitem__(self, p):
return self.lista[p]
def indiceValido(self, i):
return i >= 0 and i < len(self.lista)
def adiciona(self, elem):
if self.pesquisa(elem) == -1:
self.lista.append(elem)
def remove(self, elem):
self.lista.remove(elem)
def pesquisa(self, elem):
self.verifica_tipo(elem)
try:
return self.lista.index(elem)
except ValueError:
return -1
def verifica_tipo(self, elem):
if not isinstance(elem, self.elem_class):
raise TypeError("Tipo inválido")
def ordena(self, chave=None):
self.lista.sort(key=chave)
lu = ListaUnica(int)
lu.adiciona(5)
lu.adiciona(3)
lu.adiciona(2.5)
len(lu)
for e in lu:
print(e)
lu.adiciona(5)
len(lu)
lu[0]
lu[1]
id(lu)
Outro exemplo com métodos "mágicos"
class Nome:
def __init__(self, nome):
if nome is None or not nome.strip():
raise ValueError("Nome não pode ser nulo nem um branco")
self.nome = nome
self.chave = nome.strip().lower()
def __str__(self):
return self.nome
def __repr__(self):
return f"<Classe {type(self).__name__} em 0x{id(self):x} Nome: {self.nome} Chave: {self.chave}>"
def __eq__(self, outro):
print("__eq__ Chamado")
return self.nome == outro.nome
def __lt__(self, outro):
print("__lt__ Chamado")
return self.nome < outro.nome
A = Nome("Nilo")
print(A)
B = Nome(" ")
C = Nome(None)
A == Nome("Nilo")
A != Nome("Nilo")
A < Nome("Nilo")
A > Nome("Nilo")
A >= Nome("Nilo")
Criando exceções¶
Podemos criar novos tipos de exceção para diferenciar os erros gerados nos nossos programas.
Para isso, é é necessário extender a class Exception
class NovaException(Exception):
pass
def lançar_erro():
raise NovaException
try:
lançar_erro()
except NovaException as n:
print("Uma excecão de tipo NovaExceção foi lançada")
As exceções podem ter informações ou atributos
class EstoqueException(Exception):
def __init__(self, mensagem, codigo_de_erro):
super().__init__(mensagem)
self.codigo_de_erro = codigo_de_erro
def verifique_quantidade(quantidade):
if quantidade < 0:
raise EstoqueException("Quantidade negativa", codigo_de_erro=1)
try:
verifique_quantidade(-10)
except EstoqueException as ee:
print(f"Erro: {ee.codigo_de_erro} {ee}")
Comentários
Comments powered by Disqus