Como Verificar se uma Função Foi Chamada em Python

PythonBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá como verificar se uma função foi chamada em Python, uma habilidade crucial para depuração (debugging), análise de desempenho e testes. O laboratório explora técnicas para rastrear invocações de funções, incluindo o número de vezes que uma função é chamada e os argumentos utilizados.

O laboratório começa com um exemplo simples de função Python e introduz uma técnica básica para rastrear manualmente as chamadas de função usando uma variável global para contar as invocações. Em seguida, orienta você a modificar o código para incrementar o contador cada vez que a função é chamada e exibir o número total de invocações. O laboratório continuará a explorar métodos mais avançados para monitorar chamadas de função, como a criação de funções wrapper e o uso do módulo unittest.mock.

Entenda o Rastreamento de Invocação de Funções

Nesta etapa, exploraremos o conceito fundamental de rastreamento de invocações de funções em Python. Entender quantas vezes uma função é chamada e com quais argumentos é crucial para depuração (debugging), análise de desempenho e testes. Começaremos com um exemplo simples e, em seguida, introduziremos uma técnica básica para rastrear manualmente as chamadas de função.

Vamos começar criando uma função Python simples no diretório ~/project usando o editor VS Code. Crie um arquivo chamado my_function.py e adicione o seguinte código:

## ~/project/my_function.py
def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  print(f"Olá, {name}!")

## Exemplo de uso
greet("Alice")
greet("Bob")

Este código define uma função greet que recebe um nome como entrada e imprime uma saudação. Em seguida, ele chama a função duas vezes com nomes diferentes.

Para executar este script, abra seu terminal no VS Code (você geralmente pode fazer isso pressionando Ctrl + `` ouCmd + ``) e execute o seguinte comando:

python ~/project/my_function.py

Você deve ver a seguinte saída:

Olá, Alice!
Olá, Bob!

Agora, vamos modificar o arquivo my_function.py para rastrear quantas vezes a função greet é chamada. Introduziremos uma variável global para controlar a contagem de invocações.

Modifique o arquivo my_function.py para incluir o seguinte código:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  global invocation_count
  invocation_count += 1
  print(f"Olá, {name}!")

## Exemplo de uso
greet("Alice")
greet("Bob")

print(f"A função greet foi chamada {invocation_count} vezes.")

Nesta versão modificada, adicionamos uma variável global invocation_count e a incrementamos cada vez que a função greet é chamada. Também adicionamos uma instrução print no final para exibir o número total de invocações.

Execute o script novamente usando o mesmo comando:

python ~/project/my_function.py

Você deve agora ver a seguinte saída:

Olá, Alice!
Olá, Bob!
A função greet foi chamada 2 vezes.

Este exemplo simples demonstra uma maneira básica de rastrear invocações de funções. No entanto, essa abordagem pode se tornar complicada para aplicações mais complexas com muitas funções. Nas próximas etapas, exploraremos técnicas mais sofisticadas usando funções wrapper e o módulo unittest.mock.

Crie uma Função Wrapper

Nesta etapa, aprenderemos como usar funções wrapper para rastrear invocações de funções. Uma função wrapper é uma função que envolve outra função, permitindo que você adicione funcionalidade antes ou depois que a função original for executada. Esta é uma técnica poderosa para monitorar chamadas de função sem modificar o código da função original.

Vamos continuar com a função greet da etapa anterior. Criaremos uma função wrapper que rastreia o número de vezes que greet é chamada.

Modifique o arquivo my_function.py no diretório ~/project para incluir o seguinte código:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  print(f"Olá, {name}!")

def greet_wrapper(func):
  """Esta é uma função wrapper que rastreia o número de vezes que a função envolvida é chamada."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Aplica o wrapper à função greet
greet = greet_wrapper(greet)

## Exemplo de uso
greet("Alice")
greet("Bob")

print(f"A função greet foi chamada {invocation_count} vezes.")

Neste código:

  • Definimos uma função greet_wrapper que recebe uma função (func) como entrada.
  • Dentro de greet_wrapper, definimos outra função chamada wrapper. Esta função wrapper é o wrapper real que será executado quando chamarmos a função envolvida.
  • A função wrapper incrementa o invocation_count, chama a função original (func) com quaisquer argumentos passados para ela e retorna o resultado.
  • Em seguida, aplicamos o wrapper à função greet atribuindo greet = greet_wrapper(greet). Isso substitui a função greet original pela versão envolvida.

Execute o script novamente usando o mesmo comando:

python ~/project/my_function.py

Você deve ver a seguinte saída:

Olá, Alice!
Olá, Bob!
A função greet foi chamada 2 vezes.

Esta saída é a mesma da etapa anterior, mas agora estamos usando uma função wrapper para rastrear as invocações. Essa abordagem é mais flexível porque você pode facilmente aplicar o mesmo wrapper a várias funções.

Agora, vamos adicionar outra função e aplicar o mesmo wrapper a ela. Adicione a seguinte função ao arquivo my_function.py:

## ~/project/my_function.py
invocation_count = 0

def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  print(f"Olá, {name}!")

def add(x, y):
  """Esta função soma dois números e retorna o resultado."""
  print(f"Somando {x} e {y}")
  return x + y

def greet_wrapper(func):
  """Esta é uma função wrapper que rastreia o número de vezes que a função envolvida é chamada."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Aplica o wrapper à função greet
greet = greet_wrapper(greet)
add = greet_wrapper(add)

## Exemplo de uso
greet("Alice")
greet("Bob")
add(5, 3)

print(f"A função greet foi chamada {invocation_count} vezes.")

Adicionamos uma função add e aplicamos o greet_wrapper a ela também. Agora, o invocation_count rastreará o número total de chamadas para greet e add.

Execute o script novamente:

python ~/project/my_function.py

Você deve ver uma saída semelhante a esta:

Olá, Alice!
Olá, Bob!
Somando 5 e 3
A função greet foi chamada 3 vezes.

Como você pode ver, o invocation_count agora reflete o número total de chamadas para as funções greet e add. As funções wrapper fornecem uma maneira limpa e reutilizável de monitorar as invocações de funções.

Use unittest.mock para Monitorar Chamadas

Nesta etapa, exploraremos como usar o módulo unittest.mock para monitorar chamadas de função. O módulo unittest.mock é uma ferramenta poderosa para testes e depuração (debugging), e fornece uma maneira conveniente de rastrear invocações de funções, argumentos e valores de retorno.

Vamos continuar com a função greet das etapas anteriores. Usaremos unittest.mock para monitorar as chamadas para greet.

Modifique o arquivo my_function.py no diretório ~/project para incluir o seguinte código:

## ~/project/my_function.py
from unittest import mock

def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  print(f"Olá, {name}!")

## Cria um objeto mock para a função greet
mock_greet = mock.Mock(wraps=greet)

## Exemplo de uso
mock_greet("Alice")
mock_greet("Bob")

## Imprime o número de vezes que a função foi chamada
print(f"A função greet foi chamada {mock_greet.call_count} vezes.")

## Imprime os argumentos com os quais a função foi chamada
print(f"As chamadas foram: {mock_greet.call_args_list}")

Neste código:

  • Importamos o módulo mock de unittest.
  • Criamos um objeto mock.Mock que envolve a função greet. O argumento wraps diz ao objeto mock para chamar a função greet original quando ela for chamada.
  • Em seguida, chamamos o objeto mock_greet em vez da função greet original.
  • Finalmente, usamos os atributos call_count e call_args_list do objeto mock para obter informações sobre as chamadas de função.

Execute o script novamente usando o mesmo comando:

python ~/project/my_function.py

Você deve ver uma saída semelhante a esta:

Olá, Alice!
Olá, Bob!
A função greet foi chamada 2 vezes.
As chamadas foram: [call('Alice'), call('Bob')]

Esta saída mostra que a função greet foi chamada duas vezes e também mostra os argumentos que foram passados para a função em cada chamada.

Agora, vamos examinar o call_args_list mais de perto. É uma lista de objetos call, cada um representando uma única chamada para a função mockada. Você pode acessar os argumentos de cada chamada usando os atributos args e kwargs do objeto call.

Por exemplo, vamos modificar o código para imprimir os argumentos da primeira chamada:

## ~/project/my_function.py
from unittest import mock

def greet(name):
  """Esta função cumprimenta a pessoa passada como parâmetro."""
  print(f"Olá, {name}!")

## Cria um objeto mock para a função greet
mock_greet = mock.Mock(wraps=greet)

## Exemplo de uso
mock_greet("Alice")
mock_greet("Bob")

## Imprime o número de vezes que a função foi chamada
print(f"A função greet foi chamada {mock_greet.call_count} vezes.")

## Imprime os argumentos com os quais a função foi chamada
print(f"As chamadas foram: {mock_greet.call_args_list}")

## Imprime os argumentos da primeira chamada
if mock_greet.call_args_list:
  print(f"Os argumentos da primeira chamada foram: {mock_greet.call_args_list[0].args}")

Execute o script novamente:

python ~/project/my_function.py

Você deve ver uma saída semelhante a esta:

Olá, Alice!
Olá, Bob!
A função greet foi chamada 2 vezes.
As chamadas foram: [call('Alice'), call('Bob')]
Os argumentos da primeira chamada foram: ('Alice',)

Esta saída mostra que a primeira chamada para greet foi feita com o argumento "Alice".

O módulo unittest.mock fornece uma maneira poderosa e flexível de monitorar chamadas de função em Python. É uma ferramenta valiosa para testes, depuração e análise de desempenho.

Resumo

Neste laboratório, começamos entendendo a importância de rastrear invocações de funções para depuração (debugging), análise de desempenho e testes em Python. Criamos uma função greet simples que imprime uma saudação e, em seguida, rastreamos manualmente o número de vezes que ela foi chamada, introduzindo uma variável global invocation_count e incrementando-a dentro da função. Isso nos permitiu monitorar quantas vezes a função foi executada.