|
| 1 | +# 80. pdb 를 사용해 대화형으로 디버깅하라 |
| 2 | + |
| 3 | +## 1. 버그 찾기 |
| 4 | + |
| 5 | +- 출력을 통해 버그 찾기 |
| 6 | +- 테스트를 작성하여 버그 찾기 |
| 7 | +- 파이선 내장 대화형 디버거를 사용하여 버그 찾기 |
| 8 | + |
| 9 | +## 2. 파이선 내장 대화형 디버거 pdb |
| 10 | + |
| 11 | +- 문제를 조사하기에 가장 적합한 지점에서 프로그램이 디버거를 초기화할 수 있게 프로그램 변경 |
| 12 | + - `breakpoint` 내장 함수 호출 |
| 13 | + - `pdb` 내장 모듈 임포트 |
| 14 | + - `set_trace` 함수 실행 |
| 15 | +- breakpoint 함수 실행되자마자, 다음 줄로 실행이 옮겨지기 전에 프로그램 실행이 일시 중단 |
| 16 | +- 대화형 파이선 쉘 실행 |
| 17 | + |
| 18 | +```python |
| 19 | +# always_breakpoint.py |
| 20 | +import math |
| 21 | + |
| 22 | +def compute_rmse(observed, ideal): |
| 23 | + total_err_2 = 0 |
| 24 | + count = 0 |
| 25 | + for got, wanted in zip(observed, ideal): |
| 26 | + err_2 = (got - wanted) ** 2 |
| 27 | + breakpoint() # 여기서 디버거를 시작함 |
| 28 | + total_err_2 += err_2 |
| 29 | + count += 1 |
| 30 | + |
| 31 | + mean_err = total_err_2 / count |
| 32 | + rmse = math.sqrt(mean_err) |
| 33 | + return rmse |
| 34 | + |
| 35 | +result = compute_rmse( |
| 36 | + [1.8, 1.7, 3.2, 6], |
| 37 | + [2, 1.5, 3, 5]) |
| 38 | +print(result) |
| 39 | +``` |
| 40 | + |
| 41 | +```python |
| 42 | +$ python3 always_breakpoint.py |
| 43 | +> always_breakpoint.py(12)compute_rmse() |
| 44 | +-> total_err_2 += err_2 |
| 45 | +(Pdb) |
| 46 | +``` |
| 47 | + |
| 48 | +- `(Pdb)` 프롬프트에서 `p` 명령을 통해 지역 변수 이름을 입력하면 변수에 저장된 값 출력 |
| 49 | +- `locals` 내장 함수 호출하면 모든 지역 변수 목록을 볼 수 있음 |
| 50 | +- `(Pdb)` 프롬프트에서 모듈 임포트, 새 객체 생성, `help` 내장함수 실행, 프로그램 일부 변경 가능 |
| 51 | +- `Pdb` 명령 |
| 52 | + - `where` |
| 53 | + - 현재 실행 중인 프로그램의 호출 스택 출력 |
| 54 | + - 프로그램의 현재 위치 파악 가능, `breakpoint` 트리거 발동 위치 파악 가능 |
| 55 | + - `up`, `u` |
| 56 | + - 실행 호출 스택에서 현재 관찰 중인 함수를 호출한 쪽으로 호출 스택 영역을 이동해서 해당 함수의 지역 변수 관찰 가능 |
| 57 | + - `down`, `d` |
| 58 | + - 실행 호출 스택에서 한 수준 아래로 호출 스택 영역 이동 |
| 59 | + - `step` |
| 60 | + - 다음 줄까지 실행한 다음 제어를 디버거로 돌려서 디버거 프롬프트 표시 |
| 61 | + - 소스 코드 다음 줄에 함수를 호출하는 부분이 있다면 해당 함수의 첫 줄에서 디버거로 제어가 돌아옴 |
| 62 | + - `next` |
| 63 | + - `step` 과 유사, 차이점은 함수 호출 부분에서 해당 함수 반환 후 제어가 디버거로 돌아옴 |
| 64 | + - `return` |
| 65 | + - 현재 함수에서 반환될 때까지 프로그램을 실행 한 후 제어가 디버거로 돌아옴 |
| 66 | + - `continue` |
| 67 | + - 다음 중단점에 도달할 때까지 프로그램을 계속 실행 |
| 68 | + - breakpoint 호출이나 디버거에서 설정한 중단점에서 제어가 디버거에게 돌아옴 |
| 69 | + - 실행하는 중에 중단점을 만나지 못하면 프로그램 계속 실행 |
| 70 | + - `quit` |
| 71 | + - 디버거에서 나가면서 프로그램 중단 |
| 72 | + |
| 73 | +- `breakpoint` 함수는 프로그램 어디서든 호출 가능 |
| 74 | + |
| 75 | +```python |
| 76 | +# conditional_breakpoint.py |
| 77 | +def compute_rmse(observed, ideal): |
| 78 | + ... |
| 79 | + for got, wanted in zip(observed, ideal): |
| 80 | + err_2 = (got - wanted) ** 2 |
| 81 | + if err_2 >= 1: # True인 경우에만 디버거를 실행함 |
| 82 | + breakpoint() |
| 83 | + total_err_2 += err_2 |
| 84 | + count += 1 |
| 85 | + ... |
| 86 | +result = compute_rmse( |
| 87 | + [1.8, 1.7, 3.2, 7], |
| 88 | + [2, 1.5, 3, 5]) |
| 89 | +print(result) |
| 90 | +``` |
| 91 | + |
| 92 | +```python |
| 93 | +$ python3 conditional_breakpoint.py |
| 94 | +> conditional_breakpoint.py(14)compute_rmse() |
| 95 | +-> total_err_2 += err_2 |
| 96 | +(Pdb) wanted |
| 97 | +5 |
| 98 | +(Pdb) got |
| 99 | +7 |
| 100 | +(Pdb) err_2 |
| 101 | +4 |
| 102 | +``` |
| 103 | + |
| 104 | +- 사후 디버깅(post-mortem debugging) |
| 105 | + - 예외 발생이나 프로그램 비정상 종류 이후 디버깅 가능 |
| 106 | +- `python3 -m pdb -c continue [프로그램 경로]` |
| 107 | + - `pdb` 모듈이 프로그램 실행을 제어하게 할 수 있음 |
| 108 | + - `continue` 명령은 `pdb`에게 프로그램을 즉시 시작하라고 명령 |
| 109 | + - 프로그램 실행 후 문제가 발생하면 대화형 디버거로 제어 이전 그 시점부터 코드 상태 관찰 가능 |
| 110 | + |
| 111 | +```python |
| 112 | +# postmortem_breakpoint.py |
| 113 | +import math |
| 114 | + |
| 115 | +# 평균 제곱근 오차(root# mean square error)를 구함 |
| 116 | +def compute_rmse(observed, ideal): |
| 117 | + ... |
| 118 | + |
| 119 | +result = compute_rmse( |
| 120 | + [1.8, 1.7, 3.2, 7j], # 잘못된 입력 |
| 121 | + [2, 1.5, 3, 5]) |
| 122 | +print(result) |
| 123 | +``` |
| 124 | + |
| 125 | +```python |
| 126 | +$ python3 -m pdb -c continue postmortem_breakpoint.py |
| 127 | +Traceback (most recent call last): |
| 128 | + File ".../pdb.py", line 1697, in main |
| 129 | + pdb._runscript(mainpyfile) |
| 130 | + File ".../pdb.py", line 1566, in _runscript |
| 131 | + self.run(statement) |
| 132 | + File ".../bdb.py", line 585, in run |
| 133 | + exec(cmd, globals, locals) |
| 134 | + File "<string>", line 1, in <module> |
| 135 | + File "postmortem_breakpoint.py", line 4, in <module> |
| 136 | + import math |
| 137 | + File "postmortem_breakpoint.py", line 16, in compute_rmse |
| 138 | + rmse = math.sqrt(mean_err) |
| 139 | +TypeError: can't convert complex to float |
| 140 | +Uncaught exception. Entering post mortem debugging |
| 141 | +Running 'cont' or 'step' will restart the program |
| 142 | +> postmortem_breakpoint.py(16)compute_rmse() |
| 143 | +-> rmse = math.sqrt(mean_err) |
| 144 | +(Pdb) mean_err |
| 145 | +(-5.97-17.5j) |
| 146 | +``` |
| 147 | + |
| 148 | +- 프로그램을 실행하는 중에 예외가 발생했는데 처리되지 않은 경우 |
| 149 | + - `pdb` 모듈의 `pm` 함수를 호출하면 사후 디버깅 사용 가능 |
| 150 | + |
| 151 | +```python |
| 152 | +$ python3 |
| 153 | +>>> import my_module |
| 154 | +>>> my_module.compute_stddev([5]) |
| 155 | +Traceback (most recent call last): |
| 156 | + File "<stdin>", line 1, in <module> |
| 157 | + File "my_module.py", line 17, in compute_stddev |
| 158 | + variance = compute_variance(data) |
| 159 | + File "my_module.py", line 13, in compute_variance |
| 160 | + variance = err_2_sum / (len(data) - 1) |
| 161 | +ZeroDivisionError: float division by zero |
| 162 | +>>> import pdb; pdb.pm() |
| 163 | +> my_module.py(13)compute_variance() |
| 164 | +-> variance = err_2_sum / (len(data) - 1) |
| 165 | +(Pdb) err_2_sum |
| 166 | +0.0 |
| 167 | +(Pdb) len(data) |
| 168 | +1 |
| 169 | +``` |
0 commit comments