파이썬의 `breakpoint()`

`breakpoint()` 소개: 현대 파이썬에 내장된 디버깅 초능력

왜 알아둬야 하나

파이썬 3.7부터 소박한 breakpoint() 호출이 언어에 그대로 들어왔다. 여기저기 import pdb; pdb.set_trace()를 흩뿌릴 필요 없이 디버거로 즉시 들어갈 수 있다. 실행을 멈추고, 이것저것 들여다보고, 아무 일 없었다는 듯 재개하는 가장 빠른 방법이다.

시간을 멈추는 한 줄

def weird_math(x):
    result = (x ** 2) // 3
    breakpoint()          # ↓ 프로그램이 여기서 멈춘다
    return result + 42

스크립트를 실행하면 파이썬이 그 줄에서 익숙한 pdb 프롬프트로 데려다준다. 이제 지역 변수를 확인하고(p locals()), 변수를 바꾸고(x = 99), 한 줄씩 진행하거나(n / s) 계속 실행할 수 있다(c).

팁: 탐색용 코드에는 breakpoint()를 마음껏 뿌려도 되고, 나중에 import 정리 없이 그냥 지우면 된다.

pdb / ipdb 명령어 치트시트

아래는 간결하지만 빠짐없는 참고표다.

명령 긴 형태 하는 일 짧은 예시
n next 현재 프레임의 다음 줄 실행 (함수 본문은 건너뜀). len() 안으로 들어가지 않고 for 루프 진행
s step 다음 함수 호출 안으로 진입. calculate()가 왜 None을 주는지 안으로 들어가 보기
c continue 다음 breakpoint(또는 프로그램 끝)까지 재개. 변수만 확인하고 디버거 빠져나오기
l list 현재 줄 주변 소스 표시. 줄 번호를 붙이면 → l 120. “내가 지금 정확히 어디지?”
ll — 현재 함수 전체 표시. 50줄짜리 핸들러의 전체 문맥 훑기
p expr print expr 표현식을 평가하고 출력. p len(data)
pp expr prettyprint expr pprint.pformat으로 객체를 예쁘게 출력. pp settings
! stmt — 임의의 파이썬 문장 실행. ! filtered = [x*2 for x in data]
b line break line 현재 파일에 breakpoint 설정. b 210
b file:line — 다른 파일에 breakpoint. b utils.py:45
b func — 함수 진입점에 breakpoint. b DataLoader.load
b — 모든 breakpoint과 번호 나열. 빠른 개관
cl num clear num num 번 breakpoint 제거. cl 3
disable num / enable num — 삭제하지 않고 breakpoint 토글. 비싼 중단을 잠시 건너뛰기
tbreak line — 일회성 breakpoint. 한 번 걸리면 자동 해제. 루프가 처음 도는 순간만 멈추기
u up 한 프레임 위로 이동 (호출자). 호출 스택에서 인자 확인
d down 한 프레임 아래로 이동 (피호출자). 방금 들어갔던 프레임으로 복귀
args — 현재 프레임의 함수 인자 출력. def foo(a, b=3) 안에서 args
where / w — 전체 스택 트레이스 (현재 줄은 →로 표시). 호출 사슬 스냅샷
bt backtrace where의 별칭.  
retval — 스텝 중 마지막 return이 반환한 값 표시. parse()가 무엇을 만들었는지 확인
q quit 디버그 세션 중단하고 프로그램 종료. 비상 탈출
jump line j line 실행 커서를 line 으로 이동 (같은 파일). 위험하지만 강력하다. 분기 건너뛰기: jump 180
run args — 주어진 인자로 프로그램 재시작. CLI 스크립트에서 run --fast 500
debug expr — 표현식을 자체 미니 디버거에서 평가. debug some_generator()
help help topic 내장 도움말 (help break, help list, …). 미묘한 플래그 발견하기

팁: 어떤 명령이든 앞에 반복 횟수를 붙일 수 있다. 10 n은 열 줄을 건너뛰고, 2 u는 두 프레임을 올라간다.

내부에서 벌어지는 일

breakpoint()는 그저 sys.breakpointhook()를 호출할 뿐이고, 기본 구현이 pdb를 띄운다. 그래서 이런 게 가능하다:

  • 모든 breakpoint을 동적으로 비활성화:

    export PYTHONBREAKPOINT=0      # POSIX 셸
    set PYTHONBREAKPOINT=0         # 윈도우 cmd
    

    모든 breakpoint()가 아무 일도 하지 않게 된다. CI 파이프라인에서 유용하다.

  • 다른 디버거로 교체 (예: ipdb, pudb, web-pdb):

    export PYTHONBREAKPOINT=ipdb.set_trace
    

    이제 같은 코드 줄이 소스 파일을 건드리지 않고도 원하는 디버거를 연다.

  • 런타임에 커스텀 훅 제공:

    import sys, logging
    def log_only(*args, **kwargs):
        logging.warning("breakpoint() hit!")
    sys.breakpointhook = log_only
    

실제로 쓰게 되는 응용 기법

목적 빠른 레시피
조건이 터질 때만 진입하기 if total < 0: breakpoint()
리스트 컴프리헨션 안에서 디버거 진입 lambda 사용: [f(x) if x>0 else (lambda: (breakpoint(), None)[1])() for x in data]
컨테이너 안에서 원격 디버깅 export PYTHONBREAKPOINT=remote_pdb.set_trace + remote-pdb 서버 실행
비동기 코드를 매끄럽게 진행 python -m pdb -m your_script와 조합하거나, 코루틴을 더 잘 이해하는 ipdb를 쓴다