Python __init__ e self
Na aula Classes e Objetos, a Ana criava um produto vazio e depois pendurava os dados um por um: cafe = Produto(), cafe.nome = "Café", cafe.preco = 18.90. Três linhas por produto, e se ela esquecesse uma, o programa quebrava lá na frente com AttributeError. Nesta aula você vai escrever o __init__, o trecho da classe que roda sozinho toda vez que um objeto nasce e já deixa tudo preenchido. Com ele, criar um produto vira uma linha: Produto("Café", 18.90). E vai conhecer o self, a palavrinha que aparece em todo método e que quer dizer "o próprio objeto".
- A escrever o
__init__, para que todo objeto já nasça com os seus dados. - O que é o
self, e por queself.nome = nometem dois "nome". - Quando o
__init__roda, e quem passa oselfpara ele. - A escrever o primeiro método,
mostrar(self), e o quecafe.mostrar()faz por baixo. - Valores padrão (
estoque=0) e argumentos nomeados no__init__. - Os erros reais: o
selfesquecido, argumentos faltando ou sobrando, o__int__digitado errado e oself.esquecido.
A ideia: a ficha com os campos obrigatórios
Na lojinha da Ana, o cadastro de um produto novo é uma ficha com campos obrigatórios: ninguém cadastra um produto sem nome e sem preço. A pessoa do caixa preenche os campos na hora em que a ficha é criada, e a ficha já vai para o arquivo completa.
O __init__ é essa hora do preenchimento. O nome vem de initialize, "inicializar": é o que acontece logo no começo da vida do objeto. E o self (em inglês, "si mesmo", "o próprio") é a ficha que está sendo preenchida agora. Quando o __init__ diz self.nome = nome, ele está dizendo: "no campo nome desta ficha aqui, escreva o nome que me deram".
| Na lojinha | Em Python |
|---|---|
| A ficha em branco, com os campos obrigatórios | A classe com o __init__(self, nome, preco) |
| "Cadastre o Café, que custa 18,90" | Produto("Café", 18.90) |
| A ficha que está sendo preenchida agora | self |
| Escrever no campo "nome" desta ficha | self.nome = nome |
1. O primeiro __init__
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
print(cafe.nome, cafe.preco)
print(pao.nome, pao.preco)
- def __init__
- Uma função escrita dentro da classe (recuada 4 espaços). Uma função que mora numa classe se chama método. O nome tem dois sublinhados antes e dois depois:
__init__. Os programadores leem "dunder init" (de double underscore, "sublinhado duplo"). Nomes assim são especiais: o Python os chama sozinho em certos momentos. - (self, nome, preco)
- Os parâmetros. O primeiro é sempre o
self, o objeto que está nascendo. Depois vêm os dados que quem cria o produto precisa informar. - self.nome = nome
- Lido da direita para a esquerda: pegue o
nomeque chegou como parâmetro e guarde no atributonomedo objeto (self.nome). Os dois "nome" são coisas diferentes: o da direita é o parâmetro, que some quando o__init__termina; o da esquerda é o atributo, que fica guardado no objeto. - self.preco = preco
- O mesmo, para o preço.
- Produto("Café", 18.90)
- Cria um produto passando os dados entre os parênteses, na ordem dos parâmetros depois do
self:"Café"vai paranome,18.90vai parapreco. Repare que ninguém passa oself: o Python passa sozinho.
$ python3 init1.py Café 18.9 Pão de forma 9.5
O resultado é o mesmo da aula anterior, mas cada produto agora é criado numa linha só, e não tem como nascer um produto sem nome ou sem preço: o Python não deixa (você vai ver o erro em Quando dá errado).
2. Quando o __init__ roda?
Você nunca escreve cafe.__init__(…). Quem chama o __init__ é o próprio Python, no momento em que o objeto é criado. Um print dentro dele mostra a hora exata:
class Produto:
def __init__(self, nome, preco):
print(f"__init__ rodou para: {nome}")
self.nome = nome
self.preco = preco
print("Antes de criar")
cafe = Produto("Café", 18.90)
print("Entre um e outro")
pao = Produto("Pão de forma", 9.50)
print("Depois de criar")
$ python3 quando.py Antes de criar __init__ rodou para: Café Entre um e outro __init__ rodou para: Pão de forma Depois de criar
O __init__ roda uma vez para cada objeto criado, bem na linha do Produto(…). Quando você escreve Produto("Café", 18.90), o Python faz três coisas, nesta ordem:
- Cria um objeto
Produtonovo, ainda vazio (uma ficha em branco). - Chama o
__init__passando o objeto novo comoself, mais os seus dados: é como se fosse__init__(objeto_novo, "Café", 18.90). O__init__preenche a ficha. - Entrega o objeto pronto, que vai para a variável
cafe.
3. O self é o próprio objeto
Se o self é o objeto que está nascendo, então ele e o cafe têm de ser a mesma coisa. Dá para conferir pelo endereço da memória (aquele 0x… da aula passada):
class Produto:
def __init__(self, nome, preco):
print("self é:", self)
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
print("cafe é:", cafe)
$ python3 self_mesmo.py self é: <__main__.Produto object at 0x7f933bd90590> cafe é: <__main__.Produto object at 0x7f933bd90590>
O mesmo endereço. Dentro do __init__, o objeto se chama self; fora dele, depois de pronto, ele se chama cafe. São dois nomes para um objeto só. (No seu computador o número será outro, mas os dois vão ser iguais entre si.)
Por que o Python precisa disso? Porque a classe é uma só, e os objetos são muitos. O mesmo __init__ preenche o café, o pão e o leite. O self é a forma de o código da classe dizer "este objeto, aquele que está sendo cuidado agora", sem saber de antemão qual é.
Missão: a classe Aluno recebe o nome e a turma, mas o __init__ não guarda nada (o pass só ocupa o lugar), e o programa dá AttributeError. Troque o pass pelas linhas que guardam o nome e a turma no objeto.
class Aluno:
def __init__(self, nome, turma):
pass
bia = Aluno("Bia", "3º A")
print(bia.nome, bia.turma)
class Aluno:
def __init__(self, nome, turma):
self.nome = nome
self.turma = turma
bia = Aluno("Bia", "3º A")
print(bia.nome, bia.turma)
4. O primeiro método: o objeto sabe fazer coisas
O __init__ é um método que o Python chama sozinho. Você também pode escrever os seus métodos, para o objeto fazer coisas. Todo método recebe o self como primeiro parâmetro, e é por ele que o método enxerga os dados do objeto:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
cafe.mostrar()
pao.mostrar()
Produto.mostrar(cafe)
- def mostrar(self):
- Um método novo, no mesmo recuo do
__init__(dentro da classe), separado dele por uma linha em branco. Ele só precisa doself: tudo de que precisa já está guardado no objeto. - self.nome, self.preco
- Dentro do método, os dados do objeto são lidos pelo
self. O:.2fmostra o preço com duas casas (aula f-strings). - cafe.mostrar()
- Chama o método no objeto
cafe. Os parênteses estão vazios, e mesmo assim o método recebe oself: o Python põe ocafelá. - Produto.mostrar(cafe)
- O que o Python faz por baixo: chama o
mostrarda classe passando ocafecomoself. Ninguém escreve assim no dia a dia, mas é o que explica oself.
$ python3 metodo.py Café: R$ 18.90 Pão de forma: R$ 9.50 Café: R$ 18.90 « Produto.mostrar(cafe): o mesmo que cafe.mostrar()
Esta é a regra que você precisa guardar:
| Você escreve | O Python faz | Dentro do método, self é… |
|---|---|---|
cafe.mostrar() | Produto.mostrar(cafe) | o cafe |
pao.mostrar() | Produto.mostrar(pao) | o pao |
Produto("Café", 18.90) | __init__(novo, "Café", 18.90) | o objeto que está nascendo |
O que vem antes do ponto vira o self. É por isso que o mesmo método mostrar mostra o café numa chamada e o pão na outra. Métodos têm uma aula inteira, a próxima: Atributos e Métodos.
5. Valores padrão e argumentos nomeados
O __init__ é uma função, então tudo o que você aprendeu na aula Valores Padrão e Argumentos Nomeados vale aqui. Um produto recém-cadastrado pode ainda não ter estoque: o estoque vale 0 se ninguém disser nada.
class Produto:
def __init__(self, nome, preco, estoque=0):
self.nome = nome
self.preco = preco
self.estoque = estoque
cafe = Produto("Café", 18.90, 12)
leite = Produto("Leite", 6.50)
pao = Produto(nome="Pão de forma", preco=9.50, estoque=30)
print(cafe.nome, cafe.estoque)
print(leite.nome, leite.estoque)
print(pao.nome, pao.estoque)
$ python3 padrao.py Café 12 Leite 0 « não disse o estoque: ficou o padrão Pão de forma 30 « argumentos nomeados: dá para ler sem decorar a ordem
Nem todo atributo vem de um parâmetro
O __init__ pode criar atributos calculados, ou com um valor inicial fixo. A ficha de um aluno já nasce com a média calculada e com zero faltas:
class Aluno:
def __init__(self, nome, nota1, nota2):
self.nome = nome
self.nota1 = nota1
self.nota2 = nota2
self.media = (nota1 + nota2) / 2
self.faltas = 0
bia = Aluno("Bia", 8.0, 9.5)
print(bia.nome, bia.media, bia.faltas)
print(vars(bia))
$ python3 calculado.py
Bia 8.75 0
{'nome': 'Bia', 'nota1': 8.0, 'nota2': 9.5, 'media': 8.75, 'faltas': 0}
O vars(bia) mostra os cinco atributos que o __init__ criou. Uma boa prática: crie todos os atributos do objeto dentro do __init__, mesmo os que começam vazios ou zerados. Assim, quem lê a classe descobre num lugar só quais dados todo objeto tem.
Formas diferentes de escrever
Os parâmetros não precisam ter o nome dos atributos
class Produto:
def __init__(self, n, p):
self.nome = n
self.preco = p
cafe = Produto("Café", 18.90)
print(cafe.nome, cafe.preco)
print(cafe.n)
$ python3 nomes_dif.py
Café 18.9
Traceback (most recent call last):
File "/home/ana/nomes_dif.py", line 9, in <module>
print(cafe.n)
^^^^^^
AttributeError: 'Produto' object has no attribute 'n'
Funciona: o atributo se chama o que estiver depois de self.. O parâmetro n só existe enquanto o __init__ roda; por isso cafe.n não existe. Mas usar o mesmo nome (self.nome = nome) é o costume, e é mais claro: quem lê sabe na hora qual parâmetro vira qual atributo.
O self é só um nome
class Produto:
def __init__(eu, nome, preco):
eu.nome = nome
eu.preco = preco
cafe = Produto("Café", 18.90)
print(cafe.nome)
$ python3 outro_nome.py Café
Também funciona. Para o Python, o que importa é a posição: o primeiro parâmetro de um método recebe o objeto, chame-se ele como for. Mas todo mundo usa self: os editores de código, os colegas e os tutoriais esperam esse nome. Trocar só confunde.
| Forma | Quando usar |
|---|---|
def __init__(self, nome, preco): + self.nome = nome | Sempre. É o costume, e o mais fácil de ler |
def __init__(self, nome, preco, estoque=0): | Quando um dado tem um valor óbvio para começar |
Produto(nome="Café", preco=18.90) | Quando há muitos parâmetros e a ordem não é óbvia |
def __init__(self, n, p): | Funciona, mas evite: nomes curtos escondem o que cada um é |
def __init__(eu, nome, preco): | Funciona, mas não faça: o nome é sempre self |
Quando dá errado
self no método: takes 0 positional arguments but 1 was given
É o erro mais comum de quem está começando com classes:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar():
print("Um produto da lojinha")
cafe = Produto("Café", 18.90)
cafe.mostrar()
$ python3 sem_self.py
Traceback (most recent call last):
File "/home/ana/sem_self.py", line 11, in <module>
cafe.mostrar()
~~~~~~~~~~~~^^
TypeError: Produto.mostrar() takes 0 positional arguments but 1 was given
Linha por linha:
File "/home/ana/sem_self.py", line 11: o erro estourou na linha 11, a da chamadacafe.mostrar(), e não na linha 6, onde está o engano. O Python só descobre o problema quando tenta usar o método.~~~~~~~~~~~~^^: os tils marcam o que foi chamado (cafe.mostrar) e os^^marcam os parênteses da chamada, onde o problema aconteceu.TypeError: "erro de tipo", usado quando uma função é chamada do jeito errado.Produto.mostrar() takes 0 positional arguments but 1 was given: "oProduto.mostrar()recebe 0 argumentos, mas foi dado 1".
"Mas eu não passei nada!", pensa a Ana. Passou, sim, sem ver: cafe.mostrar() vira Produto.mostrar(cafe). O Python entregou o cafe, e o def mostrar(): não tinha onde recebê-lo. Como consertar: def mostrar(self):. Todo método comum começa com self.
takes 1 positional argument but 2 were given
O erro contrário: quem entendeu que o método recebe o objeto e resolve passá-lo à mão.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
cafe = Produto("Café", 18.90)
cafe.mostrar(cafe)
$ python3 self_arg.py
Traceback (most recent call last):
File "/home/ana/self_arg.py", line 11, in <module>
cafe.mostrar(cafe)
~~~~~~~~~~~~^^^^^^
TypeError: Produto.mostrar() takes 1 positional argument but 2 were given
"Recebe 1 argumento (o self), mas foram dados 2": o cafe que o Python passou sozinho, e o cafe que você escreveu. Como consertar: cafe.mostrar(). O objeto antes do ponto já é o self.
missing 1 required positional argument
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café")
$ python3 falta_arg.py
Traceback (most recent call last):
File "/home/ana/falta_arg.py", line 7, in <module>
cafe = Produto("Café")
~~~~~~~^^^^^^^^
TypeError: Produto.__init__() missing 1 required positional argument: 'preco'
"Falta 1 argumento posicional obrigatório: preco". É o __init__ fazendo o seu trabalho: um produto sem preço não nasce. A mensagem até diz qual campo faltou. Sem nenhum dado, faltam os dois:
$ python3 vazio.py
Traceback (most recent call last):
File "/home/ana/vazio.py", line 7, in <module>
cafe = Produto()
TypeError: Produto.__init__() missing 2 required positional arguments: 'nome' and 'preco'
(Desta vez sem os ~~~^^^: quando a linha inteira é o problema, o Python não sublinha nada.) Como consertar: passe todos os dados obrigatórios, Produto("Café", 18.90), ou dê um valor padrão ao parâmetro que pode faltar.
takes 3 positional arguments but 4 were given
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90, 12)
$ python3 sobra_arg.py
Traceback (most recent call last):
File "/home/ana/sobra_arg.py", line 7, in <module>
cafe = Produto("Café", 18.90, 12)
~~~~~~~^^^^^^^^^^^^^^^^^^^
TypeError: Produto.__init__() takes 3 positional arguments but 4 were given
A Ana passou 3 dados e a mensagem fala em 4. A conta do Python inclui o self: o __init__ recebe 3 (self, nome, preco) e ganhou 4 (o objeto novo, "Café", 18.90 e 12). Sempre que um número dessas mensagens parecer um a mais, lembre do self. Como consertar: tire o dado que sobrou, ou acrescente o parâmetro estoque no __init__.
__int__ ou _init_ no lugar de __init__
class Produto:
def __int__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
$ python3 init_errado.py
Traceback (most recent call last):
File "/home/ana/init_errado.py", line 7, in <module>
cafe = Produto("Café", 18.90)
~~~~~~~^^^^^^^^^^^^^^^
TypeError: Produto() takes no arguments
"Produto() não recebe argumentos". Faltou um i: __int__ (que existe, mas serve para outra coisa, converter o objeto em número inteiro). Como a classe não tem nenhum __init__, o Python acha que ela não aceita dados. Com um sublinhado só de cada lado, _init_, sai exatamente a mesma mensagem. Como consertar: confira o nome letra por letra: dois sublinhados, init, dois sublinhados.
self. na hora de guardar
class Produto:
def __init__(self, nome, preco):
nome = nome
preco = preco
cafe = Produto("Café", 18.90)
print(cafe.nome)
$ python3 sem_self_ponto.py
Traceback (most recent call last):
File "/home/ana/sem_self_ponto.py", line 8, in <module>
print(cafe.nome)
^^^^^^^^^
AttributeError: 'Produto' object has no attribute 'nome'
O objeto nasceu sem problema, mas vazio. nome = nome só copiou o parâmetro para ele mesmo, uma variável comum que some quando o __init__ termina (aula Escopo). Nada foi guardado no objeto. Como consertar: self.nome = nome. Sem o self., o dado não vai para a ficha.
return dentro do __init__
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
return self
cafe = Produto("Café", 18.90)
$ python3 retorna.py
Traceback (most recent call last):
File "/home/ana/retorna.py", line 8, in <module>
cafe = Produto("Café", 18.90)
~~~~~~~^^^^^^^^^^^^^^^
TypeError: __init__() should return None, not 'Produto'
"O __init__() deveria devolver None, e não um Produto". O __init__ só preenche o objeto; quem entrega o objeto pronto é o Python (o passo 3 lá de cima). Como consertar: apague o return. O __init__ nunca devolve nada.
Missão: o método mostrar foi escrito sem o self e ainda tenta usar o nome e o preço. Rode, leia o TypeError e conserte o método para o programa mostrar Café: R$ 18.90.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar():
print(f"{nome}: R$ {preco:.2f}")
cafe = Produto("Café", 18.90)
cafe.mostrar()
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
cafe = Produto("Café", 18.90)
cafe.mostrar()
Repare que eram dois consertos: acrescentar o self nos parênteses e trocar nome por self.nome (e preco por self.preco). Se você consertar só o primeiro, o próximo Rodar mostra outro erro, e o Python até dá a dica:
$ python3 main.py
Traceback (most recent call last):
File "/home/ana/main.py", line 11, in <module>
cafe.mostrar()
~~~~~~~~~~~~^^
File "/home/ana/main.py", line 7, in mostrar
print(f"{nome}: R$ {preco:.2f}")
^^^^
NameError: name 'nome' is not defined. Did you mean: 'self.nome'?
Agora o rastro tem dois trechos File: o erro começou na linha 11 (a chamada) e estourou na linha 7, in mostrar, dentro do método. "O nome nome não está definido. Você quis dizer self.nome?" Dentro do método, nome sozinho não existe: o nome mora no objeto.
Referência rápida
| Escrita | O que faz |
|---|---|
def __init__(self, nome, preco): | Roda sozinho quando um objeto nasce; recebe o objeto (self) e os dados |
self.nome = nome | Guarda o parâmetro nome no atributo nome do objeto |
Produto("Café", 18.90) | Cria o objeto e chama __init__(novo, "Café", 18.90) |
def __init__(self, nome, preco, estoque=0): | Parâmetro com valor padrão |
Produto(nome="Café", preco=18.90) | Argumentos nomeados |
def mostrar(self): | Um método; o self é o objeto antes do ponto |
cafe.mostrar() | O mesmo que Produto.mostrar(cafe) |
| Sintoma | Causa provável |
|---|---|
takes 0 positional arguments but 1 was given | O método não tem self |
takes 1 positional argument but 2 were given | Passou o objeto à mão: cafe.mostrar(cafe) |
missing 1 required positional argument: 'preco' | Faltou um dado no Produto(…) |
takes 3 positional arguments but 4 were given | Sobrou um dado (a conta inclui o self) |
Produto() takes no arguments | O __init__ está com o nome errado (__int__, _init_) ou não existe |
AttributeError logo depois de criar o objeto | Faltou o self. no __init__ |
__init__() should return None | Há um return com valor no __init__ |
Exercícios
1. O nome do método
Complete o nome do método que roda quando o objeto nasce:
2. Guardando no objeto
Complete para guardar o parâmetro turma no objeto:
3. Quando a Ana escreve pao.mostrar(), o que o self vale dentro do mostrar?
4. Qual erro aparece quando um método é escrito como def mostrar(): e chamado com cafe.mostrar()?
5. Com def __init__(self, nome, preco, estoque=0), quais destas criações funcionam?
6. A mensagem diz takes 3 positional arguments but 4 were given, mas a Ana passou só 3 dados. Por quê?
Missão final: a conta bancária que já nasce pronta
Missão: escreva o __init__ da ContaBancaria. Ele recebe o titular e o saldo, e o saldo vale 0 quando ninguém informar. Depois escreva o método mostrar, que imprime Ana: R$ 100.00 (o titular, dois-pontos e o saldo com duas casas). O código de baixo já está pronto: a conta da Ana abre com 100 reais, e a da Bia, sem nada.
class ContaBancaria:
pass # troque por: o __init__ (titular e saldo=0) e o método mostrar
conta_ana = ContaBancaria("Ana", 100)
conta_bia = ContaBancaria("Bia")
conta_ana.mostrar()
conta_bia.mostrar()
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def mostrar(self):
print(f"{self.titular}: R$ {self.saldo:.2f}")
conta_ana = ContaBancaria("Ana", 100)
conta_bia = ContaBancaria("Bia")
conta_ana.mostrar()
conta_bia.mostrar()
Se aparecer ContaBancaria() takes no arguments, o __init__ ainda não existe (ou está com o nome errado). Se aparecer missing 1 required positional argument: 'saldo', faltou o =0.
Resumo
- O
__init__(dois sublinhados de cada lado) roda sozinho toda vez que um objeto nasce, e serve para preencher os dados dele. Produto("Café", 18.90)cria um objeto vazio, chama__init__(novo, "Café", 18.90)e entrega o objeto pronto.selfé o próprio objeto: dentro do__init__, o que está nascendo; num método, o que veio antes do ponto.cafe.mostrar()é o mesmo queProduto.mostrar(cafe).self.nome = nomeguarda o parâmetro no objeto. Sem oself., nada é guardado.- Todo método comum tem
selfcomo primeiro parâmetro. Sem ele:takes 0 positional arguments but 1 was given. - Nas mensagens de argumentos, a conta inclui o
self.Produto() takes no argumentsquer dizer que o__init__não existe ou está com o nome errado. - Valores padrão e argumentos nomeados funcionam no
__init__como em qualquer função. Crie todos os atributos dentro dele.