如何检查 Python 函数是否被调用

PythonPythonBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在这个实验中,你将学习如何检查 Python 中的函数是否被调用,这是调试、性能分析和测试的一项关键技能。本实验将探索跟踪函数调用的技术,包括函数被调用的次数和使用的参数。

实验从一个简单的 Python 函数示例开始,介绍一种基本技术,即使用全局变量来手动跟踪函数调用并统计调用次数。然后,指导你修改代码,使每次调用函数时计数器递增,并显示调用的总次数。实验还将继续探索更高级的函数调用监控方法,例如创建包装函数和使用 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 中跟踪函数调用的基本概念。了解一个函数被调用的次数以及使用了哪些参数,对于调试、性能分析和测试至关重要。我们将从一个简单的示例开始,然后介绍一种手动跟踪函数调用的基本技术。

让我们首先使用 VS Code 编辑器在 ~/project 目录中创建一个简单的 Python 函数。创建一个名为 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.

这个简单的示例展示了一种跟踪函数调用的基本方法。然而,对于具有许多函数的更复杂应用程序,这种方法可能会变得繁琐。在接下来的步骤中,我们将探索使用包装函数和 unittest.mock 模块的更复杂技术。

创建包装函数

在这一步中,你将学习如何使用包装函数来跟踪函数调用。包装函数是一种包裹另一个函数的函数,它允许你在原函数执行前后添加额外的功能。这是一种强大的技术,可用于监控函数调用,而无需修改原函数的代码。

让我们继续使用上一步中的 greet 函数。你将创建一个包装函数来跟踪 greet 函数的调用次数。

修改 ~/project 目录下的 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 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_wrapper(greet) 将包装器应用到 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 将跟踪 greetadd 两个函数的总调用次数。

再次运行脚本:

python ~/project/my_function.py

你应该会看到类似以下的输出:

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

如你所见,invocation_count 现在反映了 greetadd 两个函数的总调用次数。包装函数提供了一种简洁且可复用的方式来监控函数调用。

使用 unittest.mock 监控调用

在这一步中,你将探索如何使用 unittest.mock 模块来监控函数调用。unittest.mock 模块是用于测试和调试的强大工具,它提供了一种便捷的方式来跟踪函数调用、参数和返回值。

让我们继续使用前面步骤中的 greet 函数。你将使用 unittest.mock 来监控对 greet 函数的调用。

修改 ~/project 目录下的 my_function.py 文件,使其包含以下代码:

## ~/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}")

在这段代码中:

  • unittest 导入 mock 模块。
  • 创建一个 mock.Mock 对象,它包装了 greet 函数。wraps 参数告诉模拟对象在被调用时调用原始的 greet 函数。
  • 然后调用 mock_greet 对象,而不是原始的 greet 函数。
  • 最后,使用模拟对象的 call_countcall_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 对象的列表,每个对象代表对被模拟函数的一次调用。你可以使用 call 对象的 argskwargs 属性来访问每次调用的参数。

例如,让我们修改代码以打印第一次调用的参数:

## ~/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 中监控函数调用提供了强大而灵活的方法。它是用于测试、调试和性能分析的宝贵工具。

总结

在本次实验中,你首先了解了在 Python 里跟踪函数调用对于调试、性能分析和测试的重要性。你创建了一个简单的 greet 函数,该函数用于打印问候语,然后通过引入一个全局变量 invocation_count 并在函数内部对其进行递增操作,手动跟踪了该函数的调用次数。这样你就能监控该函数的执行次数了。