Classes e Objetos

Neste capítulo iremos visitar uma breve introdução ao paradigma de orientação a objetos, ou seja, cobriremos apenas a pontinha do iceberg sobre orientação a objetos. É importante ressaltar antes de começarmos que iremos apenas ver conceitos bem básicos aqui neste capítulo e que este é um assunto que poderia cobrir um livro inteiro.

O que iremos ver neste capítulo

  • O que é um objeto
  • Como declarar uma classe
  • Como instanciar e utilizar um objeto

O que não veremos neste capítulo

  • Herança
  • Sobrecarga
  • Polimorfismo
  • Classes Abstratas
  • Encapsulamento

Resalto aqui o que não iremos cobrir para que o aluno fique ciente que existe muito mais a aprender nete paradigma de programação.

O que é Orientação a Objetos

O paradigma de orientação a objetos não é novo, já nos anos 1960 falava-se sobre alguns destes conceitos no MIT. Este paradigma de programação é bastante popular nos dias de hoje e é importante que um programador o conheça. Um objeto representa usualemnte uma abstração de algum objeto do mundo real. Portanto, quando falamos de um objeto, nos referimos a uma unidade auto contida, esta unidade, ou seja, o objeto vai possuir todos os dados e funcionalidades relacionadas com o objeto em questão. Alguns exemplos de objetos que podemos conceber: uma pessoa, um aluno, um Carro, uma TV, uma fila de tratamento de prioridades, um robo, e assim por diante.

Até o momento, temos trabalhados com os dados e as funções separadamente em nossos programas, ou seja, declaramos as variaveis em algum lugar e as funções em outro, e ambas são independentes uma das outras. Quando falamos de um objeto a ideia é que o objeto contenha seus proprios dados e suas propias funções, facilitando assim a organização e reutilização de código. Por exemplo, se criarmos um objeto que represente uma abstração de um carro, este objeto vai ter como dados informações a respeito deste carro, por exemplo, o modelo, a cor, quantidade de combustível, etc.. Além disto este objeto vai conter funções que este carro pode fazer, por exemplo, acelerar, freiar, abastecer, etc... Portanto toda vez que quisermos criar um objeto que represente um carro, iremos criar uma nova instancia do objeto carro, uma instancia é uma unidade de um objeto criada na memória do computador, e todas as instancias de um tipo de objeto vâo ter os mesmos tipos de dados e funções.

Para o python, assim como para várias outras linguagens, para trabalharmos com objetos é necessário fornecer a "planta" de como este objeto deve ser construido, assim como na construção de uma casa. Esta "planta" é chamada de classe. Portanto, uma classe nada mais é que as especificações de como construir um objeto no python. Uma vez que construimos um objeto no python, temos uma isntancia deste objeto.

Definindo uma classe

Para definirmos uma classe no python utilizamos a palavra reservada Class seguido do nome da classe(esse tbm vai ser o nome ou tipo do objeto). Por exemplo, se quisermos criar uma classe que represente um Carro no python, iremos escrever da seguinte forma:

In [1]:
class Carro():
  File "<ipython-input-1-7939d78b97fa>", line 1
    class Carro():
                  ^
SyntaxError: unexpected EOF while parsing

Observe que a delcaração de uma classe é muito parecida com a de uma função, mas, em vez de utilizarmos def, utilizamos class. No exemplo anterior é gerado um erro pois o python espera que exista algo dentro da classe.

Nosso primeiro passo vai ser definir como vai ser construido(inicializado) este objeto, e quais vão ser os atributos (variáveis que o mesmo vai guardar). Para indicar como o objeto deve ser construido em uma classe definimos dentro dela a função init, esta função diz para o python como o objeto deve ser criado.

In [2]:
class Carro():
    def __init__(self):
        self.modelo ="Fusca"
        

Como você pode ver a classe carro agora tem a função init, você notou que esta função recebe um parametro chamado self? Este é um parametro especial, pois nele que armazenamos/recuperamos os dados de uma classe. Além disto cada instância de um objeto vai ter seu próprio self, pois cada instância pode ter valores diferentes. No exemplo anterior estamos definindo que o modelo do carro é um fusca. Observe que é a partir do self que fazemos esta atribuição.

Ao rodarmos o código anterior nada acontece. Isso é pq só definimos como o objeto carro deve ser (por enquanto só com o modelo). Uma vez que temos a classe do objeto, podemos instanciar quantos objetos deste tipo quisermos. Para criarmos uma instancia da nossa classe é muito parecido com a criação de variaveis. Damos um nome para a variável e dizemos que queremos que ela seja do tipo da classe que definimos.

Exemplo:

In [3]:
class Carro():
    def __init__(self):
        self.modelo ="Fusca"


fusquinhaPreto = Carro()
print fusquinhaPreto.modelo

corola = Carro()
print corola.modelo
Fusca
Fusca

Observe que na linha fusquinhaPreto = Carro() estamos criando uma instancia deste objeto, na na linha a seguir imprimimos o modelo deste carro, que é um fusca. Entretanto, na sequencia criamos um corola, mas este quando imprimimos o seu modelo, apresneta que é um fusca. Isto se deve a forma como definimos o nosso init da classe. Sempre associamos fusca ao modelo. Mas podemos mudar isso, mudando esta atributo de fusca para corola. Exemplo

In [4]:
class Carro():
    def __init__(self):
        self.modelo ="Fusca"


fusquinhaPreto = Carro()
print fusquinhaPreto.modelo

corola = Carro()
corola.modelo = "corola"
print corola.modelo
Fusca
corola

Observe que agora o modelo foi impresso corretamete, então, podemos mudar o atributo de um objeto depois que ele foi criado. Entretanto, esta não é a melhor forma de resolver este problema, eu mostrei desta forma para você saber que pode mudar os atributos de um objeto depois que ele foi criado.

Uma forma mais interessante de dizer qual é o modelo do carro é indormar o modelo ao criar o objeto. Para isso é só passarmos isso por parâmetro na função init Exemplo:

In [5]:
class Carro():
    def __init__(self, modelo):
        self.modelo = modelo


fusquinhaPreto = Carro("fusca")
print fusquinhaPreto.modelo

corola = Carro("corola")
print corola.modelo
fusca
corola

O que fizemos de diferemte? Primeiro que na função init estamos passando além do self o parâmetro modelo e este parâmetro é associado com o atributo associado a instancia do objeto. E segundo, ao construirmos o objeto informamos qual é o modelo do mesmo. Bem mais simples né? E agora podemos criar carros de diversos modelos.

Mas e se quisermos adicionar mais atributos a cada objeto de um carro, por exemplo, cor, numero de passageiros, etc? Bom, é só seguir a mesma lógica: Exemplo:

In [6]:
class Carro():
    def __init__(self, modelo, cor, numeroDePassageiros):
        self.modelo = modelo
        self.cor = cor
        self.numeroDePassageiros = int(numeroDePassageiros)

fusquinhaPreto = Carro("fusca","preto",4)
print fusquinhaPreto.modelo + " " + fusquinhaPreto.cor + " " + str(fusquinhaPreto.numeroDePassageiros) 

corola = Carro("corola","prata", 5)
print corola.modelo + " " + corola.cor + " " + str(corola.numeroDePassageiros)
fusca preto 4
corola prata 5

Observe que agora ao criar um carro informamos todas as informações e podemos acessar e imprimir cada uma delas separadamente. Entretanto, você deve ter notado, é meio chato ter que repetir os mesmos comandos para imprimir o fusca e o corola, e a única coisa que muda entre as duas impressões é o nome do objeto.

Lembra que anteriormente falamos que um objeto pode ter funções? Então, podemos criar uma função dentro da classe que imprime todo o objeto, facilitando o reuso desta tarefa. Para fazer isso basicamente criamos uma função de imprimir dentro da classe carro. Exemplo:

In [7]:
class Carro():
    def __init__(self, modelo, cor, numeroDePassageiros):
        self.modelo = modelo
        self.cor = cor
        self.numeroDePassageiros = int(numeroDePassageiros)
    
    def imprime(self):
        print self.modelo + " " + self.cor + " " + str(self.numeroDePassageiros) 

fusquinhaPreto = Carro("fusca","preto",4)
fusquinhaPreto.imprime()

corola = Carro("corola","prata", 5)
corola.imprime()
fusca preto 4
corola prata 5

Observe que agora para imprimirmos um objeto do tipo carro só precisamos chamar a função imprime. Bem mais facil certo? É importante resaltar que a função imprime na classe carro recebe o parâmetro self tambem, e é de lá que saem os valores da isntancia de cada instância de carro diferente.

Mas porque não posso simplemsnte fazer isso? print fuscaPreto, como já fazemos com listas e dicionários. Não podemos pois o python não sabe como imprimir este nosso objeto no terminal. Observe o que acontece se tentarmos fazer isso:

In [8]:
class Carro():
    def __init__(self, modelo, cor, numeroDePassageiros):
        self.modelo = modelo
        self.cor = cor
        self.numeroDePassageiros = int(numeroDePassageiros)
    
    def imprime(self):
        print self.modelo + " " + self.cor + " " + str(self.numeroDePassageiros) 

fusquinhaPreto = Carro("fusca","preto",4)
print fusquinhaPreto

corola = Carro("corola","prata", 5)
print corola
<__main__.Carro instance at 0x7f1ab8792638>
<__main__.Carro instance at 0x7f1ab8792bd8>

O python imprime que é uma instancia de carro, em vez de imprimir seus dados. Isso pq o python não sabe como imprimir esta nossa classe, mas podemos ensina-lo. Para isso foi definido que o método __ str __ . Este método retorna uma string para o print dizendo como este objeto deve ser impresso. Vamos mudar nosso imprime para se adecuar a isso.

In [9]:
class Carro():
    def __init__(self, modelo, cor, numeroDePassageiros):
        self.modelo = modelo
        self.cor = cor
        self.numeroDePassageiros = int(numeroDePassageiros)
    
    def __str__(self):
        return self.modelo + " " + self.cor + " " + str(self.numeroDePassageiros) 

fusquinhaPreto = Carro("fusca","preto",4)
print fusquinhaPreto

corola = Carro("corola","prata", 5)
print corola
fusca preto 4
corola prata 5

As unicas coisas que mudamos foi que agora em vez de imprime o método se chama __str__ e que em vez de ter um print dentro dele o mesmo retorna uma string. Pronto agora o python sabe como imprimir nossa classe. =)

Nós podemos criar inumeras funções para um objeto, por exemplo, vamos criar agora funções para acelerar e freiar o carro, o que vai mudar a velocidade do carro.

In [10]:
class Carro():
    def __init__(self, modelo, cor, numeroDePassageiros):
        self.modelo = modelo
        self.cor = cor
        self.numeroDePassageiros = int(numeroDePassageiros)
        self.velocidade = 0
        
    def __str__(self):
        return self.modelo + " " + self.cor + " " + str(self.numeroDePassageiros)
    
    def acelerar(self):
        self.velocidade = self.velocidade + 10
    
    def freiar(self):
        self.velocidade = self.velocidade - 8
    
    def mostrarVelocidade(self):
        print self.velocidade
        

corola = Carro("corola","prata", 5)
corola.mostrarVelocidade()
corola.acelerar()
corola.mostrarVelocidade()
corola.acelerar()
corola.acelerar()
corola.mostrarVelocidade()
corola.freiar()
corola.mostrarVelocidade()
0
10
30
22

Observe que agora podemos controlar a velocidade do carro acelerando ou freiando :). Este é o básico do básico que precisamos saber para trabalharmos com objetos. E é importante ressaltar que você precisará de muito mais estudo para dominar este parâdigma de programação.

Agora vamos fazer alguns exemplos utilizando dicionarios e listas para armazenar objetos, e desta forma armazenar inúmeras instâncias de objetos. Para mudar um pouco vamos criar um cadastro de alunos.

Primeiro criamos a classe aluno:

In [11]:
class Aluno():
    def __init__(self,nome,idade,notaGA,notaGB):
        self.nome = nome
        self.idade = idade
        self.notaGA = float(notaGA)
        self.notaGB = float(notaGB)
    
    def notaFinal(self):
        return (self.notaGA + 2 * self.notaGB)/3  
    
    def aprovado(self):
        if self.notaFinal() >= 6:
            return True
        return False
        
    def __str__(self):
        apr = "Nao"
        if self.aprovado():
            apr = "Sim"
        return "nome: " + self.nome + " idade: " + str(self.idade) + " Aprovado : " + apr
        
        
    

Observe que o objeto aluno guarda o nom, idade e as notas do aluno. Como métodos temos um q calcula a nota final, outro que utiliza este método para dizer se o aluno esta aprovado e finalmente o método que diz para o python como imprimir um aluno no terminal.

Vamos testar se nossa classe funciona criando uma instancia de aluno.

In [12]:
class Aluno():
    def __init__(self,nome,idade,notaGA,notaGB):
        self.nome = nome
        self.idade = idade
        self.notaGA = float(notaGA)
        self.notaGB = float(notaGB)
    
    def notaFinal(self):
        return (self.notaGA + 2 * self.notaGB)/3  
    
    def aprovado(self):
        if self.notaFinal() >= 6:
            return True
        return False
        
    def __str__(self):
        apr = "Nao"
        if self.aprovado():
            apr = "Sim"
        return "nome: " + self.nome + " idade: " + str(self.idade) + " Aprovado : " + apr
    
aluno1 = Aluno("Felipe", 19, 6.8, 8.0)
print aluno1
nome: Felipe idade: 19 Aprovado : Sim

Legal, conseguimos criar um único aluno e ele, felizmente, esta aprovado. Mas e se quisermos guardar inímeros alunos? Bom, podemos fazer isso utilizando uma lista ou dicionário. Vamos começar com uma lista:

In [13]:
class Aluno():
    def __init__(self,nome,idade,notaGA,notaGB):
        self.nome = nome
        self.idade = int(idade)
        self.notaGA = float(notaGA)
        self.notaGB = float(notaGB)
    
    def notaFinal(self):
        return (self.notaGA + 2 * self.notaGB)/3  
    
    def aprovado(self):
        if self.notaFinal() >= 6:
            return True
        return False
        
    def __str__(self):
        apr = "Nao"
        if self.aprovado():
            apr = "Sim"
        return "nome: " + self.nome + " idade: " + str(self.idade) + " Aprovado : " + apr
    
alunos = []
#cadastra os alunos como objetos na lista
while True:
    nome = raw_input("digite o nome do aluno ou sair")
    if nome == "sair":
        break
    idade = int(raw_input("digite a idade do aluno"))
    ga = float(raw_input("digite o ga do aluno"))
    gb = float(raw_input("digite o gb do aluno"))
    novoAluno = Aluno(nome,idade,ga,gb)
    alunos.append(novoAluno)
    
#Mostra os alunos na lista
for aluno in alunos:
    print aluno
digite o nome do aluno ou sairJoao
digite a idade do aluno12
digite o ga do aluno4
digite o gb do aluno8
digite o nome do aluno ou sairMaria
digite a idade do aluno10
digite o ga do aluno7
digite o gb do aluno8
digite o nome do aluno ou sairFulana
digite a idade do aluno4
digite o ga do aluno7
digite o gb do aluno3
digite o nome do aluno ou sairsair
nome: Joao idade: 12 Aprovado : Sim
nome: Maria idade: 10 Aprovado : Sim
nome: Fulana idade: 4 Aprovado : Nao

Observe que com objetos fica bem mais simples a manipulação. E o uso com listas fica muito interessante. Podemos também utilizar um dicionário, onde a chave pode ser o nome do aluno e o valor o objeto do aluno.

Exemplo:

In [14]:
class Aluno():
    def __init__(self,nome,idade,notaGA,notaGB):
        self.nome = nome
        self.idade = int(idade)
        self.notaGA = float(notaGA)
        self.notaGB = float(notaGB)
    
    def notaFinal(self):
        return (self.notaGA + 2 * self.notaGB)/3  
    
    def aprovado(self):
        if self.notaFinal() >= 6:
            return True
        return False
        
    def __str__(self):
        apr = "Nao"
        if self.aprovado():
            apr = "Sim"
        return "nome: " + self.nome + " idade: " + str(self.idade) + " Aprovado : " + apr
    
alunos = {}
#cadastra os alunos como objetos no dicionario
while True:
    nome = raw_input("digite o nome do aluno ou sair")
    if nome == "sair":
        break
    idade = int(raw_input("digite a idade do aluno"))
    ga = float(raw_input("digite o ga do aluno"))
    gb = float(raw_input("digite o gb do aluno"))
    novoAluno = Aluno(nome,idade,ga,gb)
    alunos[nome] = novoAluno
    
#consulta o aluno no dicionario ate sair.
while True:
    nome = raw_input("digite o nome do aluno a consultar ou sair")
    if nome == "sair":
        break
    elif nome in alunos:
        print alunos[nome]
    else:
        print "aluno não cadastrado"
digite o nome do aluno ou sairMaria
digite a idade do aluno15
digite o ga do aluno10
digite o gb do aluno5
digite o nome do aluno ou sairAna
digite a idade do aluno16
digite o ga do aluno8
digite o gb do aluno7
digite o nome do aluno ou sairFernando
digite a idade do aluno4
digite o ga do aluno7
digite o gb do aluno6
digite o nome do aluno ou sairsair
digite o nome do aluno a consultar ou sairgusatavo
aluno não cadastrado
digite o nome do aluno a consultar ou sairFernando
nome: Fernando idade: 4 Aprovado : Sim
digite o nome do aluno a consultar ou sairAna
nome: Ana idade: 16 Aprovado : Sim
digite o nome do aluno a consultar ou sairsair

Pudemos ver que um objeto é uma forma de guardar informações e funções para um mesmo objeto de forma que tratamos este conjunto de dados e funcionalidades como uma unidade. Isso facilita muito na implementação e resolução de alguns problemas.

Lembre que este capítulo apenas apresenta uma forma bem primitiva de como funciona a orientação a objetos e que você tem muito a aprender ainda. =).

In [ ]: