Как проверить, был ли вызван функция в Python

PythonPythonBeginner
Практиковаться сейчас

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом практическом занятии (lab) вы узнаете, как проверить, был ли вызван функция в Python. Это важный навык для отладки, анализа производительности и тестирования. В рамках занятия рассматриваются методы отслеживания вызовов функций, включая количество раз, когда функция была вызвана, и аргументы, которые были использованы.

Занятие начинается с простого примера функции на Python и представляет базовую технику для ручного отслеживания вызовов функций с использованием глобальной переменной для подсчета вызовов. Затем вас научат модифицировать код для увеличения счетчика каждый раз, когда функция вызывается, и отображать общее количество вызовов. В дальнейшем занятие будет продолжать исследовать более продвинутые методы мониторинга вызовов функций, такие как создание оберток (wrapper functions) и использование модуля unittest.mock.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/BasicConceptsGroup -.-> python/variables_data_types("Variables and Data Types") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/variables_data_types -.-> lab-559518{{"Как проверить, был ли вызван функция в Python"}} python/function_definition -.-> lab-559518{{"Как проверить, был ли вызван функция в Python"}} python/arguments_return -.-> lab-559518{{"Как проверить, был ли вызван функция в Python"}} python/decorators -.-> lab-559518{{"Как проверить, был ли вызван функция в Python"}} end

Понимание отслеживания вызовов функций

На этом этапе мы рассмотрим основные концепции отслеживания вызовов функций в Python. Понимание того, сколько раз функция была вызвана и с какими аргументами, является важным для отладки, анализа производительности и тестирования. Мы начнем с простого примера, а затем представим базовую технику для ручного отслеживания вызовов функций.

Начнем с создания простой функции на Python в директории ~/project с использованием редактора VS Code. Создайте файл с именем my_function.py и добавьте следующий код:

## ~/project/my_function.py
def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Example usage
greet("Alice")
greet("Bob")

Этот код определяет функцию greet, которая принимает имя в качестве входного параметра и выводит приветствие. Затем он вызывает функцию дважды с разными именами.

Чтобы запустить этот скрипт, откройте терминал в VS Code (обычно это можно сделать, нажав Ctrl + `` или Cmd + ``) и выполните следующую команду:

python ~/project/my_function.py

Вы должны увидеть следующий вывод:

Hello, Alice!
Hello, Bob!

Теперь изменим файл my_function.py для отслеживания количества вызовов функции greet. Мы введем глобальную переменную для отслеживания количества вызовов.

Измените файл my_function.py, добавив следующий код:

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

def greet(name):
  """This function greets the person passed in as a parameter."""
  global invocation_count
  invocation_count += 1
  print(f"Hello, {name}!")

## Example usage
greet("Alice")
greet("Bob")

print(f"The greet function was called {invocation_count} times.")

В этой модифицированной версии мы добавили глобальную переменную invocation_count и увеличиваем ее каждый раз, когда функция greet вызывается. Мы также добавили инструкцию вывода в конце для отображения общего количества вызовов.

Запустите скрипт еще раз, используя ту же команду:

python ~/project/my_function.py

Теперь вы должны увидеть следующий вывод:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.

Этот простой пример демонстрирует базовый способ отслеживания вызовов функций. Однако этот подход может стать громоздким для более сложных приложений с большим количеством функций. В следующих шагах мы рассмотрим более сложные методы с использованием оберток (wrapper functions) и модуля unittest.mock.

Создание обертки (wrapper function)

На этом этапе мы узнаем, как использовать обертки (wrapper functions) для отслеживания вызовов функций. Обертка - это функция, которая "оборачивает" другую функцию, позволяя добавить функциональность до или после выполнения исходной функции. Это мощный метод для мониторинга вызовов функций без изменения кода исходной функции.

Продолжим работу с функцией greet из предыдущего шага. Мы создадим обертку, которая будет отслеживать количество вызовов функции greet.

Измените файл my_function.py в директории ~/project, добавив следующий код:

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

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

def greet_wrapper(func):
  """This is a wrapper function that tracks the number of times the wrapped function is called."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Apply the wrapper to the greet function
greet = greet_wrapper(greet)

## Example usage
greet("Alice")
greet("Bob")

print(f"The greet function was called {invocation_count} times.")

В этом коде:

  • Мы определяем функцию greet_wrapper, которая принимает функцию (func) в качестве входного параметра.
  • Внутри greet_wrapper мы определяем другую функцию с именем wrapper. Эта функция wrapper является фактической оберткой, которая будет выполняться при вызове обернутой функции.
  • Функция wrapper увеличивает invocation_count, вызывает исходную функцию (func) с любыми переданными ей аргументами и возвращает результат.
  • Затем мы применяем обертку к функции greet, присвоив greet = greet_wrapper(greet). Это заменяет исходную функцию greet на обернутую версию.

Запустите скрипт еще раз, используя ту же команду:

python ~/project/my_function.py

Вы должны увидеть следующий вывод:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.

Этот вывод аналогичен тому, что был на предыдущем этапе, но теперь мы используем обертку для отслеживания вызовов. Этот подход более гибок, так как вы можете легко применить ту же обертку к нескольким функциям.

Теперь добавим еще одну функцию и применим к ней ту же обертку. Добавьте следующую функцию в файл my_function.py:

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

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

def add(x, y):
  """This function adds two numbers and returns the result."""
  print(f"Adding {x} and {y}")
  return x + y

def greet_wrapper(func):
  """This is a wrapper function that tracks the number of times the wrapped function is called."""
  def wrapper(*args, **kwargs):
    global invocation_count
    invocation_count += 1
    result = func(*args, **kwargs)
    return result
  return wrapper

## Apply the wrapper to the greet function
greet = greet_wrapper(greet)
add = greet_wrapper(add)

## Example usage
greet("Alice")
greet("Bob")
add(5, 3)

print(f"The greet function was called {invocation_count} times.")

Мы добавили функцию add и также применили к ней обертку greet_wrapper. Теперь invocation_count будет отслеживать общее количество вызовов как функции greet, так и функции add.

Запустите скрипт еще раз:

python ~/project/my_function.py

Вы должны увидеть вывод, похожий на следующий:

Hello, Alice!
Hello, Bob!
Adding 5 and 3
The greet function was called 3 times.

Как вы можете видеть, invocation_count теперь отражает общее количество вызовов как функции greet, так и функции add. Обертки (wrapper functions) предоставляют чистый и повторно используемый способ мониторинга вызовов функций.

Использование unittest.mock для мониторинга вызовов функций

На этом этапе мы рассмотрим, как использовать модуль unittest.mock для мониторинга вызовов функций. Модуль unittest.mock представляет собой мощный инструмент для тестирования и отладки, который предоставляет удобный способ отслеживать вызовы функций, аргументы и возвращаемые значения.

Продолжим работу с функцией greet из предыдущих этапов. Мы будем использовать unittest.mock для мониторинга вызовов функции greet.

Измените файл my_function.py в директории ~/project, добавив следующий код:

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

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Create a mock object for the greet function
mock_greet = mock.Mock(wraps=greet)

## Example usage
mock_greet("Alice")
mock_greet("Bob")

## Print the number of times the function was called
print(f"The greet function was called {mock_greet.call_count} times.")

## Print the arguments the function was called with
print(f"The calls were: {mock_greet.call_args_list}")

В этом коде:

  • Мы импортируем модуль mock из unittest.
  • Создаем объект mock.Mock, который оборачивает функцию greet. Аргумент wraps сообщает объекту-заглушке (mock object), что при вызове он должен вызывать исходную функцию greet.
  • Затем мы вызываем объект mock_greet вместо исходной функции greet.
  • В конце мы используем атрибуты call_count и call_args_list объекта-заглушки для получения информации о вызовах функции.

Запустите скрипт еще раз, используя ту же команду:

python ~/project/my_function.py

Вы должны увидеть вывод, похожий на следующий:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.
The calls were: [call('Alice'), call('Bob')]

Этот вывод показывает, что функция greet была вызвана дважды, и также отображает аргументы, переданные в функцию при каждом вызове.

Теперь рассмотрим call_args_list более подробно. Это список объектов call, каждый из которых представляет отдельный вызов заглушки (mocked function). Вы можете получить доступ к аргументам каждого вызова, используя атрибуты args и kwargs объекта call.

Например, изменим код, чтобы вывести аргументы первого вызова:

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

def greet(name):
  """This function greets the person passed in as a parameter."""
  print(f"Hello, {name}!")

## Create a mock object for the greet function
mock_greet = mock.Mock(wraps=greet)

## Example usage
mock_greet("Alice")
mock_greet("Bob")

## Print the number of times the function was called
print(f"The greet function was called {mock_greet.call_count} times.")

## Print the arguments the function was called with
print(f"The calls were: {mock_greet.call_args_list}")

## Print the arguments of the first call
if mock_greet.call_args_list:
  print(f"The arguments of the first call were: {mock_greet.call_args_list[0].args}")

Запустите скрипт еще раз:

python ~/project/my_function.py

Вы должны увидеть вывод, похожий на следующий:

Hello, Alice!
Hello, Bob!
The greet function was called 2 times.
The calls were: [call('Alice'), call('Bob')]
The arguments of the first call were: ('Alice',)

Этот вывод показывает, что первый вызов функции greet был выполнен с аргументом "Alice".

Модуль unittest.mock предоставляет мощный и гибкий способ мониторинга вызовов функций в Python. Это ценный инструмент для тестирования, отладки и анализа производительности.

Резюме

В этом практическом занятии (lab) мы начали с понимания важности отслеживания вызовов функций для отладки, анализа производительности и тестирования в Python. Мы создали простую функцию greet, которая выводит приветствие, и затем вручную отслеживали количество ее вызовов, введя глобальную переменную invocation_count и увеличивая ее внутри функции. Это позволило нам отслеживать, сколько раз функция была выполнена.