Curso de Python / Criar classes, instâncias e métodos com self

Uma classe define dados e comportamentos relacionados, e uma instância é um objeto individual criado a partir dela. Use essa organização quando o estado e suas regras precisarem ficar juntos; uma função simples ainda pode ser suficiente para um cálculo sem estado próprio.

Relacionar a classe às instâncias e aos métodos

A classe Expense deste exemplo representa uma despesa com nome e valor. Uma chamada como Expense("lunch", 8000) cria uma instância e inicializa seus atributos. Outra chamada pode criar uma despesa diferente, sem substituir a primeira. As duas usam os comportamentos definidos na mesma classe.

Um método é uma função definida na classe que pode receber uma instância e trabalhar com seus atributos. self é o nome convencional do parâmetro que recebe essa instância; não é uma palavra reservada. Ao chamar first.describe() na forma usual, Python fornece o objeto first para esse parâmetro automaticamente.

__init__() prepara o estado inicial depois da criação do objeto. Aqui ele recebe nome e valor, verifica uma regra e armazena os dois atributos. Não passe self manualmente ao chamar Expense(...). A documentação sobre objetos de classe e de instância apresenta essa relação.

Executar duas instâncias de despesa

Salve o programa completo em classes_demo.py, com UTF-8, numa pasta de prática. Execute python classes_demo.py no terminal com o ambiente virtual selecionado, se houver. Em macOS ou Ubuntu sem ambiente virtual, use python3 classes_demo.py quando necessário. Não há dependências externas.

class Expense:
    def __init__(self, name, amount):
        if amount < 0:
            raise ValueError("negative amount")
        self.name = name
        self.amount = amount

    def describe(self):
        return f"{self.name}: {self.amount:,} KRW"

first = Expense("lunch", 8000)
second = Expense("bus", 2500)
print(first.describe())
print(second.describe())
print(first.amount + second.amount)

A saída esperada é:

lunch: 8,000 KRW
bus: 2,500 KRW
10500

lunch e bus são nomes de exemplo, correspondentes a almoço e ônibus. A primeira instância guarda o valor 8000 e a segunda, 2500. Cada chamada a describe() usa os atributos do objeto que a recebeu e devolve uma string; o código externo a imprime. A última linha soma diretamente os dois atributos numéricos e produz 10500.

O especificador :, separa milhares por vírgula, e o texto KRW identifica won sul-coreano. Esse método apenas apresenta o valor com um rótulo; não converte moeda nem aplica regras de formatação brasileira. A diferença entre retornar uma descrição e imprimi-la segue a mesma lógica das funções com valores de retorno.

Acompanhar o estado e os limites da validação

No estado inicial do programa, os nomes e atributos têm esta relação:

Nome Instância Atributos iniciais
first Primeiro objeto Expense. name="lunch", amount=8000.
second Outro objeto Expense. name="bus", amount=2500.

Alterar first.amount não altera second.amount neste exemplo: são atributos de instâncias distintas. Já atribuir alias = first daria um segundo nome ao mesmo objeto, e mudanças feitas por esse alias seriam vistas por first. A prática de aliases e objetos mutáveis ajuda a separar um novo objeto de apenas um novo nome.

A condição em __init__() só rejeita um valor quando a comparação amount < 0 é verdadeira. Um texto numérico não é convertido automaticamente e causa TypeError na comparação com zero. Um booleano ou um número fracionário não negativo pode passar por essa verificação. Se a aplicação exigir inteiros comuns, nomes não vazios ou outra escala, precisa de verificações adicionais.

A verificação inicial também não controla toda alteração posterior. Uma atribuição direta como first.amount = -1 é aceita pela implementação apresentada; o método de descrição poderá mostrar o valor negativo. Não confunda “foi validado na inicialização” com “não pode se tornar inválido”. A referência de inicialização de instâncias explica o papel de __init__.

Testar independência, chamadas e alterações indevidas

  1. Depois da criação de second e antes das impressões, acrescente first.amount = 9000. Execute e confira lunch: 9,000 KRW, bus: 2,500 KRW e 11500. A segunda despesa deve permanecer em 2500. Retire a linha para restaurar o estado inicial.
  2. Troque somente 8000 por -1 na criação de first. A inicialização deve levantar ValueError: negative amount antes das impressões. Restaure 8000 após o teste.
  3. Ao fim do programa, acrescente print(Expense.describe(first) == first.describe()). O resultado adicional deve ser True: na primeira forma, você fornece explicitamente a instância; na segunda, ela é associada ao método. Remova a impressão depois de comparar.
  4. Para observar o limite de validação, acrescente temporariamente first.amount = -1 depois das duas criações. A execução continua e a primeira descrição mostra o valor negativo. Retire essa atribuição e confirme novamente as três linhas originais.

Uma grafia errada como first.amout = 9000 pode criar um atributo novo, sem corrigir first.amount. Verifique os atributos usados e os resultados observados, em vez de supor que toda atribuição bem-sucedida atingiu o campo pretendido.

Mantenha a responsabilidade de Expense focada nos dados e nas regras da despesa. Acrescentar entrada interativa, armazenamento em arquivo e envio pela rede à mesma classe pode tornar seus usos e testes mais difíceis. Recursos como propriedades, dataclasses e anotações de tipo podem ajudar em problemas específicos, mas não substituem automaticamente as verificações que o programa precisa impor em tempo de execução.