
Passo 1 de 6
Vincular atributos e métodos ao parâmetro de tipo
Use um único parâmetro de tipo para manter relacionados o valor recebido, o valor armazenado e os valores lidos ou substituídos em uma instância.
Trilha de aprendizado · Nível 12 · Tutorial 7
Criar classes reutilizáveis que preservem o tipo dos dados armazenados e explicar por que algumas substituições entre classes parametrizadas são inseguras.
Vincular atributos e métodos ao parâmetro de tipo
Use um único parâmetro de tipo para manter relacionados o valor recebido, o valor armazenado e os valores lidos ou substituídos em uma instância. 4 min
Especializar a classe na construção
Crie instâncias de uma classe genérica com tipos explícitos ou inferidos e confira o contrato resultante com mypy. 4 min
Explicar a invariância pela possibilidade de escrita
Entenda por que um contêiner mutável de gatos não pode ser usado livremente onde se espera um contêiner de animais. 5 min
Reconhecer covariância em contratos de consulta
Use contratos de consulta para aceitar elementos de subtipos com segurança, sem prometer que a coleção concreta é imutável. 3 min
Relacionar a interface à variância inferida
Examine o que uma classe expõe para prever se suas especializações podem ser covariantes ou precisam ser invariantes. 5 min
Aplicação final: definir e verificar classes genéricas
Integre armazenamento mutável e consulta covariante em um exemplo completo, verificando quais substituições o mypy aceita ou rejeita. 5 min

Passo 1 de 6
Use um único parâmetro de tipo para manter relacionados o valor recebido, o valor armazenado e os valores lidos ou substituídos em uma instância.
No Python 3.12 ou superior, declare o parâmetro entre colchetes no nome da classe: class Caixa[T].
Esse T pertence à classe. Portanto, todos os métodos da mesma Caixa reutilizam o mesmo tipo: o valor que entra na criação é armazenado, lido e eventualmente substituído por outro valor compatível com aquele contrato.
As quatro ocorrências de T abaixo representam a mesma relação de tipos dentro de cada instância.

O mesmo T conecta entrada, armazenamento, leitura e substituição.
Python 3.12 ou superior
class Caixa[T]:
def __init__(self, valor: T) -> None:
self.valor: T = valor
def obter(self) -> T:
return self.valor
def substituir(self, novo_valor: T) -> None:
self.valor = novo_valorA assinatura de __init__ diz que valor tem tipo T. O atributo self.valor guarda esse mesmo T; por isso, obter() devolve T e substituir() aceita T.
Não declare um novo parâmetro de tipo em cada método: isso permitiria que cada chamada escolhesse um tipo independente e quebraria a relação com o conteúdo já armazenado.
Dica
As anotações orientam o verificador estático, como o mypy. Elas não convertem valores nem validam automaticamente dados recebidos em tempo de execução.
Complete cada lacuna com o parâmetro de tipo da classe:
class Caixa[T]:
def __init__(self, valor: T) -> None:
self.valor: ____ = valor
def obter(self) -> ____:
return self.valor
def substituir(self, novo_valor: ____) -> None:
self.valor = novo_valor

Passo 2 de 6
Crie instâncias de uma classe genérica com tipos explícitos ou inferidos e confira o contrato resultante com mypy.
Depois de declarar class Caixa[T], cada construção fixa uma especialização para aquela instância. Você pode escrevê-la explicitamente, como Caixa[str]("relatório"), ou deixar o verificador deduzi-la pelo argumento, como Caixa(42).
A especialização não é uma conversão de valor: ela é o contrato estático que liga o valor inicial, as leituras e futuras substituições.
class Caixa[T]:
def __init__(self, valor: T) -> None:
self.valor = valor
def ler(self) -> T:
return self.valor
def substituir(self, valor: T) -> None:
self.valor = valor
# Especialização escrita explicitamente.
titulo = Caixa[str]("Relatório mensal")
# Especialização inferida a partir do argumento do construtor.
quantidade = Caixa(42)
reveal_type(titulo) # mypy: Caixa[builtins.str]
reveal_type(titulo.ler()) # mypy: builtins.str
reveal_type(quantidade) # mypy: Caixa[builtins.int]
reveal_type(quantidade.ler()) # mypy: builtins.intO mesmo tipo escolhido na construção determina o que entra e o que sai pelos métodos.

Caixa[str] preserva str em toda a interface; Caixa[int] preserva int.
Uma anotação no nome da variável torna explícito o domínio que a instância deve aceitar. Em codigo: Caixa[str], o verificador exige uma caixa de textos; isso restringe tanto o argumento inicial quanto os próximos argumentos de substituir.
O contexto da anotação também participa da verificação. Ele não transforma 10 em texto: uma construção incompatível continua sendo um erro.
codigo: Caixa[str] = Caixa("A-17")
texto = codigo.ler() # tipo: str
codigo.substituir("B-08") # válido
codigo.substituir(8) # erro do mypy: esperado str
incompativel: Caixa[str] = Caixa(10)
# erro do mypy: Caixa[int] não é compatível com Caixa[str]Dica
Mypy analisa as anotações sem executar o programa. Em Python, as anotações não fazem Caixa[str] rejeitar automaticamente um valor inadequado em tempo de execução. Quando dados vêm de fora do programa, a validação de valores é uma preocupação separada.
Associe cada expressão à especialização ou ao efeito correto.
Toque em um item e depois no par correspondente.
Em um arquivo local com Python 3.12+, copie a classe e os exemplos deste step. Execute python -m mypy seu_arquivo.py para ver os resultados de reveal_type. Depois, remova as linhas intencionalmente incompatíveis antes de executar o arquivo normalmente, pois reveal_type é usado para análise estática.
No seu arquivo local, qual tipo mypy revelou para uma instância construída com texto e para a chamada a ler()? Relate também o que ocorreu ao tentar substituir o valor por outro tipo.
Escreva pelo menos 30 caracteres (0/30).

Passo 3 de 6
Entenda por que um contêiner mutável de gatos não pode ser usado livremente onde se espera um contêiner de animais.
Se Gato herda de Animal, um Gato pode ser usado onde um Animal é esperado. Isso fala sobre objetos individuais.
Já a variância pergunta outra coisa: uma especialização genérica pode ocupar o lugar de outra? Mesmo que Gato seja subtipo de Animal, não conclua que Caixa[Gato] pode ser usada como Caixa[Animal].
O tipo de uma instância não muda durante essa análise. O verificador avalia se a substituição entre os dois contratos seria segura.
Compare a relação segura entre os objetos com a substituição insegura entre contêineres mutáveis.

Gato é compatível com Animal; isso não torna automaticamente Caixa[Gato] compatível com Caixa[Animal].
Considere uma Caixa[T] que permite ler e substituir seu valor. Se uma Caixa[Gato] fosse aceita como Caixa[Animal], uma função poderia gravar nela qualquer subtipo de Animal, inclusive um Cachorro.
Mas outra referência ainda poderia acreditar que aquela mesma caixa contém somente Gato. A escrita respeita o contrato amplo da função, porém quebra o contrato específico da referência original.
Execute com Python 3.12+ e mypy para observar o diagnóstico na chamada final.
class Animal:
pass
class Gato(Animal):
def miar(self) -> None:
print("Miau")
class Cachorro(Animal):
pass
class Caixa[T]:
def __init__(self, valor: T) -> None:
self.valor = valor
def obter(self) -> T:
return self.valor
def substituir(self, novo_valor: T) -> None:
self.valor = novo_valor
def guardar_cachorro(caixa: Caixa[Animal]) -> None:
caixa.substituir(Cachorro())
caixa_de_gatos = Caixa(Gato())
guardar_cachorro(caixa_de_gatos) # mypy: argumento incompatível
gato = caixa_de_gatos.obter()
gato.miar() # seria inseguro se a chamada acima fosse aceitaO problema aparece porque as duas variáveis apontariam para a mesma caixa em memória.

Permitir a passagem criaria uma rota para inserir Cachorro em uma caixa prometida como Caixa[Gato].
Suponha, apenas para raciocinar, que Caixa[Gato] pudesse ser passada para uma função que espera Caixa[Animal]. Coloque os acontecimentos na ordem correta.
Uma list[Gato] permite leitura de Gato, mas também permite inserção de Gato. Se ela pudesse substituir uma list[Animal], quem recebesse a lista ampla poderia fazer append(Cachorro()).
Por isso, list é invariante: list[Gato] não é compatível com list[Animal], nem o contrário. A relação de subtipagem dos elementos não basta quando o contêiner pode ser alterado.
A chamada deve ser rejeitada pelo verificador pelo mesmo motivo da Caixa.
class Animal:
pass
class Gato(Animal):
def miar(self) -> None:
print("Miau")
class Cachorro(Animal):
pass
def adicionar_cachorro(animais: list[Animal]) -> None:
animais.append(Cachorro())
gatos: list[Gato] = [Gato()]
adicionar_cachorro(gatos) # mypy: argumento incompatível
gatos[0].miar() # a lista poderia agora conter um CachorroPor que list[Gato] não deve ser aceita como argumento de uma função que recebe list[Animal] e pode chamar append? Explique usando Cachorro e o compartilhamento da mesma lista.
Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 6
Use contratos de consulta para aceitar elementos de subtipos com segurança, sem prometer que a coleção concreta é imutável.
Se todo Gato é um Animal, um contrato que apenas fornece elementos pode aceitar uma especialização mais específica:
Sequence[Gato] pode ser usada onde se espera Sequence[Animal].
Essa é a covariância: a direção da subtipagem é preservada entre as especializações. Quem recebe a sequência só pode consultar animais; portanto, receber gatos não quebra o contrato.
O consumidor lê cada elemento como Animal, embora os objetos concretos sejam gatos.

Como Gato é subtipo de Animal, valores produzidos como Gato servem para uma leitura que espera Animal.
Python 3.12+
from collections.abc import Sequence
class Animal:
def emitir_som(self) -> str:
return "som"
class Gato(Animal):
def emitir_som(self) -> str:
return "miau"
def mostrar_sons(animais: Sequence[Animal]) -> None:
for animal in animais:
print(animal.emitir_som())
gatos: list[Gato] = [Gato(), Gato()]
mostrar_sons(gatos) # Aceito: list[Gato] atende a Sequence[Animal]
list continua sendo invariável: list[Gato] não é um list[Animal]. Porém, uma lista de gatos oferece a interface de consulta Sequence[Gato], e essa interface é compatível com Sequence[Animal].
A função não adiciona, remove nem substitui elementos. Por isso, anotar o parâmetro como Sequence[Animal] descreve exatamente as operações de que ela precisa.
Dica
Não é necessário copiar nem converter gatos para list[Animal]. Mantenha o tipo concreto verdadeiro, list[Gato], e use Sequence[Animal] no ponto em que o código só consulta a coleção.
Sequence[Animal] impede que a função tipada trate o parâmetro como uma coleção alterável. Isso não torna o objeto original imutável: quem ainda possui a referência gatos pode alterá-la como uma list[Gato].
A mesma lista pode ser vista por uma função somente de leitura e pelo código que a criou, que conserva as operações de lista.

A restrição está no contrato usado pela função, não em uma mudança no objeto concreto.
Ao receber gatos: list[Gato] em um parâmetro anotado como Sequence[Animal], a lista original se torna imutável em todo o programa.
Uma função percorre uma coleção, chama emitir_som() em cada elemento e não a altera. Ela deve aceitar tanto list[Gato] quanto outras sequências de animais. Qual anotação de parâmetro é a mais adequada?
Resumo
Sequence[Gato] onde se espera Sequence[Animal].list[Gato] pode ser passada a um parâmetro Sequence[Animal] quando a função só consulta.list permanece invariável: não trate list[Gato] como list[Animal].
Passo 5 de 6
Examine o que uma classe expõe para prever se suas especializações podem ser covariantes ou precisam ser invariantes.
Em Python 3.12+, declare uma classe genérica com class Nome[T]. O mypy pode inferir a variância de T a partir da interface exposta: como T é lido e como pode ser escrito.
Uma classe que apenas fornece um T pode ser covariante: onde se espera uma especialização mais ampla, uma especialização de subtipo é segura. Quando a interface também aceita um T para alteração, ela precisa ser invariante.
A relação já conhecida entre Gato e Animal continua a mesma; o que muda é a compatibilidade entre especializações da classe.
Compare as três formas de expor um valor genérico.

Somente consulta pode preservar a direção Gato → Animal; uma entrada pública de T exige invariância.
Use Python 3.12 ou superior e verifique com mypy.
class Animal:
pass
class Gato(Animal):
pass
class Consulta[T]:
def __init__(self, valor: T) -> None:
self._valor = valor
@property
def valor(self) -> T:
return self._valor
def observar(animal: Consulta[Animal]) -> Animal:
return animal.valor
gatos = Consulta(Gato())
observado = observar(gatos) # Aceito: Consulta[Gato] -> Consulta[Animal]T entra no __init__, mas o inicializador participa apenas da criação do objeto. Depois disso, a interface pública de Consulta só devolve T, pela propriedade sem setter.
Assim, observar consegue ler um Animal sem poder inserir outro animal na instância recebida. O mypy pode inferir T como covariante nessa classe.
O prefixo _ comunica armazenamento interno e é relevante para essa análise do mypy. Ele é uma convenção: em execução, Python ainda permite acessar gatos._valor.
Dica
Para inspecionar a especialização inferida, adicione temporariamente reveal_type(gatos). O mypy informa algo como Consulta[Gato]. Remova essa chamada antes de executar o programa normalmente.
Estas duas interfaces recebem e devolvem T.
class CaixaComSetter[T]:
def __init__(self, valor: T) -> None:
self._valor = valor
@property
def valor(self) -> T:
return self._valor
@valor.setter
def valor(self, novo_valor: T) -> None:
self._valor = novo_valor
class CaixaPublica[T]:
def __init__(self, valor: T) -> None:
self.valor: T = valor
def guardar_animal(caixa: CaixaComSetter[Animal]) -> None:
caixa.valor = Animal()
def trocar_animal(caixa: CaixaPublica[Animal]) -> None:
caixa.valor = Animal()Em CaixaComSetter, o setter aceita um novo T; em CaixaPublica, o atributo público pode receber um novo T. Ambas oferecem leitura e escrita, mesmo que não exista um método chamado trocar.
Portanto, o mypy infere invariância: CaixaComSetter[Gato] não substitui CaixaComSetter[Animal], e o mesmo vale para CaixaPublica. Se fosse permitido, uma função poderia gravar um Animal que não é Gato na mesma caixa.
Não tente tornar um atributo público “interno” apenas pelo nome: _valor não bloqueia acesso em tempo de execução. A decisão de interface continua sendo sua.
Associe cada interface à classificação que o mypy pode inferir para T.
Toque em um item e depois no par correspondente.
Uma classe Consulta[T] possui armazenamento _valor e uma propriedade de leitura valor -> T. Ela é usada como Consulta[Gato] em uma função que espera Consulta[Animal]. Explique por que isso é seguro e o que muda se você adicionar um setter valor(novo: T).
Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 6
Integre armazenamento mutável e consulta covariante em um exemplo completo, verificando quais substituições o mypy aceita ou rejeita.
Nesta aplicação final, use duas classes com o mesmo vínculo entre T, inicializador e valor armazenado:
Caixa[T] lê e substitui o valor. Por permitir escrita, sua especialização é invariante.Consulta[T] expõe apenas uma propriedade de leitura. Com armazenamento interno _valor e sem setter, o mypy pode inferir covariância.Assim, uma Consulta[Gato] pode atender um consumidor que só consulta Animal; uma Caixa[Gato] não pode atender um consumidor que poderia gravar qualquer Animal.

Na caixa mutável, uma escrita ampla comprometeria a promessa de ler apenas gatos. Na consulta, só há saída de valores.
No seu computador, crie genericos.py com o código abaixo. As chamadas com reveal_type servem para o mypy; remova-as ou deixe-as apenas durante a análise. Há uma chamada insegura intencional, que deve produzir um diagnóstico.
class Animal:
pass
class Gato(Animal):
pass
class Cachorro(Animal):
pass
class Caixa[T]:
def __init__(self, valor: T) -> None:
self._valor = valor
def ler(self) -> T:
return self._valor
def trocar(self, novo_valor: T) -> None:
self._valor = novo_valor
class Consulta[T]:
def __init__(self, valor: T) -> None:
self._valor = valor
@property
def valor(self) -> T:
return self._valor
def colocar_cachorro(destino: Caixa[Animal]) -> None:
destino.trocar(Cachorro())
def descrever(origem: Consulta[Animal]) -> Animal:
return origem.valor
caixa_explicita = Caixa[Gato](Gato())
caixa_inferida = Caixa(Gato())
consulta_explicita = Consulta[Gato](Gato())
consulta_inferida = Consulta(Gato())
caixa_animais = Caixa[Animal](Gato())
reveal_type(caixa_explicita) # Caixa[Gato]
reveal_type(caixa_inferida) # Caixa[Gato]
reveal_type(consulta_explicita) # Consulta[Gato]
reveal_type(consulta_inferida) # Consulta[Gato]
colocar_cachorro(caixa_animais) # Aceito: Caixa[Animal] pode receber Cachorro.
colocar_cachorro(caixa_explicita) # Erro intencional: Caixa[Gato] não é Caixa[Animal].
animal = descrever(consulta_explicita) # Aceito: Consulta[Gato] é compatível com Consulta[Animal].Dica
No diretório do arquivo, execute:
python -m mypy --strict --python-version 3.12 genericos.py
Espere os quatro tipos revelados e um erro na passagem de caixa_explicita para colocar_cachorro. Depois, comente somente essa chamada insegura e rode novamente: o resultado deve ser livre de erros.
Após executar o mypy, responda:
descrever(consulta_explicita) é aceita?colocar_cachorro(caixa_explicita) é rejeitada?Any, casts ou supressões. Explique a alternativa adequada se a função precisa gravar qualquer Animal e a alternativa adequada se ela deve trabalhar apenas com Gato.Escreva pelo menos 280 caracteres (0/280).
Resumo
Use este roteiro ao projetar e revisar uma classe parametrizada.
Caixa[Gato], ou com reveal_type quando o construtor a inferir.T, trate suas especializações como invariantes: Caixa[Gato] não substitui Caixa[Animal].Consulta[Gato] pode ser usada onde se consulta Consulta[Animal].Parabéns! Você concluiu: Definir classes genéricas com parâmetros de tipo
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks