Em Python, métodos são funções definidas dentro de uma classe que operam em instâncias ou na própria classe. Dependendo de como são declarados, os métodos podem ter acesso a diferentes níveis de dados: instância, classe ou nenhum. Compreender essas diferenças é essencial para escrever código orientado a objetos limpo e eficiente.

Nesta aula, abordaremos os três tipos principais de métodos: métodos de instância (os mais comuns), métodos de classe (marcados com @classmethod) e métodos estáticos (marcados com @staticmethod). Veremos como cada um funciona, quando utilizá-los e exemplos práticos.

Métodos de instância

Métodos de instância são os métodos mais comuns em Python. Eles recebem como primeiro parâmetro self, que representa a instância atual da classe. Através de self, é possível acessar e modificar atributos da instância, bem como chamar outros métodos da instância.

Esses métodos são chamados a partir de uma instância da classe e podem acessar tanto atributos de instância quanto atributos de classe (através de self.__class__). São ideais para comportamentos que dependem do estado específico de cada objeto.

class Pessoa:
    especie = "Homo sapiens"

    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def apresentar(self):
        return f"Olá, meu nome é {self.nome} e tenho {self.idade} anos."

    def aniversario(self):
        self.idade += 1

# Uso
p = Pessoa("Alice", 30)
print(p.apresentar())  # Olá, meu nome é Alice e tenho 30 anos.
p.aniversario()
print(p.idade)  # 31

No exemplo acima, apresentar e aniversario são métodos de instância. Eles usam self para acessar os atributos da instância (nome, idade).

@classmethod

Métodos de classe são declarados com o decorador @classmethod e recebem como primeiro parâmetro cls, que representa a própria classe (não uma instância). Eles podem acessar e modificar atributos de classe e chamar outros métodos de classe, mas não têm acesso a atributos de instância (a menos que recebam uma instância como argumento).

Métodos de classe são úteis para criar fábricas (factory methods) que retornam instâncias da classe usando lógica alternativa, ou para operações que envolvem a classe como um todo.

class Pessoa:
    especie = "Homo sapiens"

    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    @classmethod
    def criar_anonimo(cls):
        return cls("Anônimo", 0)

    @classmethod
    def mudar_especie(cls, nova_especie):
        cls.especie = nova_especie

# Uso
p = Pessoa.criar_anonimo()
print(p.nome)  # Anônimo

Pessoa.mudar_especie("Homo sapiens sapiens")
print(Pessoa.especie)  # Homo sapiens sapiens

Note que criar_anonimo usa cls para instanciar a classe, e mudar_especie altera o atributo de classe especie.

@staticmethod

Métodos estáticos são declarados com o decorador @staticmethod e não recebem nenhum parâmetro especial (self ou cls). Eles se comportam como funções normais, mas pertencem ao namespace da classe. Não podem acessar diretamente atributos de instância ou de classe (a menos que recebam explicitamente como argumentos).

Métodos estáticos são usados para funções utilitárias que estão relacionadas ao conceito da classe, mas não dependem do estado da instância ou da classe.

class Calculadora:
    @staticmethod
    def somar(a, b):
        return a + b

    @staticmethod
    def subtrair(a, b):
        return a - b

# Uso
resultado = Calculadora.somar(10, 5)
print(resultado)  # 15

# Também pode ser chamado a partir de uma instância
calc = Calculadora()
print(calc.subtrair(10, 5))  # 5

No exemplo, somar e subtrair são funções independentes que apenas realizam operações matemáticas, sem depender de nenhum estado interno.

Quando usar cada um

A escolha entre método de instância, método de classe e método estático depende do tipo de acesso necessário:

  • Método de instância: Use quando o método precisa acessar ou modificar o estado de uma instância específica (atributos de instância). É o tipo mais comum.
  • Método de classe: Use quando o método precisa acessar ou modificar atributos de classe, ou quando precisa criar instâncias da classe de forma alternativa (factory). Também útil para polimorfismo em herança.
  • Método estático: Use quando o método não precisa de acesso a instância nem classe, mas faz sentido agrupar a função dentro da classe por questões de organização ou coesão. Exemplos: funções de validação, conversão, operações matemáticas.

Em geral, se você não precisa de self ou cls, opte por @staticmethod. Se precisa de cls (para acessar a classe), use @classmethod. Caso contrário, um método de instância comum é a escolha correta.

Boas práticas

  • Não abuse de métodos estáticos se a função poderia ser uma função independente no módulo. Use-os apenas quando fizer sentido semântico pertencer à classe.
  • Métodos de classe são úteis para implementar construtores alternativos (ex.: from_string, from_dict).
  • Lembre-se de que métodos de classe podem ser sobrescritos em subclasses, enquanto métodos estáticos se comportam como funções regulares (não há polimorfismo).
  • Evite usar @staticmethod quando o método poderia ser um método de instância ou de classe – isso torna o código menos flexível.

Referências

Exercícios

  1. Crie uma classe ContaBancaria com atributos titular (instância) e saldo (instância). Adicione métodos de instância depositar e sacar que modifiquem o saldo.

    ✓ Resposta:
    class ContaBancaria:
        def __init__(self, titular, saldo=0):
            self.titular = titular
            self.saldo = saldo
    
        def depositar(self, valor):
            self.saldo += valor
    
        def sacar(self, valor):
            if valor <= self.saldo:
                self.saldo -= valor
            else:
                print("Saldo insuficiente")
    
    # Teste
    conta = ContaBancaria("João", 100)
    conta.depositar(50)
    conta.sacar(30)
    print(conta.saldo)  # 120
  2. Adicione um método de classe criar_conta_vip à classe ContaBancaria que cria uma conta com saldo inicial de 1000 e titular fornecido.

    ✓ Resposta:
    class ContaBancaria:
        def __init__(self, titular, saldo=0):
            self.titular = titular
            self.saldo = saldo
    
        @classmethod
        def criar_conta_vip(cls, titular):
            return cls(titular, saldo=1000)
    
    # Teste
    conta_vip = ContaBancaria.criar_conta_vip("Maria")
    print(conta_vip.saldo)  # 1000
  3. Crie um método estático validar_cpf na classe Pessoa que recebe uma string e retorna True se tiver 11 dígitos, False caso contrário. Não precisa de validação real.

    ✓ Resposta:
    class Pessoa:
        def __init__(self, nome, cpf):
            self.nome = nome
            self.cpf = cpf
    
        @staticmethod
        def validar_cpf(cpf):
            return len(cpf) == 11 and cpf.isdigit()
    
    # Teste
    print(Pessoa.validar_cpf("12345678901"))  # True
    print(Pessoa.validar_cpf("123"))  # False
  4. Explique por que o método criar_conta_vip foi implementado como método de classe em vez de método estático.

    ✓ Resposta:Porque ele precisa criar uma nova instância da classe usando cls. Se fosse um método estático, teríamos que nomear explicitamente a classe ContaBancaria, o que quebra a herança: se uma subclasse herdar de ContaBancaria, o método estático ainda criaria objetos da classe pai, enquanto o método de classe usa cls que se refere à subclasse corretamente.
  5. Refatore o código abaixo para usar o tipo de método mais adequado: a classe Matematica tem um método calcular_media que recebe uma lista de números e retorna a média. O método não usa self nem cls.

    class Matematica:
        def calcular_media(self, numeros):
            return sum(numeros) / len(numeros)

    ✓ Resposta:O método não usa self nem cls, portanto deve ser um método estático. Refatoração:
    class Matematica:
        @staticmethod
        def calcular_media(numeros):
            return sum(numeros) / len(numeros)