Voltar ao Glossario
Glossario Python

Classe em Python: O que É e Como Funciona | Python Brasil

Aprenda o que é classe em Python: atributos, métodos, herança, dunder methods, metaclasses, dataclasses e muito mais. Guia completo com exemplos.

O que é uma Classe?

Uma classe é um molde (ou blueprint) para criar objetos em Python. Ela define atributos (dados) e métodos (comportamentos) que os objetos criados a partir dela terão. Classes são a base da Programação Orientada a Objetos (POO) e, em Python, absolutamente tudo é um objeto — inclusive inteiros, strings e funções.

Entender classes em profundidade é fundamental para escrever código Python idiomático, legível e fácil de manter.

Atributos de classe vs atributos de instância

Um dos pontos mais importantes — e fonte de muitos bugs — é a diferença entre atributos de classe e atributos de instância.

class Contador:
    total = 0  # Atributo de classe: compartilhado por TODAS as instâncias

    def __init__(self, nome):
        self.nome = nome       # Atributo de instância: exclusivo de cada objeto
        Contador.total += 1

c1 = Contador("c1")
c2 = Contador("c2")

print(Contador.total)  # 2 — acessado pela classe
print(c1.total)        # 2 — também acessado pela instância (leitura)
print(c1.nome)         # c1 — exclusivo desta instância

# Cuidado: atribuir via instância cria um atributo LOCAL, não altera o de classe
c1.total = 99
print(c1.total)        # 99 — atributo local da instância c1
print(Contador.total)  # 2 — atributo de classe não foi alterado

Métodos de instância, de classe e estáticos

Python oferece três tipos de métodos, cada um com um papel distinto:

class Produto:
    desconto_padrao = 0.10  # Atributo de classe

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

    # Método de instância: recebe 'self', acessa atributos do objeto
    def preco_com_desconto(self):
        return self.preco * (1 - self.desconto_padrao)

    # Método de classe: recebe 'cls', acessa/modifica a classe
    @classmethod
    def alterar_desconto(cls, novo_desconto):
        cls.desconto_padrao = novo_desconto

    # Método estático: não recebe 'self' nem 'cls', é apenas uma função relacionada
    @staticmethod
    def validar_preco(preco):
        return preco > 0

p = Produto("Notebook", 3000)
print(p.preco_com_desconto())     # 2700.0
Produto.alterar_desconto(0.15)
print(p.preco_com_desconto())     # 2550.0
print(Produto.validar_preco(-1)) # False

Use @classmethod quando precisar de acesso à classe em si (por exemplo, fábricas alternativas). Use @staticmethod para utilitários relacionados à classe mas que não precisam acessar estado nenhum.

Métodos mágicos (dunder methods)

Os métodos que começam e terminam com dois underscores são chamados de dunder methods (de “double underscore”) ou métodos mágicos. Eles definem como seus objetos se comportam com operadores e funções nativas do Python.

class Vetor:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vetor({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __add__(self, outro):
        return Vetor(self.x + outro.x, self.y + outro.y)

    def __mul__(self, escalar):
        return Vetor(self.x * escalar, self.y * escalar)

    def __eq__(self, outro):
        return self.x == outro.x and self.y == outro.y

    def __len__(self):
        return int((self.x**2 + self.y**2) ** 0.5)

    def __bool__(self):
        return self.x != 0 or self.y != 0

    def __iter__(self):
        yield self.x
        yield self.y

v1 = Vetor(1, 2)
v2 = Vetor(3, 4)

print(v1 + v2)      # (4, 6)
print(v1 * 3)       # (3, 6)
print(v1 == v2)     # False
print(len(v2))      # 5
print(list(v1))     # [1, 2]

Outros dunder methods importantes: __lt__, __le__, __gt__, __ge__ (comparações), __hash__ (uso em dicionários e sets), __contains__ (operador in), __getitem__ e __setitem__ (acesso por índice), __call__ (tornar objeto chamável como função), __enter__ e __exit__ (context managers).

Otimizando memória com slots

Por padrão, cada instância armazena seus atributos em um dicionário interno (__dict__), o que consome memória. Com __slots__, você define explicitamente quais atributos são permitidos, reduzindo o consumo de memória significativamente em classes com muitas instâncias.

class PontoSemSlots:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class PontoComSlots:
    __slots__ = ['x', 'y']

    def __init__(self, x, y):
        self.x = x
        self.y = y

# PontoComSlots usa menos memória e tem acesso de atributos mais rápido
p = PontoComSlots(1, 2)
# p.z = 3  # AttributeError — apenas x e y são permitidos

Classes abstratas com ABC

O módulo abc (Abstract Base Classes) permite definir interfaces que subclasses são obrigadas a implementar. Isso é útil para garantir um contrato entre a classe base e suas filhas.

from abc import ABC, abstractmethod

class Forma(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

    @abstractmethod
    def perimetro(self) -> float:
        pass

    def descricao(self):
        return f"Área: {self.area():.2f}, Perímetro: {self.perimetro():.2f}"

class Circulo(Forma):
    def __init__(self, raio):
        self.raio = raio

    def area(self):
        return 3.14159 * self.raio ** 2

    def perimetro(self):
        return 2 * 3.14159 * self.raio

# Forma()  # TypeError — não pode instanciar classe abstrata
c = Circulo(5)
print(c.descricao())  # Área: 78.54, Perímetro: 31.42

Dataclasses como alternativa moderna

Para classes que servem principalmente para armazenar dados, o decorator @dataclass gera automaticamente __init__, __repr__ e __eq__:

from dataclasses import dataclass

@dataclass
class Ponto:
    x: float
    y: float

    def distancia_origem(self):
        return (self.x**2 + self.y**2) ** 0.5

p = Ponto(3.0, 4.0)
print(p)                    # Ponto(x=3.0, y=4.0)
print(p.distancia_origem()) # 5.0

MRO: Ordem de Resolução de Métodos

Quando há herança múltipla, o Python usa o algoritmo C3 Linearization para determinar a ordem em que as classes são consultadas. Você pode inspecionar o MRO com .__mro__ ou mro():

class A:
    def metodo(self): return "A"

class B(A):
    def metodo(self): return "B"

class C(A):
    def metodo(self): return "C"

class D(B, C):
    pass

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
print(D().metodo())  # "B" — segue o MRO

Introdução a metaclasses

Toda classe em Python é uma instância de uma metaclasse — por padrão, type. As metaclasses controlam a criação das próprias classes e são usadas em frameworks avançados como o ORM do Django.

class MetaValidador(type):
    def __new__(mcs, nome, bases, namespace):
        for attr, valor in namespace.items():
            if not attr.startswith('_') and callable(valor):
                print(f"Método registrado: {attr}")
        return super().__new__(mcs, nome, bases, namespace)

class MinhaClasse(metaclass=MetaValidador):
    def metodo_a(self): pass
    def metodo_b(self): pass
# Imprime: Método registrado: metodo_a
# Imprime: Método registrado: metodo_b

O protocolo descritor

Descritores são objetos que customizam o acesso a atributos via __get__, __set__ e __delete__. O decorator @property é implementado como um descritor internamente:

class Validado:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, f'_{self.name}', None)

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)) or value < 0:
            raise ValueError(f"{self.name} deve ser um número positivo")
        setattr(obj, f'_{self.name}', value)

class Produto:
    preco = Validado()
    estoque = Validado()

    def __init__(self, preco, estoque):
        self.preco = preco
        self.estoque = estoque

p = Produto(10.0, 5)
# p.preco = -1  # ValueError

Erros comuns

O erro mais frequente com atributos mutáveis de classe é usar listas ou dicionários como padrão:

# ERRADO — a lista é compartilhada entre todas as instâncias
class Turma:
    alunos = []  # Atributo de classe mutável!

    def adicionar(self, aluno):
        self.alunos.append(aluno)

# CORRETO — lista criada por instância
class Turma:
    def __init__(self):
        self.alunos = []  # Atributo de instância

Quando usar classes?

Use classes quando você tem dados e comportamentos intimamente relacionados, quando precisa de múltiplas instâncias com estado próprio, ou quando quer aproveitar herança e polimorfismo. Para dados simples sem comportamento, considere dataclass ou namedtuple. Para funções puras sem estado, prefira módulos com funções.

Termos Relacionados