데코레이터와 pdb는 함께 사용하면 디버깅 작업을 크게 효율화할 수 있습니다. 데코레이터를 통해 특정 함수에만 자동으로 pdb를 적용할 수 있어, 코드 수정 없이 필요한 함수에 디버깅 모드를 설정할 수 있습니다.

이번 가이드에서는 pdb와 데코레이터를 함께 활용해 특정 함수에만 자동으로 디버거를 활성화하는 방법과, 실전에서 사용할 수 있는 예제를 소개합니다.


1. pdb를 자동으로 활성화하는 디버깅 데코레이터

아래 데코레이터는 디버깅 모드가 필요한 함수에만 자동으로 pdb를 적용하는 방법을 보여줍니다. 이 데코레이터는 함수가 호출될 때마다 자동으로 중단점을 설정하여 pdb 디버깅 모드로 진입하도록 합니다.

import pdb
from functools import wraps

def debug_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[DEBUG] Entering function '{func.__name__}' with args={args}, kwargs={kwargs}")
        pdb.set_trace()  # 디버깅 모드 활성화
        result = func(*args, **kwargs)
        print(f"[DEBUG] Exiting function '{func.__name__}' with result={result}")
        return result
    return wrapper

@debug_decorator
def calculate_total(price, tax):
    total = price + (price * tax)
    return total

# 실제 실행
calculate_total(100, 0.1)

설명: @debug_decorator 데코레이터는 calculate_total 함수가 호출될 때마다 pdb를 활성화하여, 함수 내에서 변수를 확인하고 코드 흐름을 추적할 수 있도록 합니다. 함수 입출력 상태를 자동으로 기록하므로, 함수의 시작과 종료 시점에서 변수 상태를 추적하기에 유용합니다.


2. 특정 조건에서만 pdb 디버깅 활성화하기

때로는 특정 조건을 충족할 때만 디버거가 활성화되도록 설정하고 싶을 때가 있습니다. 예를 들어, 특정 인자를 받을 때만 디버깅 모드를 활성화하도록 데코레이터에 조건을 추가할 수 있습니다.

def conditional_debug_decorator(condition):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if condition(*args, **kwargs):
                print(f"[CONDITIONAL DEBUG] '{func.__name__}' activated for debugging.")
                pdb.set_trace()  # 조건을 만족할 때만 디버거 활성화
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 특정 조건 함수: tax가 0.1일 때만 디버깅 활성화
def debug_condition(price, tax):
    return tax == 0.1

@conditional_debug_decorator(debug_condition)
def calculate_discounted_total(price, tax):
    total = price + (price * tax)
    return total

# 실제 실행
calculate_discounted_total(100, 0.1)  # 조건에 맞아 디버깅 활성화
calculate_discounted_total(100, 0.2)  # 디버깅 비활성화

설명: conditional_debug_decorator는 인자가 특정 조건을 만족할 때만 pdb를 활성화합니다. 이렇게 조건부 디버깅을 설정하면 특정 상황에서만 함수 내부를 집중적으로 추적할 수 있습니다. 위 코드에서는 tax0.1일 때만 디버깅 모드로 진입하도록 설정했습니다.


3. 오류 발생 시 자동 디버깅 모드 활성화 데코레이터

코드가 오류를 발생시킬 때 자동으로 pdb 디버깅 모드로 진입하게 하는 데코레이터입니다. 이 방법은 예기치 않은 예외가 발생할 경우 문제가 발생한 즉시 디버깅을 시작할 수 있어 매우 유용합니다.

def error_debug_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"[ERROR DEBUG] Exception in '{func.__name__}': {e}")
            pdb.set_trace()  # 예외 발생 시 디버거 활성화
            raise e  # 예외를 다시 발생시켜 호출자에게 전달
    return wrapper

@error_debug_decorator
def risky_division(a, b):
    return a / b

# 실제 실행
risky_division(10, 2)  # 정상 실행
risky_division(10, 0)  # ZeroDivisionError 발생 시 디버깅 모드 진입

설명: @error_debug_decorator는 함수 실행 중 예외가 발생했을 때 자동으로 pdb를 활성화해, 오류 발생 시점에서 변수 상태와 코드 흐름을 즉시 확인할 수 있습니다. 이 데코레이터는 예외 디버깅에 매우 유용합니다.


4. 함수 호출 흐름을 한눈에 살펴보는 pdb와 로깅 데코레이터 결합

다양한 함수가 서로 호출될 때 함수 호출 순서를 확인하는 것은 매우 중요합니다. 아래는 각 함수 호출을 기록하고, 필요할 때 pdb로 중단해 호출 흐름과 변수 상태를 한꺼번에 추적하는 데코레이터입니다.

def trace_and_debug_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[TRACE] Entering '{func.__name__}' with args={args}, kwargs={kwargs}")
        pdb.set_trace()  # 함수 호출 시 디버깅 활성화
        result = func(*args, **kwargs)
        print(f"[TRACE] Exiting '{func.__name__}' with result={result}")
        return result
    return wrapper

@trace_and_debug_decorator
def add(a, b):
    return a + b

@trace_and_debug_decorator
def multiply(x, y):
    return x * y

@trace_and_debug_decorator
def main():
    result1 = add(3, 5)
    result2 = multiply(result1, 10)
    return result2

# 실제 실행
main()

설명: trace_and_debug_decorator는 함수가 호출될 때마다 pdb를 활성화하여 각 함수의 입출력을 기록하고, 함수 호출 흐름과 변수를 추적할 수 있도록 합니다. 이 방법은 서로 호출하는 여러 함수에서 코드 흐름을 추적할 때 특히 유용합니다.


결론: pdb와 데코레이터로 디버깅을 자동화하고 효율화하기

이제 pdb와 데코레이터를 결합해 필요한 함수에만 자동으로 디버거를 적용하거나, 조건부로 디버깅을 활성화하는 방법을 알게 되었습니다. 이 방식은 코드 수정 없이 필요한 함수에만 pdb를 적용할 수 있어, 더욱 유연하고 효율적인 디버깅을 가능하게 합니다.

이제 print 대신 pdb와 데코레이터를 통해 디버깅을 체계화하고 자동화해보세요!

pdb는 Python의 내장 디버거로, 코드를 실행하면서 특정 지점에서 멈추고 그 시점의 변수 상태를 확인하거나 코드 흐름을 단계별로 추적할 수 있는 강력한 도구입니다. 특히 복잡한 코드나 예기치 못한 오류가 발생하는 부분을 디버깅할 때 pdb를 활용하면 문제의 원인을 빠르게 파악할 수 있습니다.

아래에서는 pdb 사용 방법을 기본부터 설명하고, 실제 코드에서 유용하게 쓸 수 있는 예제를 통해 pdb의 효과적인 사용법을 알아보겠습니다.


1. pdb 기본 사용법: 중단점 설정으로 코드 흐름 추적하기

pdb의 가장 기본적인 기능은 중단점(breakpoint)을 설정해 코드 실행을 멈추고 상태를 점검하는 것입니다. pdb.set_trace()를 사용하면 원하는 지점에서 코드 실행이 멈춰, 변수 값이나 상태를 확인할 수 있습니다.

import pdb

def divide(a, b):
    pdb.set_trace()  # 코드가 이 지점에서 멈추고 디버깅 모드로 전환됩니다.
    return a / b

# 실제 실행
print(divide(10, 2))

pdb 사용 방법

  • 코드가 pdb.set_trace() 부분에서 멈추면, 디버깅 모드로 전환됩니다.
  • 명령어 예시:
    • n (next): 현재 줄을 실행하고 다음 줄로 이동
    • s (step): 함수 내부로 진입하여 실행 흐름을 단계별로 확인
    • c (continue): 다음 중단점까지 실행
    • q (quit): 디버깅 종료

이렇게 pdb를 통해 중단점을 설정하면, 각 변수의 현재 상태를 직접 확인하고 코드의 흐름을 추적할 수 있습니다.


2. pdb로 특정 오류 위치 추적하기

특정 코드에서 예기치 않은 오류가 발생할 경우, try-except 블록을 사용하여 예외 발생 시 pdb를 활성화할 수 있습니다. 이 방식은 오류가 발생한 정확한 위치에서 디버깅을 시작할 수 있어 문제를 신속히 파악하는 데 도움을 줍니다.

import pdb

def divide(a, b):
    return a / b

try:
    result = divide(10, 0)  # ZeroDivisionError 발생
except Exception:
    pdb.post_mortem()  # 예외 발생 시 디버깅 모드로 전환

이 예제는 ZeroDivisionError가 발생하면 자동으로 pdb가 실행되어, 오류가 발생한 시점의 변수 값과 코드 흐름을 추적할 수 있습니다. 이를 통해 디버깅 속도를 높일 수 있습니다.


3. pdb의 주요 명령어와 예제

pdb의 기본 명령어는 디버깅할 때 코드 흐름을 단계별로 조작하는 데 사용됩니다. 자주 사용하는 몇 가지 명령어와 사용 예제를 소개합니다.

  • n (next): 현재 줄을 실행하고 다음 줄로 이동합니다.
  • s (step): 함수 내부로 진입하여 코드 흐름을 확인합니다.
  • c (continue): 다음 중단점까지 코드를 실행합니다.
  • p <변수명>: 특정 변수의 현재 값을 출력합니다. 예: p a
  • l (list): 주변 코드 줄을 표시하여 코드 흐름을 이해하는 데 도움을 줍니다.

예제

import pdb

def calculate_total(price, tax):
    total = price + (price * tax)
    pdb.set_trace()  # 코드가 여기서 멈추고 디버깅 모드로 전환됩니다.
    return total

# 실행
print(calculate_total(100, 0.1))

pdb.set_trace()에서 실행이 멈추면 p price, p tax, p total 등을 입력하여 각 변수의 값을 즉시 확인할 수 있습니다. 이를 통해 계산 과정에서 오류가 발생하는지 쉽게 파악할 수 있습니다.


4. 조건부 중단점 설정하기

코드에서 특정 조건이 충족될 때만 중단점을 설정하고 싶다면, 조건부 중단점을 활용할 수 있습니다. 이렇게 설정하면 모든 반복에서 중단되지 않고, 특정 조건을 충족할 때만 멈추므로 효율적인 디버깅이 가능합니다.

import pdb

def check_number(num):
    if num == 5:
        pdb.set_trace()  # num이 5일 때만 중단점이 활성화됩니다.
    return num * 2

# 실제 실행
for i in range(10):
    print(check_number(i))

이 예제에서는 num5일 때만 pdb가 활성화됩니다. 특정 상황에서만 멈추고 싶을 때 조건부 중단점을 활용하면 코드 흐름을 정확히 파악할 수 있습니다.


5. 함수 호출 흐름을 따라가는 pdb 디버깅

pdb는 함수 호출 구조를 한 줄씩 따라가며 확인할 수 있어, 전체 코드 흐름을 시각적으로 파악하는 데 유용합니다. 함수가 다른 함수 내에서 호출되는 상황에서 특히 유용합니다.

import pdb

def multiply(a, b):
    return a * b

def divide(a, b):
    pdb.set_trace()  # 중단점 설정
    return a / b

def calculate():
    result1 = multiply(5, 4)
    result2 = divide(result1, 2)
    return result2

# 실제 실행
print(calculate())

pdb.set_trace()가 설정된 divide 함수에서 코드가 멈추면, ns 명령어를 통해 각 함수가 호출되는 과정을 한 단계씩 추적할 수 있습니다. 이를 통해 함수 호출 흐름을 보다 정확하게 이해할 수 있습니다.


6. pdb 대신 사용할 수 있는 대안: ipdb와 VSCode 디버거

pdb의 기본 기능에 더해 자동완성과 더 나은 UI를 제공하는 ipdbpdb의 대안으로 자주 사용됩니다. ipdbpdb와 동일한 명령어로 작동하나, 더 편리하게 사용할 수 있습니다.

# ipdb 설치
pip install ipdb

설치 후 pdb.set_trace() 대신 ipdb.set_trace()를 사용하면, 보다 직관적인 디버깅을 경험할 수 있습니다.

또한, VSCode와 같은 IDE의 디버거도 강력한 대안입니다. VSCode 디버거는 코드 라인에서 클릭 한 번으로 중단점을 설정하고, 모든 변수를 직관적으로 확인할 수 있는 UI를 제공합니다. 여러 파일이나 모듈을 한 번에 디버깅해야 하는 복잡한 프로젝트에서 특히 유용합니다.


결론: pdb를 활용한 효율적인 디버깅 습관

이제 pdb의 기본 사용법과 함께 조건부 중단점, 오류 발생 시 자동 디버깅, 함수 호출 흐름 추적 등 다양한 기능을 알게 되었습니다. Python의 내장 디버거인 pdb는 코드의 흐름을 정확히 이해하고 변수 상태를 확인하면서 문제를 빠르게 파악하는 데 큰 도움이 됩니다. 이제 print 디버깅에서 벗어나 pdb로 한층 더 효율적인 디버깅을 시작해보세요!

+ Recent posts