No tópico passado vimos como criar uma classe e como criar instâncias dessa classe.
Criamos uma classe Cachorro, que serve de molde para a criação de instâncias dessa classe.
>>> class Cachorro:
... patas = 4
... focinho = 1
... rabo = 1
... lingua = 1
... def latir(self):
... print('Au au')
... def correr(self):
... print('Correndo')
... def pegar_a_bolinha(self):
... print('Peguei a bolinha')
Mas quando falamos de métodos uma palavrinha estranha apareceu como parâmetro das funções que foi a palavra "self".
A intenção deste tópico é desmistificar essa palavra e tentar entender a função dela.
Para isso, não achei melhor exemplo do que o exemplo de uma classe que vai servir de molde para criar filas.
Vamos criar uma classe Fila, que vai ter uma lista e alguns métodos como o de entrar na fila e o de sair da fila.
Uma fila serve com pessoas entrando após quem já está na fila e quem for atendido sai da fila. Então quem entrar entra por último e ao sair quem sai é o primeiro da fila. Vamos materializar isso numa classe.
class Fila:
fila = []
def entrar(self, nome):
print(f'{nome} entrou na fila.')
self.fila.append(nome)
def sair(self):
print(f'{self.fila[0]} saiu da fila.')
self.fila.pop(0)
Na classe Fila temos uma lista que está referenciada na variável fila e dois métodos, entrar e sair. O método entrar recebe como parâmetros self e nome, e o método sair recebe self. Iremos entender o self mais pra frente.
O método de lista append adiciona itens ao final da lista. O método pop retira itens da lista, e passando 0 como argumento a lista retira o primeiro elemento da lista.
Usando nossa classe Fila agora:
In [1]: loterica = Fila()
In [2]: loterica.entrar('Thiago')
Thiago entrou na fila.
In [3]: loterica.entrar('Marcelo')
Marcelo entrou na fila.
In [4]: loterica.entrar('Daniele')
Daniele entrou na fila.
In [5]: loterica.fila
Out[5]: ['Thiago', 'Marcelo', 'Daniele']
In [6]: loterica.sair()
Thiago saiu da fila.
In [7]: loterica.fila
Out[7]: ['Marcelo', 'Daniele']
Tudo funcionando conforme o esperado. Até o momento que nós instanciarmos filas distintas.
In [8]: banco = Fila()
In [9]: banco.entrar('Mauro')
Mauro entrou na fila.
In [10]: banco.fila
Out[10]: ['Marcelo', 'Daniele', 'Mauro']
In [11]: loterica.fila
Out[11]: ['Marcelo', 'Daniele', 'Mauro']
Ué, se eu acabei de criar uma fila de banco, e só adicionei o Mauro, porque a fila do banco tem também o Marcelo e a Daniele, além do Mauro? E por que a fila da lotérica tem as 3 mesmas pessoas?
Inclusive, podemos ver que as duas filas são exatamente a mesma fila, a mesma variável, e ocupam o mesmo local na memória. Então estamos com duas instâncias de Fila apontando nossa fila para o mesmo endereço de memória.
In [12]: loterica.fila is banco.fila
Out[12]: True
In [13]: id(loterica.fila)
Out[13]: 140335829375808
In [14]: id(banco.fila)
Out[14]: 140335829375808
Ou seja, confirmamos que as duas filas são exatamente a mesma fila. Mas por que isso acontece? Se o que eu queria era criar duas filas distintas, dessa forma eu não consigo.
O problema é que nossa fila foi definida como atributo da classe e não como atributo da instância.
Observando novamente o código de criação da classe:
class Fila:
fila = []
def entrar(self, nome):
print(f'{nome} entrou na fila.')
self.fila.append(nome)
def sair(self):
print(f'{self.fila[0]} saiu da fila.')
self.fila.pop(0)
Notem que a variável "fila" está no escopo da classe e não dentro de nenhuma função. Então qualquer instância que eu criar dessa classe Fila vai compartilhar a mesma variável fila com as demais instâncias.
O que eu preciso fazer é criar uma nova fila a cada vez que eu instanciar a classe Fila, ou seja, a cada vez que eu criar um exemplar de Fila. Para isso o Python nos ajuda com a função __init__ (o motivo de ela ter esses underlines é assunto para outros tópicos):
Vamos mudar nosso código agora para isto:
class Fila:
def __init__(self):
self.fila = []
def entrar(self, nome):
print(f'{nome} entrou na fila.')
self.fila.append(nome)
def sair(self):
print(f'{self.fila[0]} saiu da fila.')
self.fila.pop(0)
Criando instâncias, exemplos, da classe Fila agora e adicionando pessoas:
In [1]: banco = Fila()
In [2]: loterica = Fila()
In [3]: restaurante = Fila()
In [4]: banco.entrar('Thiago')
Thiago entrou na fila.
In [5]: loterica.entrar('Bastter')
Bastter entrou na fila.
In [6]: restaurante.entrar('Fernanda')
Fernanda entrou na fila.
In [7]: banco.fila
Out[7]: ['Thiago']
In [8]: loterica.fila
Out[8]: ['Bastter']
In [9]: restaurante.fila
Out[9]: ['Fernanda']
Agora temos filas distintas.
In [10]: banco.fila is loterica.fila
Out[10]: False
In [11]: banco.fila is restaurante.fila
Out[11]: False
In [12]: loterica.fila is restaurante.fila
Out[12]: False
E por que agora isso mudou?
O método __init__ fez a mágica pra gente. Ao definir um __init__ dentro de uma classe, estamos inicializando instâncias dessa classe. E da forma que estava antes, sem um __init__, nossos exemplares da classe Fila compartilhavam a mesma fila entre si, pois eu não tinha inicializado uma fila distinta pra cada instância.
E é agora que a palavra self entra. O self representa a instância da classe.
Quando eu criei uma instância de Fila e atribuí à variável banco, o self representa meu banco. Quando criei uma instância de Fila e atribuí à variável loterica, o self representa a loterica, e assim vai.
Então qualquer método de exemplares de uma classe deve receber como primeiro parâmetro uma palavra (que em Python se convencionou que seria a palavra "self" mas pode ser qualquer palavra...já em outras linguagens usam a palavra "this") que vai representar aquela instância específica.
Por isso nos métodos da classe Fila temos linhas como:
self.fila = []
self.fila.append(nome)
self.fila.pop(0)
Que é mais ou menos como dizer algo como
banco.fila = []
banco.fila.append('Thiago')
...
Ou seja, o self representa aquele exemplar da classe. Se não definirmos o self como parâmetro dos métodos de instância das classes o console gerará um erro:
# class Fila:
# def __init__(self):
# self.fila = []
# def entrar(self, nome):
# print(f'{nome} entrou na fila.')
# self.fila.append(nome)
# def sair(self):
# print(f'{self.fila[0]} saiu da fila.')
# self.fila.pop(0)
# Abaixo daqui é código novo
def metodo_qualquer_sem_self():
print('Esta linha não será impressa')
Ao testar agora:
In [1]: teste = Fila()
In [2]: teste.metodo_qualquer_sem_self()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-8368111544af> in <module>
----> 1 teste.metodo_qualquer_sem_self()
TypeError: metodo_qualquer_sem_self() takes 0 positional arguments but 1 was given
O console me diz que o método não recebeu nenhum argumento posicional mas um deve ser enviado, que é justamente o self, que representa a instância.
Sei que essa explicação é algo muito abstrato e eu mesmo demorei muito pra pegar essa ideia, então qualquer dúvida podem perguntar. Ainda vamos explorar muito isso em tópicos mais à frente.