class, __init__ e self: dados e comportamento juntos
Um dicionário guarda os dados de um produto, mas a lógica de aplicar desconto fica solta em outro lugar do código, sem uma conexão formal entre os dois. Uma classe une as duas coisas.
Estudar esta lição interativa →Até aqui, os dados de um produto (nome, preço, estoque) viviam num dicionário solto, e qualquer lógica sobre ele (como aplicar um desconto) ficava numa função separada, sem uma conexão formal entre os dois. Funciona, mas em programas maiores, com muitos "tipos" de coisas diferentes (produtos, clientes, pedidos), fica difícil manter tudo organizado. Uma classe resolve isso: ela junta os dados e o comportamento relacionado a eles num único lugar.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def aplicar_desconto(self, percentual):
return self.preco * (1 - percentual / 100)
caneta = Produto("Caneta", 3.5)
print(caneta.nome)
print(caneta.aplicar_desconto(10))Produto é a classe (a planta); caneta é um objeto (uma casa construída a partir dela).
__init__ é um método especial, chamado automaticamente sempre que um objeto novo é criado a partir da classe; ele é o lugar certo para configurar os dados iniciais daquele objeto específico. self é o primeiro parâmetro de todo método de uma classe, e representa o próprio objeto em que o método está sendo chamado; é através de self.nome, self.preco que cada objeto guarda seus próprios valores, separados dos valores de qualquer outro objeto da mesma classe.
Pense numa classe como a planta baixa de uma casa: ela descreve onde fica cada cômodo, mas não é, ela mesma, uma casa que alguém possa morar. Cada vez que você chama Produto("Caneta", 3.5), é como construir uma casa nova a partir daquela mesma planta: caneta e caderno são duas casas diferentes, cada uma com seus próprios móveis (self.nome, self.preco), mesmo compartilhando a mesma planta original.
Tecnicamente, o primeiro parâmetro de um método poderia ter qualquer nome; o Python passa automaticamente o próprio objeto ali, seja qual for o nome escolhido. Mas toda a comunidade Python usa self por convenção, e fugir disso só confundiria quem lê seu código depois, sem nenhum ganho real.
Se você declarar um método sem self como primeiro parâmetro, como def aplicar_desconto(percentual):, e chamar caneta.aplicar_desconto(10), o Python tenta passar o próprio objeto caneta automaticamente como primeiro argumento, sobrando sem lugar para 10 ir. O erro é algo como TypeError: aplicar_desconto() takes 1 positional argument but 2 were given. A correção quase sempre é a mesma: conferir se self está mesmo lá, como o primeiro parâmetro do método.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def aplicar_desconto(self, percentual):
return self.preco * (1 - percentual / 100)
caneta = Produto("Caneta", 3.5)
caderno = Produto("Caderno", 18.9)
print(caderno.aplicar_desconto(20))Crie o objeto caderno e chame o método de desconto nele.