├── .gitignore ├── LICENSE.txt ├── README.md ├── dicegame ├── __init__.py ├── die.py ├── runner.py └── utils.py ├── instructions.txt ├── main.py └── solutions ├── dicegame ├── __init__.py ├── die.py ├── runner.py └── utils.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # Extra 28 | *.sw[po] 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Spiro Sideris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `pdb` 튜토리얼 2 | 3 | 이 튜토리얼의 목적은 [Python 2](https://docs.python.org/2/library/pdb.html)와 [Python 3](https://docs.python.org/3/library/pdb.html)를 위한 파이썬 디버거인 `pdb` (**P**ython **D**e**B**ugger)의 기초 사용법을 알려주는 것입니다. 4 | 또한 디버깅중에 활용할 수 있는 유용한 트릭들 또한 소개하고 있습니다. 이 튜토리얼은 Python 2.7과 Python 3.4에 최적화 되어 있으며 버전간 `pdb` 명령어가 다른 경우에는 차이점을 강조하여 표시하고 있습니다. 다음 명령어를 통해 여러분의 머신에 설치되어 있는 파이썬의 버전을 확인할 수 있습니다. 5 | 6 | ```shell 7 | python --version 8 | ``` 9 | 10 | 버전을 확인했다면 튜토리얼을 시작해봅시다! 11 | 12 | 13 | ## 디버거의 목적 14 | 15 | 코드로 넘어가기 전에 디버깅과 디버깅 도구 활용의 중요성에 대해 간단한 브리핑을 해보겠습니다. 필자가 생각하는 디버거의 중요성 세 가지는 다음과 같습니다. 16 | 17 | 디버거를 사용하면 다음의 것들을 할 수 있습니다: 18 | 19 | * 실행중인 프로그램의 상태를 들여다 볼 수 있다 20 | * 적용하기 전에 구현 코드를 테스트 해볼 수 있다 21 | * 프로그램의 실행 로직을 따라갈 수 있다 22 | 23 | 디버거를 사용하면 프로그램의 모든 지점에서 [중단점(breakpoint)](https://en.wikipedia.org/wiki/Breakpoint)를 설정하여 프로그램을 중단하고 위 세 가지를 적용할 수 있습니다. 디버거는 아주 강력한 도구이며 디버거를 사용하면 사방에 `print()`문을 사용하는 것보다 디버깅 과정을 훨씬 빠르게 처리할 수 있습니다. 24 | 25 | 베테랑 프로그래머 여러분이라면, 최고의 프로그래머와 효과적으로 디버깅하는 방법을 알고있는 프로그래머 사이에 상관 관계가 있다는 것에 동의할 것입니다. 효과적으로 디버깅을 한다는 것은 프로그램의 문제를 진단하고 최소한의 어려움으로 오류를 처리 할 수 있다는 것을 의미합니다. 26 | 디버거를 사용하고 디버거를 올바르게 사용하는 방법을 배우면 효과적인 디버깅을 할 수 있는 프로그래머가 될 수 있습니다. 디버깅 환경에서 어려움없이 편하게 코드를 탐색할 수 있는데까지는 어느 정도 시간이 필요할테지만 이 튜토리얼의 목적은 여러분의 코드베이스에서 직접 `pdb`를 사용해보기전에 디버거 활용에 대한 발판을 마련해주는 것입니다. 27 | 28 | 29 | ## 게임 플레이 30 | 31 | 우리는 디버거의 목적에 대해서 알게되었고 이제 이를 실행에 옮길 시간입니다. 우선, 이 레포지토리를 클론합니다. `git`이 설치되어 있지 않다면 [여기](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)에서 `git`을 다운로드 받으십시오. 다른 소스 컨트롤 시스템을 사용해도 무관하나 `git`을 추천합니다. `git`이 설치되었다면 다음 명령어를 통해 레포지토리를 클론합니다. 32 | 33 | ```shell 34 | git clone https://github.com/spiside/pdb-tutorial 35 | ``` 36 | 37 | **참고**: 위 명령어가 동작하지 않는다면 Github의 [클론 튜토리얼](https://help.github.com/articles/cloning-a-repository/)을 따라해보세요. 38 | 39 | 이제 레포지토리를 클론 했으므로 프로젝트의 루트로 이동하여 지침서(`instructions.txt`)를 살펴보겠습니다. 40 | 41 | ```shell 42 | cd /path/to/pdb-tutorial 43 | ``` 44 | 45 | `file: instructions.txt` 46 | ``` 47 | Your boss has given you the following project to fix for a client. It's supposed to be a simple dice game where the object of the game is to correctly add up the values of the dice for 6 consecutive turns. 48 | 49 | The issue is that a former programmer worked on it and didn't know how to debug effectively. It's now up to you to fix the errors and finally make the game playable. 50 | 51 | To play the game you must run the main.py file. 52 | 53 | 여러분은 보스로부터 다음 프로젝트를 수정하라는 임무를 받았습니다. 이 프로그램은 간단한 주사위 게임이며 게임의 목적은 6번의 연속턴에서 나온 주사위 값들의 합을 맞추는 것입니다. 54 | 55 | 문제는 이 프로그램을 작성한 전 프로그래머가 디버깅을 효과적으로하는 방법을 몰랐다는겁니다. 이제 여러분이 이를 고쳐야하며 최종적으로 정상적인 게임 플레이가 가능하도록 만들어야합니다. 56 | 57 | 게임을 플레이하기 위해선 main.py 파일을 실행해야합니다. 58 | ``` 59 | 60 | 문제는 쉬워 보입니다! 시작하기 전에 무엇이 잘못되었는지 확인하기 위해 게임을 플레이해봅시다. 프로그램을 실행하려면 터미널에서 다음 명령어를 실행하세요: 61 | 62 | ```shell 63 | python main.py 64 | ``` 65 | 66 | 다음과 같은 결과를 보게 될 것입니다. 67 | 68 | ``` 69 | Add the values of the dice 70 | It's really that easy 71 | What are you doing with your life. 72 | Round 1 73 | 74 | --------- 75 | |* | 76 | | | 77 | | *| 78 | --------- 79 | --------- 80 | |* | 81 | | | 82 | | *| 83 | --------- 84 | --------- 85 | |* *| 86 | | | 87 | |* *| 88 | --------- 89 | --------- 90 | |* *| 91 | | | 92 | |* *| 93 | --------- 94 | --------- 95 | |* *| 96 | | * | 97 | |* *| 98 | --------- 99 | Sigh. What is your guess?: 100 | ``` 101 | 102 | 주사위 값들의 합이 17이므로 17을 입력해봅시다. 103 | 104 | ``` 105 | Sigh. What is your guess?: 17 106 | Sorry that's wrong 107 | The answer is: 5 108 | Like seriously, how could you mess that up 109 | Wins: 0 Loses 1 110 | Would you like to play again?[Y/n]: 111 | ``` 112 | 113 | 이상합니다. 답이 5라고 말하고 있지만 확실히 잘못되었습니다 .. 합산이 잘못된 것 같지만 한 번 더 플레이를 해봅시다. 다시 플레이 하라는 메시지가 `Y`인 것 같으니 한 번 입력해봅시다. 114 | 115 | ``` 116 | Would you like to play again?[Y/n]: Y 117 | Traceback (most recent call last): 118 | File "main.py", line 12, in 119 | main() 120 | File "main.py", line 8, in main 121 | GameRunner.run() 122 | File "/Users/Development/pdb-tutorial/dicegame/runner.py", line 62, in run 123 | i_just_throw_an_exception() 124 | File "/Users/Development/pdb-tutorial/dicegame/utils.py", line 13, in i_just_throw_an_exception 125 | raise UnnecessaryError("You actually called this function...") 126 | dicegame.utils.UnnecessaryError: You actually called this function... 127 | ``` 128 | 129 | 음 이상합니다. 우리가 입력한 값이 유효한 값인 것 같지만 프로그램은 예외를 발생시키고 있습니다. 프로그램이 망가졌다고 말해도 괜찮을 것 같으니 이제 디버깅을 시작해봅시다! 130 | 131 | 132 | ## PDB 101: `pdb` 소개 133 | 134 | 이제 드디어 파이썬의 고유 디버거인 `pdb`로 작업할 차례입니다. 디버거는 파이썬의 표준 라이브러리에 포함되어 있으며 다른 파이썬 라이브러리와 동일한 방법으로 사용하면 됩니다. 먼저, `pdb` 모듈을 임포트하고 프로그램에 디버깅 중단점을 추가하기 위한 메서드를 호출해봅시다. 이를 수행하는 일반적인 방법은 중지하려는 줄에서 임포트를 추가하고 같은 줄에서 동시에 메서드를 호출하는 것입니다. 이 한 줄이 포함시키려는 전체 명령입니다. 135 | 136 | ```python 137 | import pdb; pdb.set_trace() 138 | ``` 139 | 140 | [`set_trace()`](https://docs.python.org/3/library/pdb.html#pdb.set_trace) 메서드는 명령어가 위치하는 줄에서 중단점을 하드 코딩합니다. 이제 `main.py` 파일을 열고 8번째 줄에 중단점을 추가해봅시다. 141 | 142 | `file: main.py` 143 | ```python 144 | from dicegame.runner import GameRunner 145 | 146 | 147 | def main(): 148 | print("Add the values of the dice") 149 | print("It's really that easy") 150 | print("What are you doing with your life.") 151 | import pdb; pdb.set_trace() # add pdb here 152 | GameRunner.run() 153 | 154 | 155 | if __name__ == "__main__": 156 | main() 157 | ``` 158 | 159 | 훌륭합니다. `main.py`를 다시 실행하고 결과를 살펴봅시다. 160 | 161 | ```shell 162 | python main.py 163 | ``` 164 | ``` 165 | Add the values of the dice 166 | It's really that easy 167 | What are you doing with your life. 168 | > /Users/Development/pdb-tutorial/main.py(9)main() 169 | -> GameRunner.run() 170 | (Pdb) 171 | ``` 172 | 173 | 도착했습니다! 우리는 지금 실행중인 프로그램의 한가운데에 있고 프로그램을 추적할 수 있게 되었습니다. 우리가 해결해야할 첫 번째 문제는 주사위 값들의 올바른 합산 문제일 것 같습니다. 174 | 175 | 파이썬 인터프리터에 익숙하다면, 많은 인터프리터 지식을 pdb 디버거에서도 활용할 수 있습니다. 그러나 몇 가지 문제가 발생할 것입니다. 이에 대해서는 고급 섹션에서 살펴보겠습니다. 어쨌든 합산 문제를 해결하는데 도움이 되는 몇 가지 명령을 배워봅시다. 176 | 177 | ## 5가지 `pdb` 명령어로 여러분은 말문이 막힙니다 178 | 179 | 다음은 `pdb` 문서에서 바로 가져온 다섯 가지 명령어인데, 일단 한 번 배우기만 하면 이 명령어들 없이 어떻게 살았을까 할겁니다. 180 | 181 | 1. `l(ist)` - 현재 줄 주위의 11개의 줄을 표시하거나 이전 목록을 계속 표시합니다. 182 | 2. `s(tep)` - 현재 줄을 실행하고 현재 함수의 다음 줄에서 멈춥니다. 단, 다음 줄이 함수 호출인 경우, 호출된 함수로 들어갑니다. 183 | 3. `n(ext)` - 현재 함수의 다음 줄에 도달할 때까지 실행을 계속하거나 반환합니다. 184 | 4. `b(reak)` - 중단점을 설정합니다 (인자로 옵션을 줄 수 있음) 185 | 5. `r(eturn)` - 현재 함수의 리턴 직전까지 실행을 계속합니다. 186 | 187 | > step과 next의 차이점: 다음 줄이 함수 호출인 경우 step은 해당 함수 안으로 들어가서 멈추지만, next는 호출된 함수를 모두 실행하고 현재 함수의 다음 줄에서만 멈춤 188 | 189 | 모든 키워드의 마지막 부분에 있는 괄호는 명령어 단어의 나머지 부분을 나타내며 `pdb` 프롬프트에서 *선택적으로* 사용할 수 있습니다. 위 단축 명령어는 타이핑을 줄여주지만 프로그램에 `l`이나 `n`과 같은 변수명이 있는 경우엔 `pdb`의 명령어가 더 우선시됩니다. 즉, 프로그램에 `c`라는 변수가 있는 경우 `c`의 값을 알아내고자 `pdb`에서 `c`를 타이핑하면 중단점을 만날때까지 계속 실행되는 `c(ontinue)` 키워드가 실행됩니다. 190 | 191 | **참고**: 필자를 포함한 많은 프로그래머들은 `a`, `b`, `gme`와 같은 짧은 변수명의 사용을 지양합니다. 이런 변수명은 의미도 부실하며 코드를 읽기 어렵게 만듭니다. 필자는 짧은 변수명이 있는 상태에서 `pdb`를 사용할 때 발생할 수 있는 문제만 설명합니다. 192 | 193 | **참고**: 도움 명령어도 있습니다: `h(elp) - 인자 없이 사용하면 사용가능한 명령어의 리스트를 보여줍니다. 명령어를 인자로 사용하면 명령어에 대한 도움말을 출력합니다. ` 194 | 195 | 튜토리얼의 나머지 부분에서는 단축 명령어를 사용하겠습니다. 위에서 소개하지 않은 명령어를 사용할 경우 부연 설명을 붙일겁니다. 먼저 첫번째 명령어부터 시작해보겠습니다. 196 | 197 | ### 1. l(ist) - 소스 파일 열기가 귀찮습니다 198 | 199 | ``` 200 | l(ist) [first [,last]] 201 | List source code for the current file. Without arguments, list 11 lines around the current line 202 | or continue the previous listing. With one argument, list 11 lines starting at that line. 203 | With two arguments, list the given range; if the second argument is less than the first, it is a count. 204 | ``` 205 | 206 | **참고**: 위 설명은 `list`에 대해 `help`를 호출한 결과입니다. `pdb` 실행기에서 `help l`을 입력하면 됩니다. 207 | 208 | `list`를 사용하면 현재 파일의 소스 코드를 검사할 수 있습니다. `list`의 인자로 출력할 줄의 갯수를 지정할 수 있습니다. 이는 서드파티 패키지를 디버깅할 때 특히 유용합니다. 209 | 210 | **참고**: Python 3.2 이상에서는 현재 함수 혹은 프레임의 소스 코드를 보여주는 `ll` (long list) 명령어가 추가되었습니다. 필자는 항상 `l` 대신 `ll`을 사용하는데 현재 줄 주변의 임의의 11개의 줄을 보는것보다 훨씬 더 유용하기 때문입니다. 211 | 212 | 이제 `l`을 한 번 사용해봅시다. 이미 열려있는 `pdb` 프롬프트에서 `l`을 입력하고 출력값을 확인해보세요. 213 | 214 | ``` 215 | (Pdb) l 216 | 4 def main(): 217 | 5 print("Add the values of the dice") 218 | 6 print("It's really that easy") 219 | 7 print("What are you doing with your life.") 220 | 8 import pdb; pdb.set_trace() 221 | 9 -> GameRunner.run() 222 | 10 223 | 11 224 | 12 if __name__ == "__main__": 225 | 13 main() 226 | [EOF] 227 | ``` 228 | 229 | 전체 파일 내용을 보고 싶다면 1에서 13까지의 범위를 지정하여 list 함수를 호출할 수 있습니다: 230 | 231 | ``` 232 | (Pdb) l 1, 13 233 | 1 from dicegame.runner import GameRunner 234 | 2 235 | 3 236 | 4 def main(): 237 | 5 print("Add the values of the dice") 238 | 6 print("It's really that easy") 239 | 7 print("What are you doing with your life.") 240 | 8 import pdb; pdb.set_trace() 241 | 9 -> GameRunner.run() 242 | 10 243 | 11 244 | 12 if __name__ == "__main__": 245 | 13 main() 246 | ``` 247 | 248 | 안타깝게도 이 파일만으로는 많은 정보를 얻지 못했지만 `GameRunner` 클래스에서 `run()` 메서드를 호출하고 있음을 볼 수 있습니다. 이 부분에서, 여러분은 "오, 그럼 `dicegame/runner.py` 파일의 run 메서드에서 `pdb`를 설정하면 되겠네"라고 생각할 수도 있습니다. 그래도 되긴 하지만, 다음 섹션에서 다룰 `step` 커맨드를 사용하는 더 쉬운 방법이 있습니다. 249 | 250 | ### 2. `s(tep)` - 메서드가 무슨 일을 하는지 알고 싶습니다 251 | 252 | ``` 253 | s(tep) 254 | Execute the current line, stop at the first possible occasion 255 | (either in a function that is called or in the current 256 | function). 257 | ``` 258 | 259 | 현재 실행중인 줄은 `9`번째 줄입니다. 현재 위치는 `list` 명령어의 출력에서 `->`을 통해 알 수 있습니다. 260 | 261 | `step` 명령어를 호출하여 어떤일이 일어나는지 살펴봅시다. 262 | 263 | ``` 264 | (Pdb) s 265 | --Call-- 266 | > /Users/Development/pdb-tutorial/dicegame/runner.py(22)run() 267 | -> @classmethod 268 | ``` 269 | 270 | 잘했습니다! `> /Users/Development/pdb-tutorial/dicegame/runner.py(21)run()` 이 줄을 통해 우리가 현재 `runner.py` 파일의 22번째 줄에 있음을 알 수 있습니다. 271 | 272 | 문제는 컨텍스트 정보가 별로 없기 때문에 `list` 명령어를 실행해 메서드를 확인해봅시다. 273 | 274 | ``` 275 | (Pdb) l 276 | 16 total = 0 277 | 17 for die in self.dice: 278 | 18 total += 1 279 | 19 return total 280 | 20 281 | 21 -> @classmethod 282 | 22 def run(cls): 283 | 23 # Probably counts wins or something. 284 | 24 # Great variable name, 10/10. 285 | 25 c = 0 286 | 26 while True: 287 | ``` 288 | 289 | 와우! 이제 `run()` 메서드에 대한 컨텍스트를 조금 얻었지만, 우리는 아직 `22`번째 줄에 있습니다. 메서드에 진입하기 위해 한 번 더 step을 실행하고 현재 위치를 알 수 있도록 list를 실행해봅시다. 290 | 291 | ``` 292 | (Pdb) s 293 | > /Users/Development/pdb-tutorial/dicegame/runner.py(25)run() 294 | -> c = 0 295 | (Pdb) l 296 | 20 297 | 21 @classmethod 298 | 22 def run(cls): 299 | 23 # Probably counts wins or something. 300 | 24 # Great variable name, 10/10. 301 | 25 -> c = 0 302 | 26 while True: 303 | 27 runner = cls() 304 | 28 305 | 29 print("Round {}\n".format(runner.round)) 306 | 30 307 | ``` 308 | 309 | 보시는 바와 같이, 호출시 문제를 일으킬 수 있는 `c`라는 끔찍한 이름의 변수를 가지고 있습니다. (이전에 `c(ontinue)` 명령어와 관련된 코멘트를 생각해보세요) 우리는 현재 `while` 루프 바로 앞에 있으므로 루프로 들어가 우리가 발견할 수 있는 다른 것들을 살펴봅시다. 310 | 311 | ### 3. `n(ext)` - 현재 줄에서 예외가 발생하지 않길 바랍니다 312 | 313 | ``` 314 | n(ext) 315 | Continue execution until the next line in the current function 316 | is reached or it returns. 317 | ``` 318 | 319 | 현재 줄에서 `n(ext)`와 `list` 명령어를 실행하여 무슨 일이 일어나는지 살펴봅시다. 320 | 321 | ``` 322 | (Pdb) n 323 | > /Users/Development/pdb-tutorial/dicegame/runner.py(27)run() 324 | -> while True: 325 | (Pdb) l 326 | 21 @classmethod 327 | 22 def run(cls): 328 | 23 # Probably counts wins or something. 329 | 24 # Great variable name, 10/10. 330 | 25 c = 0 331 | 26 -> while True: 332 | 27 runner = cls() 333 | 28 334 | 29 print("Round {}\n".format(runner.round)) 335 | 30 336 | 31 for die in runner.dice: 337 | ``` 338 | 339 | 이제 현재 줄은 `whilte True` 명령문에 위치하게 되었습니다! 이제 프로그램이 예외를 발생시키거나 종료될때까지 `next`를 계속 실행할 수 있습니다. `for` 루프로 들어가기 위해 `next`를 3번 더 호출한 뒤 `list`를 연달아 실행해봅시다. 340 | 341 | ``` 342 | (Pdb) n 343 | > /Users/Development/pdb-tutorial/dicegame/runner.py(27)run() 344 | -> runner = cls() 345 | (Pdb) n 346 | > /Users/Development/pdb-tutorial/dicegame/runner.py(29)run() 347 | -> print("Round {}\n".format(runner.round)) 348 | (Pdb) n 349 | Round 1 350 | 351 | > /Users/Development/pdb-tutorial/dicegame/runner.py(31)run() 352 | -> for die in runner.dice: 353 | (Pdb) l 354 | 26 while True: 355 | 27 runner = cls() 356 | 28 357 | 29 print("Round {}\n".format(runner.round)) 358 | 30 359 | 31 -> for die in runner.dice: 360 | 32 print(die.show()) 361 | 33 362 | 34 guess = input("Sigh. What is your guess?: ") 363 | 35 guess = int(guess) 364 | ``` 365 | 366 | 현재 위치에서, `next` 명령어를 호출하면`runner.dice`의 길이만큼 `for` 루프를 순회하게 됩니다. `pdb` 실행기에서 `len()` 함수를 호출하여 `runner.dice`의 길이를 확인할 수 있습니다. 호출 결과는 5를 반환할 것입니다. 367 | 368 | ``` 369 | (Pdb) len(runner.dice) 370 | 5 371 | ``` 372 | 373 | 길이가 5밖에 안되므로, `next`를 5회만 호출하면 루프를 순회할 수 있지만, 반복해야할 횟수가 50회, 10,000회라고 가정해보면 다른 방법이 필요해 보입니다. 보다 더 좋은 옵션은 중단점을 설정하고 중단점까지 `continue`를 호출하는 것입니다. 374 | 375 | 376 | ### 4. `b(reak)` - `n`을 반복적으로 호출하고 싶지 않습니다 377 | 378 | ``` 379 | b(reak) [ ([filename:]lineno | function) [, condition] ] 380 | Without argument, list all breaks. 381 | 382 | With a line number argument, set a break at this line in the 383 | current file. With a function name, set a break at the first 384 | executable line of that function. If a second argument is 385 | present, it is a string specifying an expression which must 386 | evaluate to true before the breakpoint is honored. 387 | 388 | The line number may be prefixed with a filename and a colon, 389 | to specify a breakpoint in another file (probably one that 390 | hasn't been loaded yet). The file is searched for on 391 | sys.path; the .py suffix may be omitted. 392 | ``` 393 | 394 | 이 튜토리얼에선 `b(reak)`의 설명중 첫 두 문장에만 주의를 기울이면 됩니다. 이전 섹션에서 언급했듯이, `for` 루프 다음에 중단점을 설정하여 `run()` 메서드를 계속 탐색할 수 있도록 하려고 합니다. 유저의 입력을 기다리는 입력 함수가 있는 `34`번째 줄에 중단점을 설정합니다. `b 34`를 호출해 중단점을 설정하고 `continue`를 호출해 중단점까지 실행합니다. 395 | 396 | ``` 397 | (Pdb) b 34 398 | Breakpoint 1 at /Users/Development/pdb-tutorial/dicegame/runner.py(34)run() 399 | (Pdb) c 400 | 401 | [...] # prints some dice 402 | 403 | > /Users/Development/pdb-tutorial/dicegame/runner.py(34)run() 404 | -> guess = input("Sigh. What is your guess?: ") 405 | ``` 406 | 407 | 아무 인자 없이 `break`를 호출하면 설정한 중단점들을 볼 수 있습니다. 408 | 409 | ``` 410 | (Pdb) b 411 | Num Type Disp Enb Where 412 | 1 breakpoint keep yes at /Users/Development/pdb-tutorial/dicegame/runner.py:34 413 | breakpoint already hit 1 time 414 | ``` 415 | 416 | 위 출력의 가장 왼쪽열에서 볼 수 있는 중단점 번호와 함께 `cl(ear)` 명령어를 사용하면 중단점을 삭제할 수 있습니다. `clear` 명령어로 1번 중단점을 삭제해봅시다. 417 | 418 | **참고**: `clear` 명령어에 아무 인자도 주지 않으면 모든 중단점들을 삭제합니다. 419 | 420 | ``` 421 | (Pdb) cl 1 422 | Deleted breakpoint 1 at /Users/Development/pdb-tutorial/dicegame/runner.py:34 423 | ``` 424 | 425 | `next`를 호출하면 `input()` 함수를 실행할 수 있습니다. 추측값으로 10을 입력하고 `pdb` 실행기로 돌아가 다음 줄들을 보기 위해 `list`를 호출합니다. 426 | 427 | ``` 428 | (Pdb) n 429 | Sigh. What is your guess?: 10 430 | > /Users/Development/pdb-tutorial/dicegame/runner.py(35)run() 431 | -> guess = int(guess) 432 | (Pdb) l 433 | 30 434 | 31 for die in runner.dice: 435 | 32 print(die.show()) 436 | 33 437 | 34 guess = input("Sigh. What is your guess?: ") 438 | 35 -> guess = int(guess) 439 | 36 440 | 37 if guess == runner.answer(): 441 | 38 print("Congrats, you can add like a 5 year old...") 442 | 39 runner.wins += 1 443 | 40 c += 1 444 | 445 | 446 | ``` 447 | 448 | 우리는 지금 왜 우리의 추측이 첫 플레이에서의 결과와 맞지 않았는지를 알아내고 있음을 기억하세요. 아마 비교 조건문인 `guess == runner.answer()`에 문제가 있을 것 같습니다. 여기서 오류가 발생하는 것 같으니 `runner.answer()` 메서드가 어떻게 동작하는지 확인 해봐야합니다. `next`를 호출해 다음 라인으로 이동한 뒤 `step`을 통해 `runner.answer()` 메서드로 들어가봅시다. 449 | 450 | ``` 451 | (Pdb) s 452 | --Call-- 453 | > /Users/spiro/Development/mobify/engineering-meeting/pdb-tutorial/dicegame/runner.py(15)answer() 454 | -> def answer(self): 455 | (Pdb) l 456 | 10 def reset(self): 457 | 11 self.round = 1 458 | 12 self.wins = 0 459 | 13 self.loses = 0 460 | 14 461 | 15 -> def answer(self): 462 | 16 total = 0 463 | 17 for die in self.dice: 464 | 18 total += 1 465 | 19 return total 466 | 20 467 | ``` 468 | 469 | 필자는 문제를 찾은 것 같습니다! 18번 줄에서 `total` 변수가 주사위의 값들을 제대로 합산하고 있지 않습니다. `die`가 주사위 값을 가지고 있는지 확인하여 이를 해결할 수 있는지 확인해봅시다. 18번 줄로 가기 위해 중단점을 설정하고 첫 순회까지 `next`를 호출합니다. `18`번 줄에 도착하면, `die` 인스턴스에서 `dir()` 함수를 호출하여 어떤 메서드와 속성값들을 가지고 있는지 확인합니다. 470 | 471 | ``` 472 | -> total += 1 473 | (Pdb) dir(die) 474 | ['__class__', '__delattr__', [...], 'create_dice', 'roll', 'show', 'value'] 475 | ``` 476 | 477 | 리스트의 마지막에 `value` 속성값이 있습니다! value 값을 호출하여 어떤 값을 반환하는지 확인해 봅시다. (값은 랜덤이기 때문에 필자와 다를 수도 있다) 재미삼아, `show()` 메서드를 호출하여 주사위의 값과 주사위의 출력 모양이 같은지도 확인해봅시다. 478 | 479 | ``` 480 | (Pdb) die.value 481 | 2 482 | (Pdb) die.show() 483 | '---------\n|* |\n| |\n| *|\n---------' 484 | ``` 485 | 486 | **참고**: `\n`을 개행 문자로 출력하려면 `print()` 함수로 `die.show()`를 호출하세요. 487 | 488 | 우리가 원하는대로 잘 동작하는 것 같습니다. 이제 answer 메서드를 수정할 준비가 되었습니다. 그러나 우리중 일부는 디버깅을 계속 유지하면서 한 번에 모든 문제를 다 찾고 싶을수도 있습니다. 안타깝게도 우리는 또 다시 for 루프에 갇혔습니다. 여러분은 `19`번 줄에 중단점을 설정하고 `continue`를 호출하면 될거라고 생각할테지만 이 경우에 사용할 수 있는 더 좋은 방법이 있습니다. 489 | 490 | ### 5. `r(eturn)` - 이 함수를 빠져 나가고 싶습니다 491 | 492 | ``` 493 | r(eturn) 494 | Continue execution until the current function returns. 495 | ``` 496 | 497 | `return`은 함수의 최종 결과값을 검사할 수 있는 아주 *강력한* 명령어입니다. 반환문 호출시 중단점을 설정할 수 있지만 `return` pdb 명령어는 단일 반환에 대한 실행 경로를 따르기 때문에 단일 함수에 다중 반환 ㅁ명령문이 있는 경우에 유용합니다. `return` 명령어를 호출해 함수의 마지막 부분으로 이동합니다. 498 | 499 | ``` 500 | (Pdb) r 501 | --Return-- 502 | > /Users/Development/pdb-tutorial/dicegame/runner.py(19)answer()->5 503 | -> return total 504 | (Pdb) l 505 | 14 506 | 15 def answer(self): 507 | 16 total = 0 508 | 17 for die in self.dice: 509 | 18 total += 1 510 | 19 -> return total 511 | 20 512 | 21 @classmethod 513 | 22 def run(cls): 514 | 23 # Probably counts wins or something. 515 | 24 # Great variable name, 10/10. 516 | (Pdb) 517 | ``` 518 | 519 | 반환된 `total` 변수의 값을 확인하려면 현재 위치에서 `total` 을 호출하거나 `--Return--` 출력줄 아래에 있는 최종값을 확인하면 됩니다. 이제 `run()` 메서드를 빠져나오기 위해 `next` 명령어를 호출하면 이전 위치로 돌아가게 됩니다. 520 | 521 | 이제 `exit()` 또는 `CTRL+D` (파이썬 실행기에서와 동일)를 호출하여 `pdb` 디버거를 종료할 수 있습니다. 이러한 다섯 가지 명령어를 사용하면 여러가지 버그를 찾고 좀 더 고급의 pdb 예제를 따라할 수 있습니다. 522 | 523 | 524 | ## `pdb` 고급 주제 525 | 526 | 다음은 여러분이 사용할 수 있는 몇 가지 고급 `pdb` 명령어입니다. 527 | 528 | ### ! (뱅) 명령어 529 | 530 | ``` 531 | ! 532 | Execute the (one-line) statement in the context of the current stack frame. 533 | ``` 534 | 535 | 뱅 명령어 (`!`)는 다음의 명령문이 `pdb` 명령어가 아닌 Python의 명령인 것으로 해석할 수 있도록 해줍니다.이는 `c` 변수를 가진 `run()` 메서드에서 유용합니다. 튜토리얼의 초반부에서 언급했듯이, `pdb`에서 `c`를 호출하면 `continue` 명령어가 실행되는 문제가 있습니다. `pdb` 실행기에서 코드를 탐색하다가 `runner.py` 파일의 `26`번째 줄에서 멈추고 `c` 앞에 `!`을 붙인 명령어를 실행하면 다음과 같은 결과가 나옵니다. 536 | 537 | ``` 538 | (Pdb) !c 539 | 0 540 | ``` 541 | 542 | `25`번째 줄에서 `c = 0`으로 설정했으므로 우리 의도에 맞는 결과값을 얻었습니다! 543 | 544 | 545 | ### `pdb` 포스트 모템 546 | 547 | ``` 548 | pdb.post_mortem(traceback=None) 549 | Enter post-mortem debugging of the given traceback object. If no traceback is given, it uses the one of the exception that is currently being handled 550 | (an exception must be being handled if the default is to be used). 551 | 552 | pdb.pm() 553 | Enter post-mortem debugging of the traceback found in sys.last_traceback. 554 | ``` 555 | 556 | 두 메서드는 동일해 보이지만, `post_mortem()`과 `pm()`은 주어진 트랙백(trackback)에 따라 다릅니다. 필자는 `except` 블록에서 주로 `post_mortem()`을 사용합니다. 557 | 558 | 그러나, `pm()` 메서드가 좀 더 강력하다고 생각하기 때문에 이에 대해서 다루고자 합니다. 이 메서드가 실제로 어떻게 동작하는지 살펴봅시다. 559 | 560 | 프로젝트의 루트 디렉토리의 셸에서 `python`으로 파이썬 실행기를 엽니다. 그 다음 실행기에서 `main` 모듈에서 `main` 메서드와 `pdb`를 임포트합니다. 게임을 계속하기 위해 `Y`를 입력해 예외가 발생할 때까지 게임을 진행합니다. 561 | 562 | ``` 563 | >>> import pdb 564 | >>> from main import main 565 | >>> main() 566 | [...] 567 | Would you like to play again?[Y/n]: Y 568 | Traceback (most recent call last): 569 | File "main.py", line 12, in 570 | main() 571 | File "main.py", line 8, in main 572 | GameRunner.run() 573 | File "/Users/Development/pdb-tutorial/dicegame/runner.py", line 62, in run 574 | i_just_throw_an_exception() 575 | File "/Users/Development/pdb-tutorial/dicegame/utils.py", line 13, in i_just_throw_an_exception 576 | raise UnnecessaryError("You actually called this function...") 577 | dicegame.utils.UnnecessaryError: You actually called this function... 578 | ``` 579 | 580 | 이제 `pdb` 모듈에서 `pm()` 메서드를 호출하여 무슨일이 일어나는지 살펴봅시다. 581 | 582 | ``` 583 | >>> pdb.pm() 584 | > /Users/Development/pdb-tutorial/dicegame/utils.py(13)i_just_throw_an_exception() 585 | -> raise UnnecessaryError("You actually called this function...") 586 | (Pdb) 587 | ``` 588 | 589 | 보세요! 우리는 마지막 예외가 발생한 지점으로부터 프로그램을 복구하고 pdb 프롬프트에 들어왔습니다. 여기서 우리는 크래시 나기 전의 프로그램의 상태를 검사할 수 있습니다. 590 | 591 | **참고**: 익셉션이 발생하기 전까지 `python -m pdb main.py`와 `continue`를 사용해 `main.py`를 실행할 수도 있습니다. 파이썬은 포착되지 않은 예외가 발생한 지점에서 자동으로 `post_mortem` 모드로 들어갑니다. 592 | 593 | 594 | ## 마무리 595 | 596 | 튜토리얼을 마친걸 축하드리며 여기까지 따라와줘서 감사합니다! 의견, 잘못된 점 또는 추가 고급 예제가 있으면 풀 리퀘스트를 날려주세요. 597 | -------------------------------------------------------------------------------- /dicegame/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingrammer/pdb-tutorial/5380d3667bc7458b053688013638bd3421ca186c/dicegame/__init__.py -------------------------------------------------------------------------------- /dicegame/die.py: -------------------------------------------------------------------------------- 1 | # Just count the stupid dice 2 | import random 3 | 4 | def roll(dice): 5 | for die in dice: 6 | # XXX: I don't even know what this function does 7 | continue 8 | 9 | class Die: 10 | """ 11 | This is always correct. Seriously, look away. 12 | """ 13 | 14 | def __init__(self): 15 | self.roll() 16 | 17 | def roll(self): 18 | self.value = int(random.random() * 6 + 1) 19 | 20 | def show(self): 21 | if self.value == 1: 22 | return("---------\n| |\n| * |\n| |\n---------") 23 | elif self.value == 2: 24 | return("---------\n|* |\n| |\n| *|\n---------") 25 | elif self.value == 3: 26 | return("---------\n|* |\n| * |\n| *|\n---------") 27 | elif self.value == 4: 28 | return("---------\n|* *|\n| |\n|* *|\n---------") 29 | elif self.value == 5: 30 | return("---------\n|* *|\n| * |\n|* *|\n---------") 31 | else: 32 | return("---------\n|* *|\n|* *|\n|* *|\n---------") 33 | 34 | @classmethod 35 | def create_dice(cls, n): 36 | return [cls() for _ in range(n)] 37 | -------------------------------------------------------------------------------- /dicegame/runner.py: -------------------------------------------------------------------------------- 1 | from .die import Die 2 | from .utils import i_just_throw_an_exception 3 | 4 | class GameRunner: 5 | 6 | def __init__(self): 7 | self.dice = Die.create_dice(5) 8 | self.reset() 9 | 10 | def reset(self): 11 | self.round = 1 12 | self.wins = 0 13 | self.loses = 0 14 | 15 | def answer(self): 16 | total = 0 17 | for die in self.dice: 18 | total += 1 19 | return total 20 | 21 | @classmethod 22 | def run(cls): 23 | # Probably counts wins or something. 24 | # Great variable name, 10/10. 25 | c = 0 26 | while True: 27 | runner = cls() 28 | 29 | print("Round {}\n".format(runner.round)) 30 | 31 | for die in runner.dice: 32 | print(die.show()) 33 | 34 | guess = input("Sigh. What is your guess?: ") 35 | guess = int(guess) 36 | 37 | if guess == runner.answer(): 38 | print("Congrats, you can add like a 5 year old...") 39 | runner.wins += 1 40 | c += 1 41 | else: 42 | print("Sorry that's wrong") 43 | print("The answer is: {}".format(runner.answer())) 44 | print("Like seriously, how could you mess that up") 45 | runner.loses += 1 46 | c = 0 47 | print("Wins: {} Loses {}".format(runner.wins, runner.loses)) 48 | runner.round += 1 49 | 50 | if c == 6: 51 | print("You won... Congrats...") 52 | print("The fact it took you so long is pretty sad") 53 | break 54 | 55 | prompt = input("Would you like to play again?[Y/n]: ") 56 | 57 | if prompt == 'y' or prompt == '': 58 | continue 59 | else: 60 | i_just_throw_an_exception() 61 | -------------------------------------------------------------------------------- /dicegame/utils.py: -------------------------------------------------------------------------------- 1 | class UnnecessaryError(Exception): 2 | pass 3 | 4 | 5 | def i_just_throw_an_exception(): 6 | value = 1 7 | def some_inner_function(): 8 | value += 1 9 | 10 | some_value = "I don't know what you were expecting" 11 | raise UnnecessaryError("You actually called this function...") 12 | -------------------------------------------------------------------------------- /instructions.txt: -------------------------------------------------------------------------------- 1 | Your boss has given you the following project to fix for a client. It's supposed to be a simple dice 2 | game where the object of the game is to correctly add up the values of the dice for 6 consecutive turns. 3 | 4 | The issue is that a former programmer worked on it and didn't know how to debug effectively. It's now up to 5 | you to fix the errors and finally make the game playable. 6 | 7 | To play the game you must run the main.py file. 8 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from dicegame.runner import GameRunner 2 | 3 | 4 | def main(): 5 | print("Add the values of the dice") 6 | print("It's really that easy") 7 | print("What are you doing with your life.") 8 | GameRunner.run() 9 | 10 | 11 | if __name__ == "__main__": 12 | main() 13 | -------------------------------------------------------------------------------- /solutions/dicegame/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mingrammer/pdb-tutorial/5380d3667bc7458b053688013638bd3421ca186c/solutions/dicegame/__init__.py -------------------------------------------------------------------------------- /solutions/dicegame/die.py: -------------------------------------------------------------------------------- 1 | # Just count the stupid dice 2 | import random 3 | 4 | def roll(dice): 5 | for die in dice: 6 | die.roll() 7 | 8 | class Die: 9 | """ 10 | This is always correct. Seriously, look away. 11 | """ 12 | 13 | def __init__(self): 14 | self.roll() 15 | 16 | def roll(self): 17 | self.value = int(random.random() * 6 + 1) 18 | 19 | def show(self): 20 | if self.value == 1: 21 | return("---------\n| |\n| * |\n| |\n---------") 22 | elif self.value == 2: 23 | return("---------\n|* |\n| |\n| *|\n---------") 24 | elif self.value == 3: 25 | return("---------\n|* |\n| * |\n| *|\n---------") 26 | elif self.value == 4: 27 | return("---------\n|* *|\n| |\n|* *|\n---------") 28 | elif self.value == 5: 29 | return("---------\n|* *|\n| * |\n|* *|\n---------") 30 | else: 31 | return("---------\n|* *|\n|* *|\n|* *|\n---------") 32 | 33 | @classmethod 34 | def create_dice(cls, n): 35 | return [cls() for _ in range(n)] 36 | -------------------------------------------------------------------------------- /solutions/dicegame/runner.py: -------------------------------------------------------------------------------- 1 | from .die import Die 2 | from .utils import i_just_throw_an_exception 3 | 4 | class GameRunner: 5 | 6 | def __init__(self): 7 | self.dice = Die.create_dice(5) 8 | self.reset() 9 | 10 | def reset(self): 11 | self.round = 1 12 | self.wins = 0 13 | self.loses = 0 14 | 15 | @property 16 | def answer(self): 17 | total = 0 18 | for die in self.dice: 19 | total += die.value 20 | return total 21 | 22 | @classmethod 23 | def run(cls): 24 | count = 0 25 | runner = cls() 26 | while True: 27 | 28 | print("Round {}\n".format(runner.round)) 29 | 30 | for die in runner.dice: 31 | print(die.show()) 32 | 33 | guess = input("Sigh. What is your guess?: ") 34 | guess = int(guess) 35 | 36 | if guess == runner.answer: 37 | print("Congrats, you can add like a 5 year old...") 38 | runner.wins += 1 39 | count += 1 40 | runner.consecutive_wins += 1 41 | else: 42 | print("Sorry that's wrong") 43 | print("The answer is: {}".format(runner.answer)) 44 | print("Like seriously, how could you mess that up") 45 | runner.loses += 1 46 | count = 0 47 | print("Wins: {} Loses {}".format(runner.wins, runner.loses)) 48 | runner.round += 1 49 | 50 | if count == 6: 51 | print("You won... Congrats...") 52 | print("The fact it took you so long is pretty sad") 53 | break 54 | 55 | prompt = input("Would you like to play again?[Y/n]: ") 56 | 57 | if prompt.lower() == 'y' or prompt == '': 58 | continue 59 | else: 60 | break 61 | -------------------------------------------------------------------------------- /solutions/dicegame/utils.py: -------------------------------------------------------------------------------- 1 | class UnnecessaryError(Exception): 2 | pass 3 | 4 | 5 | 6 | 7 | def i_just_throw_an_exception(): 8 | value = 1 9 | def some_inner_function(): 10 | value += 1 11 | 12 | some_value = "I don't know what you were expecting" 13 | raise UnnecessaryError("You actually called this function...") 14 | -------------------------------------------------------------------------------- /solutions/main.py: -------------------------------------------------------------------------------- 1 | from dicegame.runner import GameRunner 2 | 3 | 4 | def main(): 5 | print("Add the values of the dice") 6 | print("It's really that easy") 7 | print("What are you doing with your life.") 8 | GameRunner.run() 9 | 10 | 11 | if __name__ == "__main__": 12 | main() 13 | --------------------------------------------------------------------------------