Trilha de aprendizado · Nível 11 · Tutorial 5

Criar objetos chamáveis com __call__

Ao concluir, você poderá criar instâncias que funcionam como operações chamáveis e usar seu estado para configurar ou acompanhar chamadas.

  • Nível: Intermediário
  • Duração: 12 min
  • 7 passos
Criar objetos chamáveis com __call__

O que você vai percorrer

  1. Distinguir a criação da instância de sua chamada Veja como os parênteses têm efeitos diferentes ao chamar uma classe e ao chamar uma instância que implementa __call__. 1 min
  2. Definir os argumentos e o retorno da operação Separe a configuração guardada no objeto dos valores recebidos em cada chamada. 2 min
  3. Acompanhar chamadas com estado por instância Amplie o Multiplicador para registrar chamadas sem misturar o estado de objetos diferentes. 2 min
  4. Passar o objeto para uma função de alta ordem Use uma instância de Multiplicador diretamente em map e observe quando suas chamadas realmente acontecem. 2 min
  5. Entender o que callable verifica Use callable para consultar se um objeto pode ser chamado, sem confundir essa consulta com uma execução bem-sucedida. 1 min
  6. Escolher entre função, closure e objeto chamável Escolha a abstração mais simples de acordo com a necessidade de configuração, estado e interface pública. 1 min
  7. Aplicar e verificar um objeto chamável completo Implemente e execute um exemplo completo para conferir retornos, contadores independentes, reinicialização e uso com map. 3 min

O que você vai aprender

  • Implementar __call__ com parâmetros e retorno adequados à operação.
  • Passar uma instância chamável para uma função de alta ordem.
  • Verificar os efeitos de chamadas repetidas e a independência do estado entre instâncias.
  • Justificar a escolha de um objeto chamável em vez de uma função ou closure.

Antes de começar

  • Definir representações com __repr__ e __str__
  • Passar funções como argumentos
  • Criar closures com estado e nonlocal
  • Testar mudanças de estado em objetos

Passo 1 de 7

Distinguir a criação da instância de sua chamada

Veja como os parênteses têm efeitos diferentes ao chamar uma classe e ao chamar uma instância que implementa call.

Dois usos dos parênteses

Classe cria; instância executa

Uma classe pode definir __call__ para dar às suas instâncias o comportamento de uma operação chamável.

No exemplo, Multiplicador(3) chama a classe e cria um objeto configurado com fator 3. Depois, triplo(10) chama a instância já criada: o Python executa triplo.__call__(10).

Criação e execução são momentos diferentes

Observe que o primeiro resultado é um objeto; o segundo é o valor transformado por esse objeto.

Diagrama em duas etapas: a classe Multiplicador recebe o número 3 e produz uma instância com fator 3; então essa mesma instância recebe o número 10 e produz 30.

Chamar a classe constrói a instância. Chamar a instância aciona sua operação.

Um Multiplicador chamável

Classe e uso

Execute este código no seu Python para observar os dois resultados.

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator

    def __call__(self, valor):
        return self.fator * valor

triplo = Multiplicador(3)
print(triplo)
print(triplo(10))

Exemplo

O que acontece

triplo = Multiplicador(3) executa __init__ e atribui uma instância à variável triplo.

triplo(10) executa __call__ nessa instância e devolve 30. Como a classe não definiu __str__ ou __repr__, a primeira impressão mostra a representação padrão do objeto, com um endereço que varia em cada execução.

Identifique cada chamada

Classe ou instância?

Associe cada expressão ao efeito principal.

Toque em um item e depois no par correspondente.

Passo 2 de 7

Definir os argumentos e o retorno da operação

Separe a configuração guardada no objeto dos valores recebidos em cada chamada.

Configuração e execução em lugares diferentes

Dois tipos de dado

No Multiplicador, o fator é uma configuração persistente: ele é recebido uma vez em __init__ e fica em self.fator.

Já valor e ajuste pertencem a cada execução de __call__. Ao escrever dobro(5), você não passa self: Python associa automaticamente a instância a esse primeiro parâmetro.

Fluxo de uma chamada

A instância conserva o fator; cada chamada fornece seu próprio valor e, opcionalmente, um ajuste.

Diagrama de um objeto Multiplicador que armazena o fator 2. Uma chamada recebe valor 5 e ajuste 1, calcula 2 vezes 5 mais 1 e devolve 11.

fator vem da configuração do objeto; valor e ajuste vêm da chamada.

Assinatura e resultado

Multiplicador com ajuste

O ajuste padrão é aplicado depois da multiplicação.

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator

    def __call__(self, valor, ajuste=0):
        return self.fator * valor + ajuste


dobro = Multiplicador(2)

print(dobro(5))           # 10
print(dobro(5, 1))        # 11
print(dobro(5, ajuste=3)) # 13

Dica

Retorno de __call__

__init__ inicializa a instância e deve retornar None. Já __call__ é uma operação comum: pode usar return para fazer com que a expressão dobro(...) produza um resultado.

Identifique os argumentos

Configuração ou chamada?

Considere triplo = Multiplicador(3) e depois triplo(4, ajuste=2). Quais valores são argumentos da execução de __call__?

Complete a expressão de retorno

Resultado da chamada

Complete o corpo de __call__ para que Multiplicador(4)(3, ajuste=2) devolva 14:

return self.fator * valor ___ ajuste

Passo 3 de 7

Acompanhar chamadas com estado por instância

Amplie o Multiplicador para registrar chamadas sem misturar o estado de objetos diferentes.

Contador pertencente a cada objeto

Configuração, retorno e estado

Além do fator configurado na criação, cada Multiplicador pode guardar quantas chamadas foram concluídas. O atributo chamadas começa em 0 em __init__, portanto pertence a cada instância.

Em __call__, primeiro calcule o resultado. Em seguida, incremente o contador e devolva o resultado. Assim, uma chamada contabilizada é aquela cujo cálculo chegou ao fim com sucesso.

Três papéis em uma chamada

Observe o que permanece, o que é devolvido e o que muda.

Diagrama de uma instância Multiplicador com fator 3 preservado, entrada 4, resultado 12 devolvido e contador mudando de 0 para 1.

O fator é configuração conservada; o produto é o retorno; chamadas é o estado atualizado.

Multiplicador que conta execuções

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator
        self.chamadas = 0

    def __call__(self, valor, ajuste=0):
        resultado = self.fator * valor + ajuste
        self.chamadas += 1
        return resultado

    def reiniciar(self):
        self.chamadas = 0

Duas instâncias, dois estados

Exemplo

Chamadas intercaladas

por_dois = Multiplicador(2)
por_dez = Multiplicador(10)

print(por_dois(3))       # 6
print(por_dez(3))        # 30
print(por_dois(4))       # 8

print(por_dois.chamadas) # 2
print(por_dez.chamadas)  # 1

As instâncias usam a mesma definição de classe, mas cada uma conserva seu próprio fator e seu próprio chamadas.

Linha do tempo por instância

As chamadas foram intercaladas, mas cada faixa registra apenas o objeto correspondente.

Duas faixas de linha do tempo. A primeira instância, fator 2, tem contador 0, depois 1 após uma chamada, permanece 1 durante a chamada da outra instância e chega a 2 após nova chamada. A segunda, fator 10, vai de 0 para 1 apenas em sua própria chamada.

Uma chamada em por_dez não altera o contador de por_dois, e vice-versa.

Reiniciar somente quem recebeu a operação

Um método público para o estado

reiniciar() altera somente self.chamadas: o contador da instância que recebeu a chamada. Ele não muda o fator e não alcança outras instâncias.

Verificação rápida

python
por_dois.reiniciar()

print(por_dois.chamadas)  # 0
print(por_dez.chamadas)   # 1
print(por_dois.fator)     # 2

Dica

Ordem intencional

Incrementar depois do cálculo deixa explícito que o contador representa cálculos concluídos. Se o cálculo falhar antes dessa linha, a chamada não será contabilizada.

Pratique a sequência de estados

Ordene os estados de chamadas

Partindo de a = Multiplicador(2) e b = Multiplicador(5), ambos com contador 0, coloque os estados na ordem em que ocorrem após esta sequência: a(3), b(4), a.reiniciar().

  1. Após b(4): a.chamadas = 1; b.chamadas = 1
  2. Após a(3): a.chamadas = 1; b.chamadas = 0
  3. Após a.reiniciar(): a.chamadas = 0; b.chamadas = 1

Passo 4 de 7

Passar o objeto para uma função de alta ordem

Use uma instância de Multiplicador diretamente em map e observe quando suas chamadas realmente acontecem.

Passe a instância, não o resultado de uma chamada

Uma operação para map

map recebe primeiro uma operação chamável e depois uma fonte de valores. Como multiplicar é uma instância que implementa __call__, passe o próprio objeto: map(multiplicar, valores).

Não escreva multiplicar(): esses parênteses tentariam executar a operação antes de map fornecer um valor.

O fluxo de valores

Cada item da fonte é entregue à mesma instância. Com uma única fonte, map fornece um argumento por chamada.

Diagrama mostrando a lista 2, 4 e 6 entrando, um por vez, na mesma instância Multiplicador configurada com fator 3, e saindo como 6, 12 e 18; um contador interno avança a cada transformação.

map chama a mesma instância uma vez para cada valor consumido.

Consuma o resultado para executar as chamadas

Multiplicador usado por map

A assinatura aceita um valor por chamada; ajuste permanece com seu valor padrão de 0.

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator
        self.chamadas = 0

    def __call__(self, valor, ajuste=0):
        resultado = valor * self.fator + ajuste
        self.chamadas += 1
        return resultado

multiplicar_por_3 = Multiplicador(3)
valores = [2, 4, 6]

transformados = list(map(multiplicar_por_3, valores))

print(transformados)                 # [6, 12, 18]
print(multiplicar_por_3.chamadas)    # 3

Dica

Por que usar list?

map(...) produz um iterador. Ao fazer list(...), você o consome integralmente agora: os resultados são criados e as três chamadas são contabilizadas. Antes desse consumo, não espere que todas as transformações já tenham ocorrido.

Complete a integração

A instância como argumento

Complete o primeiro argumento para transformar [1, 5] com a instância multiplicar_por_3:

resultado = list(map(_____, [1, 5]))

Passo 5 de 7

Entender o que callable verifica

Use callable para consultar se um objeto pode ser chamado, sem confundir essa consulta com uma execução bem-sucedida.

Uma consulta, não uma execução

O que callable responde

callable(objeto) consulta se o objeto pode ser usado com parênteses. O resultado é True ou False, e essa consulta não executa a operação.

Por isso, verificar callable(mult2) não multiplica valor nenhum nem altera o contador de chamadas da instância.

O escopo de callable

A consulta verifica a possibilidade geral de chamada; os argumentos e o resultado só entram em cena quando você realmente chama o objeto.

Diagrama mostrando callable consultando se função, classe e instância com __call__ podem ser chamadas, sem executar nenhuma delas; chamadas posteriores aparecem separadas, uma com resultado e outra com erro.

callable pergunta “pode ser chamado?” — não pergunta “esta chamada específica vai funcionar?”.

O que costuma ser chamável

Função, classe e instância

Execute este exemplo no Python:

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator

    def __call__(self, valor):
        return self.fator * valor


def dobrar(numero):
    return numero * 2

mult3 = Multiplicador(3)

print(callable(dobrar))          # True: função
print(callable(Multiplicador))   # True: classe cria instâncias
print(callable(mult3))           # True: instância tem __call__
print(callable(42))              # False

Atenção

Classe chamável não torna toda instância chamável

Uma classe usual é chamável porque Classe(...) tenta criar uma instância. Já uma instância só será chamável se sua classe definir __call__. Portanto, callable(Multiplicador) e callable(mult3) são consultas independentes.

True não garante uma chamada bem-sucedida

Assinatura e execução continuam importando

Mesmo com callable(mult3) igual a True, estas chamadas têm resultados diferentes:

python
print(callable(mult3))  # True
print(mult3(5))         # 15

# mult3()               # TypeError: falta o argumento valor
# mult3(5, 2)           # TypeError: há um argumento posicional extra


def inverter(numero):
    return 1 / numero

print(callable(inverter))  # True
# print(inverter(0))       # ZeroDivisionError durante a execução

Dica

Separe três perguntas

  1. callable(objeto): o objeto pode ser chamado?
  2. objeto(argumentos): os argumentos combinam com a assinatura?
  3. Durante a operação: o cálculo consegue terminar sem exceção?

Um True responde apenas à primeira pergunta.

Verifique sua interpretação

Consulta sem efeito

Se mult3 é uma instância de Multiplicador com __call__, executar callable(mult3) incrementa seu contador de chamadas.

Limite da verificação

Se callable(inverter) retorna True, então inverter(0) necessariamente retorna um número.

Passo 6 de 7

Escolher entre função, closure e objeto chamável

Escolha a abstração mais simples de acordo com a necessidade de configuração, estado e interface pública.

A escolha depende da interface necessária

Não existe uma opção sempre melhor

Comece pela alternativa mais simples que expressa o problema:

  • Função: uma transformação direta, sem estado próprio persistente.
  • Closure: uma chamada que conserva uma configuração ou um estado, mas cuja interface continua concentrada em chamar.
  • Objeto chamável: uma operação que, além de ser chamada, expõe estado ou operações públicas relacionadas.

Ter estado não obriga o uso de uma classe. A pergunta é: esse estado precisa fazer parte de uma interface que outras pessoas possam consultar ou controlar?

Matriz de decisão

Compare as necessidades antes de escolher.

Matriz visual comparando função, closure e objeto chamável conforme configuração persistente, estado observável e operações públicas adicionais.

Função para operação direta; closure para estado ou configuração restritos à chamada; objeto chamável quando o estado e ações relacionadas compõem uma interface.

Critérios concretos com Multiplicador

Exemplo

Três necessidades, três escolhas possíveis

1. Dobrar um número uma vez

def dobrar(valor):
    return valor * 2

Uma função é suficiente: não há configuração variável nem estado para acompanhar.

2. Criar uma operação configurada pelo fator
Uma closure pode ser adequada quando a única interface necessária é chamar a operação resultante, como por_tres(5).

3. Multiplicar e acompanhar o uso
Um Multiplicador chamável faz sentido se o programa precisa consultar chamadas e executar reiniciar(). Nesse caso, fator, contador e reinicialização pertencem à mesma interface.

O contador sozinho não decide a escolha: se ele ficar inteiramente interno e ninguém precisar consultá-lo ou reiniciá-lo, uma closure ainda pode ser uma boa solução.

Dica

Critério prático

Escolha pelo que o código consumidor precisa usar, não pelo desejo de “usar classes”. Se ele só chama uma operação, função ou closure tende a ser mais direta. Se ele também consulta e controla o estado relacionado, o objeto chamável pode deixar essa relação explícita.

Justifique pela necessidade

Qual abstração você escolheria?

Um programa cria operações de desconto com percentuais diferentes. Cada operação será chamada muitas vezes para calcular preços, mas o programa não precisa consultar quantas chamadas ocorreram nem reiniciar qualquer estado.

Você escolheria uma função, uma closure ou um objeto chamável? Justifique usando as necessidades da interface.

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7

Aplicar e verificar um objeto chamável completo

Implemente e execute um exemplo completo para conferir retornos, contadores independentes, reinicialização e uso com map.

Execute um exemplo completo

Roteiro local

No seu editor, crie um arquivo կոչado multiplicador.py, copie o código abaixo e execute com Python 3. Os asserts não exibem nada quando passam; ao final, os prints mostram o estado confirmado.

Observe que fator é a configuração persistente, chamadas é o estado de cada instância e valor/ajuste pertencem a cada execução.

multiplicador.py

python
class Multiplicador:
    def __init__(self, fator):
        self.fator = fator
        self.chamadas = 0

    def __call__(self, valor, ajuste=0):
        resultado = valor * self.fator + ajuste
        self.chamadas += 1
        return resultado

    def reiniciar(self):
        self.chamadas = 0


dobro = Multiplicador(2)
triplo = Multiplicador(3)

# Valor padrão de ajuste e argumento nomeado.
assert dobro(5) == 10
assert dobro(valor=4, ajuste=1) == 9
assert dobro.chamadas == 2

# A outra instância tem configuração e contador próprios.
assert triplo(5) == 15
assert triplo.chamadas == 1

# Reiniciar afeta somente o objeto que recebeu a chamada.
dobro.reiniciar()
assert dobro.chamadas == 0
assert triplo.chamadas == 1

# map recebe a instância, sem parênteses: ele a chamará para cada valor.
resultados = list(map(dobro, [1, 2, 3]))
assert resultados == [2, 4, 6]
assert dobro.chamadas == 3
assert triplo.chamadas == 1

print(resultados)          # [2, 4, 6]
print(dobro.chamadas)      # 3
print(triplo.chamadas)     # 1

Confira retornos e estados

Duas instâncias, dois estados

A sequência de chamadas deixa claro que o retorno de __call__ e o contador são coisas diferentes: o retorno é entregue imediatamente; o contador permanece no objeto que foi chamado.

Diagrama mostrando as instâncias dobro e triplo com fatores, chamadas realizadas e estados finais independentes.

Depois do roteiro: dobro produziu [2, 4, 6] em map e terminou com 3 chamadas; triplo continuou com 1 chamada.

Dica

Por que não usar dobro()?

Em map(dobro, [1, 2, 3]), você passa o objeto chamável. O map fornece um valor por vez e executa algo equivalente a dobro(1), dobro(2) e dobro(3). Já map(dobro(), ...) tentaria chamar dobro antes, sem o argumento obrigatório valor.

Revisão e aplicação final

Explique o que você verificou

Após executar o script, descreva o que os asserts e prints verificaram. Explique por que dobro, e não dobro(), foi passado para map. Por fim, justifique por que um objeto chamável é adequado aqui quando o contador e reiniciar fazem parte da interface.

Escreva pelo menos 180 caracteres (0/180).

Resumo

Competência consolidada

  • Implementei __call__ com argumentos por chamada, valor padrão e retorno calculado.
  • Usei atributos da instância para manter configuração e contabilizar chamadas de forma independente.
  • Passei uma instância chamável diretamente para map e materializei o resultado com list.
  • Usei assert para verificar retornos e mudanças de estado.
  • callable indicaria que o objeto pode ser chamado, mas não validaria os argumentos nem garantiria que a execução terá sucesso.
  • Uma função basta para uma transformação direta; uma closure pode concentrar estado interno; um objeto chamável é especialmente útil quando estado observável e operações públicas, como reiniciar, formam uma interface coerente.

Tutorial concluído

Parabéns! Você concluiu: Criar objetos chamáveis com __call__

Você concluiu a criação e a verificação de objetos chamáveis com __call__. Agora consegue combinar configuração persistente, estado por instância, chamadas diretas e integração com funções de alta ordem.

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