简介
在这个实验中,你将学习如何检查 Python 中的函数是否被调用,这是调试、性能分析和测试的一项关键技能。本实验将探索跟踪函数调用的技术,包括函数被调用的次数和使用的参数。
实验从一个简单的 Python 函数示例开始,介绍一种基本技术,即使用全局变量来手动跟踪函数调用并统计调用次数。然后,指导你修改代码,使每次调用函数时计数器递增,并显示调用的总次数。实验还将继续探索更高级的函数调用监控方法,例如创建包装函数和使用 unittest.mock
模块。
在这个实验中,你将学习如何检查 Python 中的函数是否被调用,这是调试、性能分析和测试的一项关键技能。本实验将探索跟踪函数调用的技术,包括函数被调用的次数和使用的参数。
实验从一个简单的 Python 函数示例开始,介绍一种基本技术,即使用全局变量来手动跟踪函数调用并统计调用次数。然后,指导你修改代码,使每次调用函数时计数器递增,并显示调用的总次数。实验还将继续探索更高级的函数调用监控方法,例如创建包装函数和使用 unittest.mock
模块。
在这一步中,我们将探讨在 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
将跟踪 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
两个函数的总调用次数。包装函数提供了一种简洁且可复用的方式来监控函数调用。
在这一步中,你将探索如何使用 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_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
对象的列表,每个对象代表对被模拟函数的一次调用。你可以使用 call
对象的 args
和 kwargs
属性来访问每次调用的参数。
例如,让我们修改代码以打印第一次调用的参数:
## ~/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
并在函数内部对其进行递增操作,手动跟踪了该函数的调用次数。这样你就能监控该函数的执行次数了。