Python 에서 함수 호출 여부 확인 방법

PythonBeginner
지금 연습하기

소개

이 랩에서는 디버깅, 성능 분석, 그리고 테스트에 필수적인 기술인 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 함수가 호출될 때마다 증가시켰습니다. 또한, 총 호출 횟수를 표시하기 위해 마지막에 print 문을 추가했습니다.

동일한 명령을 사용하여 스크립트를 다시 실행합니다.

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.")

이 코드에서:

  • 함수 (func) 를 입력으로 받는 greet_wrapper 함수를 정의합니다.
  • 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_countgreetadd 모두에 대한 총 호출 횟수를 추적합니다.

스크립트를 다시 실행합니다.

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 모듈을 가져옵니다.
  • greet 함수를 래핑하는 mock.Mock 객체를 생성합니다. wraps 인수는 모의 객체가 호출될 때 원래 greet 함수를 호출하도록 지시합니다.
  • 그런 다음 원래 greet 함수 대신 mock_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 에서 함수 호출을 모니터링하는 강력하고 유연한 방법을 제공합니다. 이는 테스트, 디버깅 및 성능 분석을 위한 귀중한 도구입니다.

요약

이 Lab 에서는 Python 에서 디버깅, 성능 분석 및 테스트를 위해 함수 호출을 추적하는 것의 중요성을 이해하는 것으로 시작했습니다. 인사말을 출력하는 간단한 greet 함수를 생성한 다음, 전역 변수 invocation_count를 도입하고 함수 내에서 이를 증가시켜 함수가 호출된 횟수를 수동으로 추적했습니다. 이를 통해 함수가 실행된 횟수를 모니터링할 수 있었습니다.