Trilha de aprendizado · Nível 12 · Tutorial 4

Anotar funções recebidas e retornadas com Callable

Descrever assinaturas de funções usadas como valores e verificar se funções, closures ou objetos chamáveis atendem ao contrato esperado.

  • Nível: Intermediário
  • Duração: 16 min
  • 7 passos
Anotar funções recebidas e retornadas com Callable

O que você vai percorrer

  1. Ler e escrever uma assinatura com Callable Represente a forma de chamada de uma função como um tipo e separe o chamável do valor que ele produz. 2 min
  2. Anotar uma função que recebe um callback Defina o contrato de um callback observando como a função consumidora o chama e usa seu resultado. 2 min
  3. Decidir se um callback pode substituir outro Compare callbacks pelo que o consumidor pode chamar e pelo resultado que ele precisa receber. 3 min
  4. Usar instâncias chamáveis no mesmo contrato Use a assinatura de __call__ para verificar quando uma instância pode ocupar o lugar de um callback Callable. 2 min
  5. Anotar a função produzida por uma fábrica Diferencie o retorno da fábrica do retorno da função interna e verifique o contrato produzido com mypy. 3 min
  6. Reconhecer o que Callable não verifica Compare assinaturas explícitas e flexíveis para entender quais garantias o verificador preserva — e quais ele deixa de oferecer. 2 min
  7. Aplicar e revisar os contratos de chamada Integre um consumidor, uma fábrica de closures e uma instância chamável em um fluxo de transformações de texto, verificando o contrato com mypy. 4 min

O que você vai aprender

  • Anotar parâmetros e retornos que representam objetos chamáveis.
  • Identificar incompatibilidades entre a assinatura esperada e a função fornecida.
  • Verificar o tipo de uma função retornada por uma fábrica de funções.
  • Reconhecer quando uma anotação de Callable deixa de verificar os argumentos.

Antes de começar

  • Verificar anotações com mypy
  • Passar funções como argumentos
  • Criar closures com estado e nonlocal
  • Criar objetos chamáveis com __call__
  • Especializar classes com herança e super

Passo 1 de 7

Ler e escrever uma assinatura com Callable

Represente a forma de chamada de uma função como um tipo e separe o chamável do valor que ele produz.

O tipo da chamada

Callable descreve quem pode ser chamado

Em Python 3.12 ou superior, importe Callable de collections.abc.

A forma geral é:

Callable[[tipos dos argumentos], tipo do retorno]

A lista interna descreve os argumentos por posição. O último tipo descreve o valor devolvido quando o objeto é chamado.

Leia a assinatura em duas partes

A anotação separa o que entra na chamada do que sai dela.

Diagrama de uma função recebendo um valor str e produzindo um valor int; ao lado, a representação Callable com lista de um argumento str e retorno int.

Callable[[str], int]: recebe um str e, ao ser chamado, produz um int.

Chamável não é o resultado

Anotando uma variável que guarda uma função

A variável medir guarda a função. Já tamanho guarda o resultado da chamada.

python
from collections.abc import Callable


def contar_caracteres(texto: str) -> int:
    return len(texto)


medir: Callable[[str], int] = contar_caracteres
tamanho: int = medir("Python")

print(tamanho)  # 6

Dica

Leia os colchetes com atenção

Em Callable[[str], int], str é o tipo do argumento fornecido na chamada; int é o tipo do valor produzido. Portanto, medir não é um int: ele é um objeto chamável que produz int.

Sem argumentos e sem resultado útil

Duas formas frequentes

Uma lista vazia indica uma chamada sem argumentos. None no retorno indica que a chamada não produz um valor útil.

python
from collections.abc import Callable


def versao() -> str:
    return "3.12"


def avisar() -> None:
    print("Processo concluído")


obter_versao: Callable[[], str] = versao
emitir_aviso: Callable[[], None] = avisar

texto: str = obter_versao()
resultado: None = emitir_aviso()

Exemplo

Tradução rápida

Callable[[], str] significa: “algo que pode ser chamado sem argumentos e devolve str”.

Callable[[], None] significa: “algo que pode ser chamado sem argumentos e não devolve um valor útil”.

Pratique a leitura da assinatura

Complete o tipo de registrar

Considere a assinatura:

def registrar() -> None:
    print("evento registrado")

Complete a anotação:

acao: Callable[____, ____] = registrar

Passo 2 de 7

Anotar uma função que recebe um callback

Defina o contrato de um callback observando como a função consumidora o chama e usa seu resultado.

O contrato nasce no ponto de uso

Leia o consumidor primeiro

Ao anotar um parâmetro que recebe uma função, observe o que o consumidor faz com ela. Se ele fornece uma str ao callback e depois espera uma str de volta, o contrato é Callable[[str], str].

A primeira lista descreve os argumentos recebidos pelo callback; o último tipo descreve o valor que ele devolve.

Fluxo do callback

O consumidor envia um texto ao callback e usa o texto transformado que recebe de volta.

Diagrama mostrando uma função consumidora enviando uma string para um callback e recebendo outra string como resultado.

aplicar_transformacao chama o callback com uma str e recebe uma str.

Consumidor de transformações

A anotação descreve a chamada que este corpo de função tem permissão de fazer.

python
from collections.abc import Callable


def aplicar_transformacao(
    texto: str,
    transformar: Callable[[str], str],
) -> str:
    resultado = transformar(texto)
    return f"Resultado: {resultado}"


def deixar_maiusculo(valor: str) -> str:
    return valor.upper()


print(aplicar_transformacao("olá", deixar_maiusculo))
# Resultado: OLÁ

O que mypy verifica

Dois lados do mesmo contrato

Com essa anotação, o mypy verifica duas coisas relacionadas:

  • Ao chamar transformar(texto), o consumidor só pode passar uma str e deve tratar o resultado como str.
  • Ao fornecer um callback a aplicar_transformacao, a função fornecida precisa atender a essa assinatura.

Isso é verificação estática: a anotação não testa nem adapta valores enquanto o programa está rodando.

Incompatibilidades detectadas antes da execução

Execute python -m mypy arquivo.py para obter diagnósticos como estes.

python
from collections.abc import Callable


def aplicar_transformacao(
    texto: str,
    transformar: Callable[[str], str],
) -> str:
    return transformar(texto)


def contar_caracteres(valor: str) -> int:
    return len(valor)


aplicar_transformacao("casa", contar_caracteres)
# mypy: argumento incompatível: a função retorna int,
# mas o callback prometido deve retornar str

# O próprio consumidor também é verificado:
# transformar(10)
# mypy: argumento incompatível; era esperada uma str

Complete o contrato

Anote o parâmetro

Complete a anotação de normalizar:

from collections.abc import Callable

def saudar(nome: str, normalizar: ___) -> str:
    limpo = normalizar(nome)
    return f"Olá, {limpo}!"

Explique pelo uso

Justifique a assinatura

Na função saudar do exercício anterior, explique por que o contrato de normalizar é Callable[[str], str].

Escreva pelo menos 40 caracteres (0/40).

Passo 3 de 7

Decidir se um callback pode substituir outro

Compare callbacks pelo que o consumidor pode chamar e pelo resultado que ele precisa receber.

O contrato define as chamadas permitidas

Compare pelo ponto de vista do consumidor

Se um parâmetro é Callable[[Animal], Animal], o consumidor tem o direito de fornecer qualquer Animal e de usar o resultado como Animal.

Para substituir esse callback com segurança, o valor fornecido precisa:

  • aceitar todas as entradas que o consumidor pode enviar;
  • devolver um resultado que o consumidor possa tratar como Animal.

As assinaturas não precisam ser idênticas. O critério é atender às chamadas previstas pelo contrato.

Entradas e saídas do contrato

Diagrama mostrando um consumidor que envia um Animal a um callback e recebe um Animal. Um callback seguro aceita object e retorna Dog; um callback inseguro exige Dog ou retorna object.

Um parâmetro mais abrangente pode aceitar o Animal enviado; um retorno mais específico ainda pode ser usado como Animal.

Exemplo

Uma leitura prática

Contrato esperado: Callable[[Animal], Animal]

Uma função def identificar(valor: object) -> Dog pode ser compatível: ela aceita até mais do que Animal, e um Dog pode ser usado onde se espera Animal.

Já def passear(cao: Dog) -> Animal não é compatível: o consumidor poderia enviar outro Animal, como um Gato.

Veja o que o mypy aceita e rejeita

Callbacks com classes relacionadas

Execute este arquivo com: python -m mypy callbacks.py

python
from collections.abc import Callable


class Animal:
    pass


class Dog(Animal):
    pass


class Cat(Animal):
    pass


def aplicar(animal: Animal, acao: Callable[[Animal], Animal]) -> Animal:
    return acao(animal)


# Aceita uma entrada mais ampla e produz uma saída mais específica.
def identificar(valor: object) -> Dog:
    return Dog()


# Exige uma entrada específica demais.
def treinar(cao: Dog) -> Animal:
    return cao


# Produz uma saída ampla demais.
def embrulhar(animal: Animal) -> object:
    return animal


resultado = aplicar(Cat(), identificar)  # Aceito
# aplicar(Cat(), treinar)    # Erro: Cat pode ser enviada, mas treinar exige Dog
# aplicar(Cat(), embrulhar)  # Erro: object não é garantidamente um Animal

Dica

Leia a mensagem como uma chamada possível

O mypy rejeita treinar não porque o nome ou a quantidade de parâmetros difere, mas porque aplicar pode chamar o callback com um Cat. Também rejeita embrulhar porque quem recebe o resultado espera as operações garantidas por Animal, não apenas por object.

Classifique as substituições

Contrato: Callable[[Animal], Animal]

Associe cada assinatura fornecida ao seu resultado ao ser passada para um parâmetro Callable[[Animal], Animal].

Toque em um item e depois no par correspondente.

Passo 4 de 7

Usar instâncias chamáveis no mesmo contrato

Use a assinatura de call para verificar quando uma instância pode ocupar o lugar de um callback Callable.

Um contrato, duas formas de atender

Callable descreve a chamada

Se um consumidor espera um Callable[[str], str], ele não exige necessariamente uma função definida com def. Uma instância também atende ao contrato quando pode ser chamada com uma str e produz uma str.

Isso permite usar um objeto chamável quando a transformação precisa guardar uma configuração, como um prefixo.

A mesma entrada, o mesmo resultado contratado

Diagrama mostrando uma entrada de texto chegando tanto a uma função quanto a uma instância configurada com __call__, e ambas produzindo uma saída de texto.

Para o consumidor, importa a assinatura da chamada: str entra e str sai.

A assinatura vista por quem chama

Função e instância no mesmo parâmetro

Em __call__, self pertence ao mecanismo do método. Quem chama a instância fornece apenas texto; por isso self não entra na lista do Callable.

python
from collections.abc import Callable


def aplicar(texto: str, transformacao: Callable[[str], str]) -> str:
    return transformacao(texto)


def remover_espacos(texto: str) -> str:
    return texto.strip()


class ComPrefixo:
    def __init__(self, prefixo: str) -> None:
        self.prefixo = prefixo

    def __call__(self, texto: str) -> str:
        return f"{self.prefixo}{texto}"


print(aplicar("  oi  ", remover_espacos))  # oi
saudacao = ComPrefixo("Olá, ")
print(aplicar("Ana", saudacao))            # Olá, Ana

Dica

Leia pela chamada da instância

A chamada saudacao("Ana") usa a assinatura visível (__call__(texto: str) -> str). Assim, ela corresponde a Callable[[str], str], e não a Callable[[ComPrefixo, str], str].

Verifique a assinatura de __call__

Compatibilidade

A instância abaixo pode ser passada para um parâmetro anotado como Callable[[str], str].

class AjustarCaixa:
    def __call__(self, texto: str) -> str:
        return texto.upper()

Compare duas instâncias

Justifique sua decisão

Um consumidor exige Callable[[str], str].

class ComPrefixo:
    def __call__(self, texto: str) -> str:
        return f"> {texto}"

class Repetir:
    def __call__(self, texto: str, vezes: int) -> str:
        return texto * vezes

Qual instância pode ser fornecida ao consumidor? Justifique pela assinatura de __call__ e explique por que self não aparece em Callable[[str], str].

Escreva pelo menos 50 caracteres (0/50).

Passo 5 de 7

Anotar a função produzida por uma fábrica

Diferencie o retorno da fábrica do retorno da função interna e verifique o contrato produzido com mypy.

Dois níveis de chamada

A fábrica devolve uma função

Uma fábrica recebe valores de configuração e devolve um objeto chamável. Portanto, a anotação depois de -> descreve a função produzida, não o texto que ela produzirá mais tarde.

Em criar_formatador, prefixo pertence à chamada da fábrica. Já texto pertence à chamada do formatador devolvido.

Fluxo da fábrica

Observe que há duas chamadas separadas.

Diagrama mostrando uma fábrica recebendo um prefixo e devolvendo uma função; depois, essa função recebe um texto e devolve um texto formatado.

Primeira chamada: configurar a fábrica. Segunda chamada: usar a função produzida.

Uma fábrica de formatadores

A lista interna de Callable contém os tipos dos argumentos da função devolvida. O último tipo é o retorno dessa função.

python
from collections.abc import Callable


def criar_formatador(prefixo: str) -> Callable[[str], str]:
    def formatar(texto: str) -> str:
        return f"{prefixo}: {texto}"

    return formatar


formatar_aviso = criar_formatador("AVISO")
print(formatar_aviso("Porta aberta"))  # AVISO: Porta aberta

Devolver a função não é chamá-la

O retorno precisa ser um chamável

A fábrica prometeu Callable[[str], str]. Assim, return formatar devolve a própria função interna, que ainda espera receber um str.

Já formatar("pronto") executa a função interna imediatamente e produz um str. Esse valor não atende ao contrato de retorno da fábrica.

Compare os dois retornos

A segunda versão quebra a promessa feita após ->. O mypy aponta que um str não pode ser retornado onde se espera um Callable[[str], str].

python
from collections.abc import Callable


def fabrica_correta(prefixo: str) -> Callable[[str], str]:
    def formatar(texto: str) -> str:
        return f"{prefixo}: {texto}"

    return formatar  # devolve a função


def fabrica_incorreta(prefixo: str) -> Callable[[str], str]:
    def formatar(texto: str) -> str:
        return f"{prefixo}: {texto}"

    return formatar("pronto")  # devolve um str: incompatível

Complete e verifique localmente

Contrato de retorno

Complete a anotação: def criar_formatador(prefixo: str) -> ______:

Inspecione a função produzida

Prática no seu computador

Crie um arquivo fabrica.py com o código abaixo e execute python -m mypy fabrica.py --python-version 3.12. A consulta reveal_type é temporária: remova-a antes de executar o arquivo normalmente com Python.

Código para verificar

A última chamada foi incluída de propósito para o mypy verificar o argumento da função produzida.

python
from collections.abc import Callable


def criar_formatador(prefixo: str) -> Callable[[str], str]:
    def formatar(texto: str) -> str:
        return f"{prefixo}: {texto}"

    return formatar


formatar_titulo = criar_formatador("TÍTULO")
reveal_type(formatar_titulo)
formatar_titulo(10)  # chamada incompatível de propósito

Leia o diagnóstico

Após rodar o mypy, relate: qual assinatura foi revelada para formatar_titulo e por que formatar_titulo(10) recebeu um diagnóstico? Inclua que providência deve ser tomada com reveal_type antes da execução normal.

Escreva pelo menos 40 caracteres (0/40).

Passo 6 de 7

Reconhecer o que Callable não verifica

Compare assinaturas explícitas e flexíveis para entender quais garantias o verificador preserva — e quais ele deixa de oferecer.

Assinatura explícita ou aberta?

O efeito de ...

Uma assinatura explícita informa entradas e saída:

Callable[[str, int], str]

Já Callable[..., str] só preserva a promessa de que a chamada produz str. As reticências dizem ao mypy que, por esse contrato, os argumentos não serão especificados nem verificados.

O que cada forma descreve

Compare as informações mantidas em cada anotação.

Diagrama lado a lado: um Callable explícito recebe uma entrada string e uma entrada inteira, ambas verificadas, e retorna string; outro Callable com reticências recebe entradas não especificadas e retorna string verificado.

A saída continua no contrato; as entradas deixam de fazer parte dele com ....

A implementação não muda

Menos diagnósticos na chamada

Execute o mypy neste arquivo e compare os dois usos.

python
from collections.abc import Callable


def rotular(texto: str, vezes: int) -> str:
    return f"{texto} " * vezes


precisa: Callable[[str, int], str] = rotular
aberta: Callable[..., str] = rotular

precisa("oi", "duas")  # mypy: argumento incompatível
aberta("oi", "duas")   # passa pela anotação; falha ao executar

resultado: str = aberta("olá", 2)

Atenção

Flexível para o verificador, não para Python

Callable[..., str] não altera rotular. Ela ainda exige um str e um int em tempo de execução. A anotação aberta apenas perde a capacidade de acusar essa chamada incompatível pelo contrato de aberta.

Em ambos os casos, o resultado ainda é tratado como str.

Nomes, parâmetros especiais e outras operações

O limite da lista de tipos

Callable[[str, int], str] descreve tipos de argumentos posicionais. Ele não registra que uma implementação espera nomes específicos em chamadas nomeadas, nem expressa exigências como parâmetros exclusivamente nomeados.

Além disso, Callable só promete que o valor pode ser chamado. Ele não pode exigir, por exemplo, um atributo .nome ou um método .reiniciar(). Quando o consumidor precisa dessas outras operações, será necessário um contrato mais expressivo, visto adiante.

Exemplo

Um contrato que Callable simples não detalha

Considere uma função que só aceita a configuração por nome:

def formatar(texto: str, *, maiusculas: bool) -> str:
    return texto.upper() if maiusculas else texto

Uma lista em Callable pode indicar que há valores str, bool e retorno str, mas não preserva que maiusculas deve ser fornecido exclusivamente como argumento nomeado.

Verifique o limite correto

Qual diagnóstico se perde?

No código da tela anterior, qual é a principal diferença estática entre precisa e aberta?

Passo 7 de 7

Aplicar e revisar os contratos de chamada

Integre um consumidor, uma fábrica de closures e uma instância chamável em um fluxo de transformações de texto, verificando o contrato com mypy.

Um contrato conectando o fluxo

Revise pelo ponto de consumo

Neste fluxo, a função aplicar pode chamar qualquer transformação com um str e precisa receber outro str como resultado. Portanto, o contrato comum é Callable[[str], str].

A origem do valor chamável pode variar: uma função criada por uma fábrica ou uma instância que implementa __call__. O que importa para o consumidor é a assinatura de chamada compatível.

Fluxo e assinaturas

Observe onde cada assinatura atua.

Diagrama mostrando um texto entrando em aplicar, que aceita Callable de str para str. À esquerda, uma fábrica recebe uma configuração e retorna uma closure de str para str; à direita, uma instância chamável recebe str e retorna str. Ambos convergem para aplicar e produzem texto.

A fábrica retorna uma transformação; a instância também pode ser a transformação. Ambas precisam aceitar str e retornar str.

Prática local: complete e corrija

Monte o arquivo

No seu computador, crie um arquivo chamado fluxo.py com o código abaixo. Complete as duas anotações marcadas por ??? usando Callable.

Depois, descomente a última linha e execute python -m mypy --strict fluxo.py. Corrija a incompatibilidade na origem: a transformação fornecida a aplicar deve produzir texto. Não use Any, cast nem Callable[..., str] como atalho.

fluxo.py

python
from collections.abc import Callable


def aplicar(texto: str, transformacao: ???) -> str:
    return transformacao(texto)


def criar_prefixo(prefixo: str) -> ???:
    def transformar(texto: str) -> str:
        return f"{prefixo}{texto}"

    return transformar


class Normalizador:
    def __init__(self, largura: int) -> None:
        self.largura = largura

    def __call__(self, texto: str) -> str:
        return " ".join(texto.split()).ljust(self.largura)


def medir(texto: str) -> int:
    return len(texto)


com_prefixo = criar_prefixo("Aviso: ")
print(aplicar("  pronto  ", Normalizador(12)))
print(aplicar("concluído", com_prefixo))
# Descomente para observar o diagnóstico e corrija a origem:
# print(aplicar("concluído", medir))

Dica

Critério para a correção

O erro não está em aplicar: ela tem o direito de usar o resultado como str. Se medir for usada como transformação nesse fluxo, adapte ou substitua essa função para que seu retorno seja str. Se a intenção for conservar uma medida numérica, ela pertence a outro contrato de chamada.

Verifique e relate o resultado

Resultado da sua verificação

Após ajustar o arquivo e rodar mypy, responda:

  1. Qual anotação você colocou no parâmetro de aplicar e no retorno de criar_prefixo?
  2. Qual era o diagnóstico ao usar medir?
  3. Por que Normalizador(12) é aceito?
  4. Por que Callable[..., str] não é uma correção apropriada aqui?

Escreva pelo menos 180 caracteres (0/180).

Síntese: contratos de chamada

Resumo

O que revisar ao usar Callable

Use a assinatura exigida pelo ponto que consome o chamável como referência.

  • Callable[[str], str] promete uma chamada que recebe um texto e produz um texto.
  • Uma fábrica pode receber configurações e retornar um Callable; o retorno da fábrica é a função, não o resultado da função.
  • Uma instância com __call__(self, texto: str) -> str atende ao mesmo contrato sem herdar de Callable.
  • A implementação fornecida deve aceitar as entradas previstas e retornar um valor compatível.
  • Quando os argumentos são conhecidos, preserve a lista explícita em vez de usar Callable[..., retorno].
  • Callable descreve a chamada; ele não valida valores em execução nem expressa atributos e outras operações exigidas do objeto.

Tutorial concluído

Parabéns! Você concluiu: Anotar funções recebidas e retornadas com Callable

Muito bem! Agora você consegue anotar callbacks e funções retornadas, verificar objetos chamáveis e localizar incompatibilidades pela assinatura. No próximo tutorial, você vai preservar relações entre tipos de entrada e saída com funções genéricas.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso