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 ConsultaSQLA 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 selfO 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.5Isso 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 atualA 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 selfEm 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 novoO 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 SelfBibliotecas 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.







