typing.Self no Python: retornos fluentes

Publicado em: 28/08/2026
Tempo de leitura: 5 minutos
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

Métodos que retornam a própria instância são comuns em builders, APIs fluentes, objetos configuráveis e context managers. Anotar esses retornos com o nome da classe funciona até aparecer uma subclasse: o analisador pode perder o tipo mais específico. typing.Self representa “a classe concreta atual”, preservando o tipo de subclasses sem criar um TypeVar manual.

Neste guia, você aprenderá a usar Self em métodos de instância, classmethods, factories, protocolos, cópias, context managers e hierarquias; também verá quando Self é inadequado e como ele se compara a tipos genéricos.

O problema de retornar o nome da classe

class Consulta:
    def limitar(self, quantidade: int) -> "Consulta":
        self._limite = quantidade
        return self

class ConsultaSQL(Consulta):
    def ordenar(self, campo: str) -> "ConsultaSQL":
        self._ordem = campo
        return self

consulta = ConsultaSQL().limitar(10)
# alguns analisadores enxergam Consulta, não ConsultaSQL

A implementação devolve a mesma instância concreta, mas a anotação fixa Consulta como retorno.

Usando Self

from typing import Self

class Consulta:
    def limitar(self, quantidade: int) -> Self:
        self._limite = quantidade
        return self

class ConsultaSQL(Consulta):
    def ordenar(self, campo: str) -> Self:
        self._ordem = campo
        return self

consulta = ConsultaSQL().limitar(10).ordenar("nome")

Self é interpretado de acordo com a classe concreta do objeto. O encadeamento mantém ConsultaSQL.

APIs fluentes

Builders frequentemente alteram o objeto e devolvem self.

class Requisicao:
    def __init__(self) -> None:
        self._headers: dict[str, str] = {}
        self._timeout = 5.0

    def header(self, nome: str, valor: str) -> Self:
        self._headers[nome] = valor
        return self

    def timeout(self, segundos: float) -> Self:
        if segundos <= 0:
            raise ValueError("timeout deve ser positivo")
        self._timeout = segundos
        return self

O consumidor pode encadear operações e subclasses mantêm seus métodos específicos.

Self em classmethods

Self também descreve factories que retornam uma instância da classe chamada.

class Documento:
    def __init__(self, texto: str) -> None:
        self.texto = texto

    @classmethod
    def vazio(cls) -> Self:
        return cls("")

class DocumentoMarkdown(Documento):
    pass

md = DocumentoMarkdown.vazio()

O tipo de md é DocumentoMarkdown, desde que a implementação realmente construa cls.

Factory que retorna sempre a classe base

Não use Self quando o método sempre devolve uma classe específica independentemente da subclasse.

class Documento:
    @classmethod
    def padrao(cls) -> "Documento":
        return Documento("modelo")

Anotar como Self seria uma promessa falsa para DocumentoMarkdown.padrao(), porque a função cria explicitamente Documento.

Construtores alternativos com argumentos

class Vetor:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    @classmethod
    def origem(cls) -> Self:
        return cls(0.0, 0.0)

    @classmethod
    def de_iteravel(cls, valores) -> Self:
        x, y = valores
        return cls(float(x), float(y))

Subclasses precisam manter um construtor compatível ou sobrescrever a factory. Self descreve o retorno, mas não garante que cls(...) aceite os mesmos argumentos em toda hierarquia.

Métodos de cópia e clone

from copy import copy

class Configuracao:
    def clone(self) -> Self:
        return copy(self)

Se copy(self) preserva a classe concreta, Self expressa corretamente o retorno. Para cópia profunda, as mesmas considerações de mutabilidade continuam válidas.

Métodos que recebem outro objeto do mesmo tipo

Self pode aparecer em parâmetros.

class Ponto:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    def distancia(self, outro: Self) -> float:
        dx = self.x - outro.x
        dy = self.y - outro.y
        return (dx * dx + dy * dy) ** 0.5

Isso indica que outro deve ser compatível com a classe concreta atual. Use com cuidado em hierarquias: algumas operações podem aceitar qualquer instância da classe base, não apenas o mesmo subtipo.

Quando o parâmetro deve ser a classe base

Se uma subclasse pode comparar ou combinar com qualquer Ponto, anote Ponto, não Self.

def distancia(self, outro: "Ponto") -> float:
    ...

Self é mais restritivo e deve representar uma relação real entre receptor e parâmetro.

Self em propriedades

Uma propriedade pode retornar a instância atual ou uma referência encadeável.

class No:
    @property
    def raiz(self) -> Self:
        atual = self
        while atual.pai is not None:
            atual = atual.pai
        return atual

A anotação só é correta se a raiz for garantidamente da mesma classe concreta. Em árvores heterogêneas, o retorno pode precisar ser a classe base.

Context managers

__enter__ costuma retornar self.

class Sessao:
    def __enter__(self) -> Self:
        self.abrir()
        return self

    def __exit__(self, exc_type, exc, tb) -> None:
        self.fechar()

class SessaoAuditada(Sessao):
    def evento(self, texto: str) -> None:
        ...

with SessaoAuditada() as sessao:
    sessao.evento("início")

Self preserva o tipo SessaoAuditada dentro do bloco.

Self em Protocol

Protocolos podem exigir métodos fluentes.

from typing import Protocol, Self

class Configuravel(Protocol):
    def configurar(self, chave: str, valor: object) -> Self:
        ...

Uma implementação compatível deve retornar sua própria instância concreta. O guia sobre Protocol no Python explica tipagem estrutural.

Self em classes genéricas

Self representa a classe concreta incluindo seus parâmetros genéricos.

from typing import Generic, TypeVar, Self

T = TypeVar("T")

class Caixa(Generic[T]):
    def __init__(self, valor: T) -> None:
        self.valor = valor

    def substituir(self, valor: T) -> Self:
        self.valor = valor
        return self

Em Caixa[int], o método continua retornando a mesma especialização.

Método que muda o parâmetro genérico

Self não serve quando o retorno possui parâmetros de tipo diferentes.

U = TypeVar("U")

class Caixa(Generic[T]):
    def mapear(self, funcao: Callable[[T], U]) -> "Caixa[U]":
        return Caixa(funcao(self.valor))

mapear() produz uma nova Caixa[U], não necessariamente o mesmo tipo de self. Use generics explícitos.

Comparação com TypeVar bound

Antes de Self, era comum declarar um TypeVar ligado à classe.

from typing import TypeVar

TConsulta = TypeVar("TConsulta", bound="Consulta")

class Consulta:
    def limitar(self: TConsulta, quantidade: int) -> TConsulta:
        ...

Self é mais curto e legível para o caso comum. TypeVar continua útil quando a relação envolve várias funções, parâmetros ou tipos externos.

Subclasses que retornam outro objeto

Uma subclasse pode sobrescrever um método e quebrar a promessa de Self.

class Base:
    def normalizar(self) -> Self:
        return self

class Especial(Base):
    def normalizar(self) -> Base:
        return Base()

Um verificador deve sinalizar a incompatibilidade. Métodos fluentes em bases exigem que subclasses respeitem a relação de retorno.

Self não obriga identidade

Self não significa necessariamente “o mesmo objeto”. Significa “uma instância da mesma classe concreta”.

class Registro:
    def com_nome(self, nome: str) -> Self:
        novo = copy(self)
        novo.nome = nome
        return novo

O método pode retornar uma nova instância do mesmo subtipo. Documente se a operação modifica o objeto ou cria uma cópia.

Decoradores e preservação de Self

Decoradores mal tipados podem apagar a assinatura de métodos fluentes. Use functools.wraps em runtime e anotações com ParamSpec quando o decorador precisa preservar parâmetros.

Para métodos simples, evite decoradores que retornem Callable[..., object], pois isso elimina a relação de Self para o analisador.

Compatibilidade de versão

typing.Self faz parte de versões modernas do Python. Em versões anteriores, use typing_extensions.Self.

try:
    from typing import Self
except ImportError:
    from typing_extensions import Self

Bibliotecas devem declarar a dependência e testar com todas as versões suportadas.

Erros comuns

  • Usar Self em factory que cria a classe base: a promessa fica incorreta.
  • Usar Self quando o retorno muda o tipo genérico: declare o tipo de destino explicitamente.
  • Restringir parâmetros sem necessidade: talvez a classe base seja suficiente.
  • Presumir que Self significa identidade: uma nova instância do mesmo subtipo também é válida.
  • Ignorar construtores incompatíveis em subclasses: factories com cls() podem falhar em runtime.
  • Aplicar em métodos estáticos: não existe receptor de classe ou instância para definir Self.

Exemplo completo: builder de consulta

from typing import Self

class Query:
    def __init__(self, tabela: str) -> None:
        self.tabela = tabela
        self.filtros: list[str] = []
        self._limite: int | None = None

    def onde(self, expressao: str) -> Self:
        self.filtros.append(expressao)
        return self

    def limitar(self, quantidade: int) -> Self:
        if quantidade < 1:
            raise ValueError("limite inválido")
        self._limite = quantidade
        return self

    @classmethod
    def da_tabela(cls, tabela: str) -> Self:
        return cls(tabela)

class QueryOrdenada(Query):
    def ordenar(self, campo: str) -> Self:
        self.ordem = campo
        return self

consulta = (
    QueryOrdenada.da_tabela("clientes")
    .onde("ativo = true")
    .limitar(20)
    .ordenar("nome")
)

Cada método preserva QueryOrdenada, inclusive a factory herdada.

Quando não usar uma API fluente

Self melhora a tipagem de APIs fluentes, mas não garante que o design seja claro. Encadeamentos longos podem esconder efeitos colaterais e dificultar debugging. Métodos que executam I/O, persistem dados ou mudam estado global talvez devam retornar resultados explícitos.

Use fluência para configuração e transformação previsível, e documente mutabilidade e falhas.

Conclusão

typing.Self descreve a classe concreta do receptor. Ele simplifica métodos que retornam a própria instância, classmethods alternativos, clones, context managers, protocolos e builders, preservando subclasses automaticamente.

A documentação oficial de Self no módulo typing detalha os usos permitidos. Use Self quando o retorno ou parâmetro realmente acompanha o tipo concreto, e prefira generics ou a classe base quando a relação for diferente.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Yellow block letters spelling 'error' on a vibrant pink background, capturing a playful message.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ExceptionGroup no Python: múltiplos erros

    Aprenda ExceptionGroup no Python para múltiplos erros, except*, grupos aninhados, TaskGroup, filtros, logging e validação em lote.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A tranquil wooden pathway winds through a vibrant autumn forest, covered in fallen leaves.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk no Python: percorra diretórios

    Aprenda Path.walk no Python para percorrer diretórios, podar pastas, tratar erros, links simbólicos, tamanhos, remoção segura e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Minimalist hourglass filled with sand symbolizing time and patience, against a soft background.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.timeout no Python: controle prazos

    Aprenda asyncio.timeout no Python para deadlines, timeout_at, reagendamento, TaskGroup, cleanup, retries e cancelamento assíncrono seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A detailed view of computer programming code on a screen, showcasing software development.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TaskGroup no Python: concorrência estruturada

    Aprenda asyncio.TaskGroup no Python para concorrência estruturada, resultados, cancelamento, ExceptionGroup, timeouts e tarefas aninhadas.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Black and white close-up of a dictionary page showing the definition of 'virus.'
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    MappingProxyType: dicionário só leitura

    Aprenda MappingProxyType no Python para expor dicionários somente leitura, criar visões dinâmicas, snapshots e proteger invariantes sem cópias.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026
    A close-up of a padlock securing a wire fence, symbolizing protection and safety.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Literal no Python: restrinja valores

    Aprenda typing.Literal no Python para restringir valores, criar overloads, discriminar TypedDict, usar match/case e melhorar APIs tipadas.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026