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".

Você vai aprender
  • A escrever o __init__, para que todo objeto já nasça com os seus dados.
  • O que é o self, e por que self.nome = nome tem dois "nome".
  • Quando o __init__ roda, e quem passa o self para ele.
  • A escrever o primeiro método, mostrar(self), e o que cafe.mostrar() faz por baixo.
  • Valores padrão (estoque=0) e argumentos nomeados no __init__.
  • Os erros reais: o self esquecido, argumentos faltando ou sobrando, o __int__ digitado errado e o self. 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 lojinhaEm Python
A ficha em branco, com os campos obrigatóriosA classe com o __init__(self, nome, preco)
"Cadastre o Café, que custa 18,90"Produto("Café", 18.90)
A ficha que está sendo preenchida agoraself
Escrever no campo "nome" desta fichaself.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 nome que chegou como parâmetro e guarde no atributo nome do 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 para nome, 18.90 vai para preco. Repare que ninguém passa o self: 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:

  1. Cria um objeto Produto novo, ainda vazio (uma ficha em branco).
  2. Chama o __init__ passando o objeto novo como self, mais os seus dados: é como se fosse __init__(objeto_novo, "Café", 18.90). O __init__ preenche a ficha.
  3. 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 do self: 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 :.2f mostra 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 o self: o Python põe o cafe lá.
Produto.mostrar(cafe)
O que o Python faz por baixo: chama o mostrar da classe passando o cafe como self. Ninguém escreve assim no dia a dia, mas é o que explica o self.
$ 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ê escreveO Python fazDentro 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.

FormaQuando usar
def __init__(self, nome, preco): + self.nome = nomeSempre. É 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

Esquecer o 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 chamada cafe.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: "o Produto.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.

Passar o objeto de novo: 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.

Faltou um dado: 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.

Sobrou um dado: 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__.

Nome errado: __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.

Esquecer o 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

EscritaO que faz
def __init__(self, nome, preco):Roda sozinho quando um objeto nasce; recebe o objeto (self) e os dados
self.nome = nomeGuarda 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)
SintomaCausa provável
takes 0 positional arguments but 1 was givenO método não tem self
takes 1 positional argument but 2 were givenPassou 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 givenSobrou um dado (a conta inclui o self)
Produto() takes no argumentsO __init__ está com o nome errado (__int__, _init_) ou não existe
AttributeError logo depois de criar o objetoFaltou o self. no __init__
__init__() should return NoneHá 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:

def (self, nome, preco):

2. Guardando no objeto

Complete para guardar o parâmetro turma no objeto:

= turma

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 que Produto.mostrar(cafe).
  • self.nome = nome guarda o parâmetro no objeto. Sem o self., nada é guardado.
  • Todo método comum tem self como 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 arguments quer 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.