├── .gitignore ├── README.md ├── ThinkPython3Notebooks.zip ├── ThinkPythonNotebooks.zip ├── ThinkPythonSolutions └── .github │ └── workflows │ └── tests.yml ├── ThinkPythonSolutionsNotebooks.zip ├── Turtle.py ├── blank ├── chap00.ipynb ├── chap01.ipynb ├── chap02.ipynb ├── chap03.ipynb ├── chap04.ipynb ├── chap05.ipynb ├── chap06.ipynb ├── chap07.ipynb ├── chap08.ipynb ├── chap09.ipynb ├── chap10.ipynb ├── chap11.ipynb ├── chap12.ipynb ├── chap13.ipynb ├── chap14.ipynb ├── chap15.ipynb ├── chap16.ipynb ├── chap17.ipynb ├── chap18.ipynb └── chap19.ipynb ├── chapters ├── build.sh ├── chap00.ipynb ├── chap01.ipynb ├── chap02.ipynb ├── chap03.ipynb ├── chap04.ipynb ├── chap05.ipynb ├── chap06.ipynb ├── chap07.ipynb ├── chap08.ipynb ├── chap09.ipynb ├── chap10.ipynb ├── chap11.ipynb ├── chap12.ipynb ├── chap13.ipynb ├── chap14.ipynb ├── chap15.ipynb ├── chap16.ipynb ├── chap17.ipynb ├── chap18.ipynb ├── chap19.ipynb └── jupyter_intro.ipynb ├── diagram.py ├── jb ├── _config.yml ├── _toc.yml ├── build.sh └── prep_notebooks.py ├── jupyturtle_flower.png ├── jupyturtle_flower.svg ├── jupyturtle_pie.png ├── jupyturtle_pie.svg ├── photos.zip ├── structshape.py ├── think_python_3e.jpg ├── thinkpython.py └── words.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Think Python, 3rd edition 2 | 3 | Jupyter notebooks and other material for the 3rd edition of *Think Python: How to Think Like a Computer Scientist* 4 | 5 | by Allen B. Downey 6 | 7 | You can order print and electronic versions of *Think Python 3e* from 8 | [Bookshop.org](https://bookshop.org/a/98697/9781098155438) and 9 | [Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325). 10 | 11 | The home page for the book is at [Green Tea Press](http://thinkpython.com). 12 | 13 | -------------------------------------------------------------------------------- /ThinkPython3Notebooks.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/ThinkPython3Notebooks.zip -------------------------------------------------------------------------------- /ThinkPythonNotebooks.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/ThinkPythonNotebooks.zip -------------------------------------------------------------------------------- /ThinkPythonSolutions/.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [v3] 6 | pull_request: 7 | schedule: 8 | # Run on the first of every month 9 | - cron: "0 0 1 * *" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | tests: 14 | name: Tests (${{ matrix.os }}, Python ${{ matrix.python-version }}) 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | # don't test on windows 19 | os: [ubuntu-latest, macos-latest] 20 | python-version: ["3.10"] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements-dev.txt 34 | 35 | - name: Run tests 36 | run: | 37 | make tests 38 | -------------------------------------------------------------------------------- /ThinkPythonSolutionsNotebooks.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/ThinkPythonSolutionsNotebooks.zip -------------------------------------------------------------------------------- /Turtle.py: -------------------------------------------------------------------------------- 1 | from IPython.display import display, HTML 2 | import time 3 | import math 4 | import re 5 | 6 | # Created at: 23rd October 2018 7 | # by: Tolga Atam 8 | # v2.1.0 Updated at: 15th March 2021 9 | # by: Tolga Atam 10 | # from https://github.com/tolgaatam/ColabTurtle/blob/master/ColabTurtle/Turtle.py 11 | 12 | # vX.X.X Updated at by Allen Downey for Think Python 3e 13 | 14 | # Module for drawing classic Turtle figures on Google Colab notebooks. 15 | # It uses html capabilites of IPython library to draw svg shapes inline. 16 | # Looks of the figures are inspired from Blockly Games / Turtle (blockly-games.appspot.com/turtle) 17 | 18 | DEFAULT_WINDOW_SIZE = (300, 150) 19 | DEFAULT_SPEED = 6 20 | DEFAULT_TURTLE_VISIBILITY = True 21 | DEFAULT_PEN_COLOR = '#663399' 22 | DEFAULT_TURTLE_COLOR = 'gray' 23 | DEFAULT_TURTLE_DEGREE = 0 24 | DEFAULT_BACKGROUND_COLOR = 'white' 25 | DEFAULT_IS_PEN_DOWN = True 26 | DEFAULT_SVG_LINES_STRING = "" 27 | DEFAULT_PEN_WIDTH = 2 28 | # all 140 color names that modern browsers support. taken from https://www.w3schools.com/colors/colors_names.asp 29 | VALID_COLORS = ('black', 'navy', 'darkblue', 'mediumblue', 'blue', 'darkgreen', 'green', 'teal', 'darkcyan', 'deepskyblue', 'darkturquoise', 'mediumspringgreen', 'lime', 'springgreen', 'aqua', 'cyan', 'midnightblue', 'dodgerblue', 'lightseagreen', 'forestgreen', 'seagreen', 'darkslategray', 'darkslategrey', 'limegreen', 'mediumseagreen', 'turquoise', 'royalblue', 'steelblue', 'darkslateblue', 'mediumturquoise', 'indigo', 'darkolivegreen', 'cadetblue', 'cornflowerblue', 'rebeccapurple', 'mediumaquamarine', 'dimgray', 'dimgrey', 'slateblue', 'olivedrab', 'slategray', 'slategrey', 'lightslategray', 'lightslategrey', 'mediumslateblue', 'lawngreen', 'chartreuse', 'aquamarine', 'maroon', 'purple', 'olive', 'gray', 'grey', 'skyblue', 'lightskyblue', 'blueviolet', 'darkred', 'darkmagenta', 'saddlebrown', 'darkseagreen', 'lightgreen', 'mediumpurple', 'darkviolet', 'palegreen', 'darkorchid', 'yellowgreen', 'sienna', 'brown', 'darkgray', 'darkgrey', 'lightblue', 'greenyellow', 'paleturquoise', 'lightsteelblue', 'powderblue', 'firebrick', 'darkgoldenrod', 'mediumorchid', 'rosybrown', 'darkkhaki', 'silver', 'mediumvioletred', 'indianred', 'peru', 'chocolate', 'tan', 'lightgray', 'lightgrey', 'thistle', 'orchid', 'goldenrod', 'palevioletred', 'crimson', 'gainsboro', 'plum', 'burlywood', 'lightcyan', 'lavender', 'darksalmon', 'violet', 'palegoldenrod', 'lightcoral', 'khaki', 'aliceblue', 'honeydew', 'azure', 'sandybrown', 'wheat', 'beige', 'whitesmoke', 'mintcream', 'ghostwhite', 'salmon', 'antiquewhite', 'linen', 'lightgoldenrodyellow', 'oldlace', 'red', 'fuchsia', 'magenta', 'deeppink', 'orangered', 'tomato', 'hotpink', 'coral', 'darkorange', 'lightsalmon', 'orange', 'lightpink', 'pink', 'gold', 'peachpuff', 'navajowhite', 'moccasin', 'bisque', 'mistyrose', 'blanchedalmond', 'papayawhip', 'lavenderblush', 'seashell', 'cornsilk', 'lemonchiffon', 'floralwhite', 'snow', 'yellow', 'lightyellow', 'ivory', 'white') 30 | VALID_COLORS_SET = set(VALID_COLORS) 31 | DEFAULT_TURTLE_SHAPE = 'circle' 32 | VALID_TURTLE_SHAPES = ('turtle', 'circle') 33 | SVG_TEMPLATE = """ 34 | 35 | 36 | {lines} 37 | {turtle} 38 | 39 | """ 40 | TURTLE_TURTLE_SVG_TEMPLATE = """ 41 | 42 | """ 43 | TURTLE_CIRCLE_SVG_TEMPLATE = """ 44 | 45 | 46 | 47 | 48 | """ 49 | 50 | 51 | SPEED_TO_SEC_MAP = {1: 1.5, 2: 0.9, 3: 0.7, 4: 0.5, 5: 0.3, 6: 0.18, 7: 0.12, 8: 0.06, 9: 0.04, 10: 0.02, 11: 0.01, 12: 0.001, 13: 0.0001} 52 | 53 | 54 | # helper function that maps [1,13] speed values to ms delays 55 | def _speedToSec(speed): 56 | return SPEED_TO_SEC_MAP[speed] 57 | 58 | 59 | turtle_speed = DEFAULT_SPEED 60 | 61 | is_turtle_visible = DEFAULT_TURTLE_VISIBILITY 62 | pen_color = DEFAULT_PEN_COLOR 63 | window_size = DEFAULT_WINDOW_SIZE 64 | turtle_pos = (DEFAULT_WINDOW_SIZE[0] // 2, DEFAULT_WINDOW_SIZE[1] // 2) 65 | turtle_degree = DEFAULT_TURTLE_DEGREE 66 | background_color = DEFAULT_BACKGROUND_COLOR 67 | is_pen_down = DEFAULT_IS_PEN_DOWN 68 | svg_lines_string = DEFAULT_SVG_LINES_STRING 69 | pen_width = DEFAULT_PEN_WIDTH 70 | turtle_shape = DEFAULT_TURTLE_SHAPE 71 | 72 | drawing_window = None 73 | 74 | 75 | # construct the display for turtle 76 | def make_turtle(speed=DEFAULT_SPEED, width=DEFAULT_WINDOW_SIZE[0], height=DEFAULT_WINDOW_SIZE[1]): 77 | global window_size 78 | global drawing_window 79 | global turtle_speed 80 | global is_turtle_visible 81 | global pen_color 82 | global turtle_color 83 | global turtle_pos 84 | global turtle_degree 85 | global background_color 86 | global is_pen_down 87 | global svg_lines_string 88 | global pen_width 89 | global turtle_shape 90 | 91 | if isinstance(speed,int) == False or speed not in range(1, 14): 92 | raise ValueError('speed must be an integer in interval [1,13]') 93 | turtle_speed = speed 94 | 95 | window_size = width, height 96 | if not (isinstance(window_size, tuple) and 97 | isinstance(window_size[0], int) and 98 | isinstance(window_size[1], int)): 99 | raise ValueError('window_size must be a tuple of 2 integers') 100 | 101 | is_turtle_visible = DEFAULT_TURTLE_VISIBILITY 102 | pen_color = DEFAULT_PEN_COLOR 103 | turtle_color = DEFAULT_TURTLE_COLOR 104 | turtle_pos = (window_size[0] // 2, window_size[1] // 2) 105 | turtle_degree = DEFAULT_TURTLE_DEGREE 106 | background_color = DEFAULT_BACKGROUND_COLOR 107 | is_pen_down = DEFAULT_IS_PEN_DOWN 108 | svg_lines_string = DEFAULT_SVG_LINES_STRING 109 | pen_width = DEFAULT_PEN_WIDTH 110 | turtle_shape = DEFAULT_TURTLE_SHAPE 111 | 112 | drawing_window = display(HTML(_generateSvgDrawing()), display_id=True) 113 | 114 | 115 | # helper function for generating svg string of the turtle 116 | def _generateTurtleSvgDrawing(): 117 | if is_turtle_visible: 118 | vis = 'visible' 119 | else: 120 | vis = 'hidden' 121 | 122 | turtle_x = turtle_pos[0] 123 | turtle_y = turtle_pos[1] 124 | degrees = turtle_degree 125 | template = '' 126 | 127 | if turtle_shape == 'turtle': 128 | turtle_x -= 18 129 | turtle_y -= 18 130 | degrees += 90 131 | template = TURTLE_TURTLE_SVG_TEMPLATE 132 | else: #circle 133 | degrees -= 90 134 | template = TURTLE_CIRCLE_SVG_TEMPLATE 135 | 136 | return template.format(turtle_color=turtle_color, turtle_x=turtle_x, turtle_y=turtle_y, \ 137 | visibility=vis, degrees=degrees, rotation_x=turtle_pos[0], rotation_y=turtle_pos[1]) 138 | 139 | 140 | # helper function for generating the whole svg string 141 | def _generateSvgDrawing(): 142 | return SVG_TEMPLATE.format(window_width=window_size[0], window_height=window_size[1], 143 | background_color=background_color, lines=svg_lines_string, 144 | turtle=_generateTurtleSvgDrawing()) 145 | 146 | 147 | # helper functions for updating the screen using the latest positions/angles/lines etc. 148 | def _updateDrawing(): 149 | if drawing_window == None: 150 | raise AttributeError("Display has not been initialized yet. Call make_turtle() before using.") 151 | time.sleep(_speedToSec(turtle_speed)) 152 | drawing_window.update(HTML(_generateSvgDrawing())) 153 | 154 | 155 | # helper function for managing any kind of move to a given 'new_pos' and draw lines if pen is down 156 | def _moveToNewPosition(new_pos): 157 | global turtle_pos 158 | global svg_lines_string 159 | 160 | # rounding the new_pos to eliminate floating point errors. 161 | new_pos = ( round(new_pos[0],3), round(new_pos[1],3) ) 162 | 163 | start_pos = turtle_pos 164 | if is_pen_down: 165 | svg_lines_string += """""".format( 166 | x1=start_pos[0], y1=start_pos[1], x2=new_pos[0], y2=new_pos[1], pen_color=pen_color, pen_width=pen_width) 167 | 168 | turtle_pos = new_pos 169 | _updateDrawing() 170 | 171 | 172 | # makes the turtle move forward by 'units' units 173 | def forward(units): 174 | if not isinstance(units, (int,float)): 175 | raise ValueError('units must be a number.') 176 | 177 | alpha = math.radians(turtle_degree) 178 | ending_point = (turtle_pos[0] + units * math.cos(alpha), turtle_pos[1] + units * math.sin(alpha)) 179 | 180 | _moveToNewPosition(ending_point) 181 | 182 | fd = forward # alias 183 | 184 | # makes the turtle move backward by 'units' units 185 | def backward(units): 186 | if not isinstance(units, (int,float)): 187 | raise ValueError('units must be a number.') 188 | forward(-1 * units) 189 | 190 | bk = backward # alias 191 | back = backward # alias 192 | 193 | 194 | # makes the turtle move right by 'degrees' degrees (NOT radians) 195 | def right(degrees): 196 | global turtle_degree 197 | 198 | if not isinstance(degrees, (int,float)): 199 | raise ValueError('degrees must be a number.') 200 | 201 | turtle_degree = (turtle_degree + degrees) % 360 202 | _updateDrawing() 203 | 204 | rt = right # alias 205 | 206 | # makes the turtle face a given direction 207 | def face(degrees): 208 | global turtle_degree 209 | 210 | if not isinstance(degrees, (int,float)): 211 | raise ValueError('degrees must be a number.') 212 | 213 | turtle_degree = degrees % 360 214 | _updateDrawing() 215 | 216 | setheading = face # alias 217 | seth = face # alias 218 | 219 | # makes the turtle move right by 'degrees' degrees (NOT radians, this library does not support radians right now) 220 | def left(degrees): 221 | if not isinstance(degrees, (int,float)): 222 | raise ValueError('degrees must be a number.') 223 | right(-1 * degrees) 224 | 225 | lt = left 226 | 227 | # raises the pen such that following turtle moves will not cause any drawings 228 | def penup(): 229 | global is_pen_down 230 | 231 | is_pen_down = False 232 | # TODO: decide if we should put the timout after lifting the pen 233 | # _updateDrawing() 234 | 235 | pu = penup # alias 236 | up = penup # alias 237 | 238 | # lowers the pen such that following turtle moves will now cause drawings 239 | def pendown(): 240 | global is_pen_down 241 | 242 | is_pen_down = True 243 | # TODO: decide if we should put the timout after releasing the pen 244 | # _updateDrawing() 245 | 246 | pd = pendown # alias 247 | down = pendown # alias 248 | 249 | def isdown(): 250 | return is_pen_down 251 | 252 | # update the speed of the moves, [1,13] 253 | # if argument is omitted, it returns the speed. 254 | def speed(speed = None): 255 | global turtle_speed 256 | 257 | if speed is None: 258 | return turtle_speed 259 | 260 | if isinstance(speed,int) == False or speed not in range(1, 14): 261 | raise ValueError('speed must be an integer in the interval [1,13].') 262 | turtle_speed = speed 263 | # TODO: decide if we should put the timout after changing the speed 264 | # _updateDrawing() 265 | 266 | 267 | # move the turtle to a designated 'x' x-coordinate, y-coordinate stays the same 268 | def setx(x): 269 | if not isinstance(x, (int,float)): 270 | raise ValueError('new x position must be a number.') 271 | if x < 0: 272 | raise ValueError('new x position must be non-negative.') 273 | _moveToNewPosition((x, turtle_pos[1])) 274 | 275 | 276 | # move the turtle to a designated 'y' y-coordinate, x-coordinate stays the same 277 | def sety(y): 278 | if not isinstance(y, (int,float)): 279 | raise ValueError('new y position must be a number.') 280 | if y < 0: 281 | raise ValueError('new y position must be non-negative.') 282 | _moveToNewPosition((turtle_pos[0], y)) 283 | 284 | 285 | def home(): 286 | global turtle_degree 287 | 288 | turtle_degree = DEFAULT_TURTLE_DEGREE 289 | _moveToNewPosition( (window_size[0] // 2, window_size[1] // 2) ) # this will handle updating the drawing. 290 | 291 | # retrieve the turtle's currrent 'x' x-coordinate 292 | def getx(): 293 | return(turtle_pos[0]) 294 | 295 | xcor = getx # alias 296 | 297 | # retrieve the turtle's currrent 'y' y-coordinate 298 | def gety(): 299 | return(turtle_pos[1]) 300 | 301 | ycor = gety # alias 302 | 303 | # retrieve the turtle's current position as a (x,y) tuple vector 304 | def position(): 305 | return turtle_pos 306 | 307 | pos = position # alias 308 | 309 | # retrieve the turtle's current angle 310 | def getheading(): 311 | return turtle_degree 312 | 313 | heading = getheading # alias 314 | 315 | # move the turtle to a designated 'x'-'y' coordinate 316 | def moveto(x, y=None): 317 | if isinstance(x, tuple) and y is None: 318 | if len(x) != 2: 319 | raise ValueError('the tuple argument must be of length 2.') 320 | 321 | y = x[1] 322 | x = x[0] 323 | 324 | if not isinstance(x, (int,float)): 325 | raise ValueError('new x position must be a number.') 326 | if x < 0: 327 | raise ValueError('new x position must be non-negative') 328 | if not isinstance(y, (int,float)): 329 | raise ValueError('new y position must be a number.') 330 | if y < 0: 331 | raise ValueError('new y position must be non-negative.') 332 | _moveToNewPosition((x, y)) 333 | 334 | goto = moveto # alias 335 | setpos = moveto # alias 336 | setposition = moveto # alias 337 | 338 | # jump to a given location without leaving a trail 339 | def jumpto(x, y=None): 340 | flag = is_pen_down 341 | penup() 342 | goto(x, y) 343 | if flag: 344 | pendown() 345 | 346 | # switch turtle visibility to ON 347 | def showturtle(): 348 | global is_turtle_visible 349 | 350 | is_turtle_visible = True 351 | _updateDrawing() 352 | 353 | st = showturtle # alias 354 | 355 | # switch turtle visibility to OFF 356 | def hideturtle(): 357 | global is_turtle_visible 358 | 359 | is_turtle_visible = False 360 | _updateDrawing() 361 | 362 | ht = hideturtle # alias 363 | 364 | def isvisible(): 365 | return is_turtle_visible 366 | 367 | def _validateColorString(color): 368 | if color in VALID_COLORS_SET: # 140 predefined html color names 369 | return True 370 | if re.search("^#(?:[0-9a-fA-F]{3}){1,2}$", color): # 3 or 6 digit hex color code 371 | return True 372 | if re.search("rgb\(\s*(?:(?:\d{1,2}|1\d\d|2(?:[0-4]\d|5[0-5]))\s*,?){3}\)$", color): # rgb color code 373 | return True 374 | return False 375 | 376 | def _validateColorTuple(color): 377 | if len(color) != 3: 378 | return False 379 | if not isinstance(color[0], int) or not isinstance(color[1], int) or not isinstance(color[2], int): 380 | return False 381 | if not 0 <= color[0] <= 255 or not 0 <= color[1] <= 255 or not 0 <= color[2] <= 255: 382 | return False 383 | return True 384 | 385 | def _processColor(color): 386 | if isinstance(color, str): 387 | color = color.lower() 388 | if not _validateColorString(color): 389 | raise ValueError('color is invalid. it can be a known html color name, 3-6 digit hex string or rgb string.') 390 | return color 391 | elif isinstance(color, tuple): 392 | if not _validateColorTuple(color): 393 | raise ValueError('color tuple is invalid. it must be a tuple of three integers, which are in the interval [0,255]') 394 | return 'rgb(' + str(color[0]) + ',' + str(color[1]) + ',' + str(color[2]) + ')' 395 | else: 396 | raise ValueError('the first parameter must be a color string or a tuple') 397 | 398 | # change the background color of the drawing area 399 | # if no params, return the current background color 400 | def bgcolor(color = None, c2 = None, c3 = None): 401 | global background_color 402 | 403 | if color is None: 404 | return background_color 405 | elif c2 is not None: 406 | if c3 is None: 407 | raise ValueError('if the second argument is set, the third arguments must be set as well to complete the rgb set.') 408 | color = (color, c2, c3) 409 | 410 | background_color = _processColor(color) 411 | _updateDrawing() 412 | 413 | 414 | # change the color of the pen 415 | # if no params, return the current pen color 416 | def color(color = None, c2 = None, c3 = None): 417 | global pen_color 418 | 419 | if color is None: 420 | return pen_color 421 | elif c2 is not None: 422 | if c3 is None: 423 | raise ValueError('if the second argument is set, the third arguments must be set as well to complete the rgb set.') 424 | color = (color, c2, c3) 425 | 426 | pen_color = _processColor(color) 427 | _updateDrawing() 428 | 429 | pencolor = color 430 | 431 | # change the width of the lines drawn by the turtle, in pixels 432 | # if the function is called without arguments, it returns the current width 433 | def width(width = None): 434 | global pen_width 435 | 436 | if width is None: 437 | return pen_width 438 | else: 439 | if not isinstance(width, int): 440 | raise ValueError('new width position must be an integer.') 441 | if not width > 0: 442 | raise ValueError('new width position must be positive.') 443 | 444 | pen_width = width 445 | # TODO: decide if we should put the timout after changing the pen_width 446 | # _updateDrawing() 447 | 448 | # pensize is an alias for width 449 | pensize = width 450 | 451 | # clear any text or drawing on the screen 452 | def clear(): 453 | global svg_lines_string 454 | 455 | svg_lines_string = "" 456 | _updateDrawing() 457 | 458 | def write(obj, **kwargs): 459 | global svg_lines_string 460 | global turtle_pos 461 | text = str(obj) 462 | font_size = 12 463 | font_family = 'Arial' 464 | font_type = 'normal' 465 | align = 'start' 466 | 467 | if 'align' in kwargs and kwargs['align'] in ('left', 'center', 'right'): 468 | if kwargs['align'] == 'left': 469 | align = 'start' 470 | elif kwargs['align'] == 'center': 471 | align = 'middle' 472 | else: 473 | align = 'end' 474 | 475 | if "font" in kwargs: 476 | font = kwargs["font"] 477 | if len(font) != 3 or isinstance(font[0], int) == False or isinstance(font[1], str) == False or font[2] not in {'bold','italic','underline','normal'}: 478 | raise ValueError('font parameter must be a triplet consisting of font size (int), font family (str) and font type. font type can be one of {bold, italic, underline, normal}') 479 | font_size = font[0] 480 | font_family = font[1] 481 | font_type = font[2] 482 | 483 | style_string = "" 484 | style_string += "font-size:" + str(font_size) + "px;" 485 | style_string += "font-family:'" + font_family + "';" 486 | 487 | if font_type == 'bold': 488 | style_string += "font-weight:bold;" 489 | elif font_type == 'italic': 490 | style_string += "font-style:italic;" 491 | elif font_type == 'underline': 492 | style_string += "text-decoration: underline;" 493 | 494 | 495 | svg_lines_string += """{text}""".format(x=turtle_pos[0], y=turtle_pos[1], text=text, fill_color=pen_color, align=align, style=style_string) 496 | 497 | _updateDrawing() 498 | 499 | def shape(shape=None): 500 | global turtle_shape 501 | if shape is None: 502 | return turtle_shape 503 | elif shape not in VALID_TURTLE_SHAPES: 504 | raise ValueError('shape is invalid. valid options are: ' + str(VALID_TURTLE_SHAPES)) 505 | 506 | turtle_shape = shape 507 | _updateDrawing() 508 | 509 | # return turtle window width 510 | def window_width(): 511 | return window_size[0] 512 | 513 | # return turtle window height 514 | def window_height(): 515 | return window_size[1] 516 | -------------------------------------------------------------------------------- /blank/chap00.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "d9724920", 16 | "metadata": {}, 17 | "source": [ 18 | "# Preface\n", 19 | "\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "id": "b76f38c6", 25 | "metadata": {}, 26 | "source": [ 27 | "## Who Is This Book For?\n", 28 | "\n", 29 | "If you want to learn to program, you have come to the right place.\n", 30 | "Python is one of the best programming languages for beginners -- and it is also one of the most in-demand skills.\n", 31 | "\n", 32 | "You have also come at the right time, because learning to program now is probably easier than ever.\n", 33 | "With virtual assistants like ChatGPT, you don't have to learn alone.\n", 34 | "Throughout this book, I'll suggest ways you can use these tools to accelerate your learning.\n", 35 | "\n", 36 | "This book is primarily for people who have never programmed before and people who have some experience in another programming language.\n", 37 | "If you have substantial experience in Python, you might find the first few chapters too slow.\n", 38 | "\n", 39 | "One of the challenges of learning to program is that you have to learn *two* languages: one is the programming language itself; the other is the vocabulary we use to talk about programs.\n", 40 | "If you learn only the programming language, you are likely to have problems when you need to interpret an error message, read documentation, talk to another person, or use virtual assistants.\n", 41 | "If you have done some programming, but you have not also learned this second language, I hope you find this book helpful." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "b4dd57bc", 47 | "metadata": {}, 48 | "source": [ 49 | "## Goals of the Book\n", 50 | "\n", 51 | "Writing this book, I tried to be careful with the vocabulary.\n", 52 | "I define each term when it first appears.\n", 53 | "And there is a glossary that the end of each chapter that reviews the terms that were introduced.\n", 54 | "\n", 55 | "I also tried to be concise.\n", 56 | "The less mental effort it takes to read the book, the more capacity you will have for programming.\n", 57 | "\n", 58 | "But you can't learn to program just by reading a book -- you have to practice.\n", 59 | "For that reason, this book includes exercises at the end of every chapter where you can practice what you have learned.\n", 60 | "\n", 61 | "If you read carefully and work on exercises consistently, you will make progress.\n", 62 | "But I'll warn you now -- learning to program is not easy, and even for experienced programmers it can be frustrating.\n", 63 | "As we go, I will suggest strategies to help you write correct programs and fix incorrect ones." 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "6516d914", 69 | "metadata": {}, 70 | "source": [ 71 | "## Navigating the Book\n", 72 | "\n", 73 | "Each chapter in this book builds on the previous ones, so you should read them in order and take time to work on the exercises before you move on.\n", 74 | "\n", 75 | "The first six chapters introduce basic elements like arithmetic, conditionals, and loops.\n", 76 | "They also introduce the most important concept in programming, functions, and a powerful way to use them, recursion.\n", 77 | "\n", 78 | "Chapters 7 and 8 introduce strings -- which can represent letter, words, and sentences -- and algorithms for working with them.\n", 79 | "\n", 80 | "Chapters 9 through 12 introduce Python's core data structures -- lists, dictionaries, and tuples -- which are powerful tools for writing efficient programs.\n", 81 | "Chapter 12 presents algorithms for analyzing text and randomly generating new text.\n", 82 | "Algorithms like these are at the core of large language models (LLMs), so this chapter will give you an idea of how tools like ChatGPT work.\n", 83 | "\n", 84 | "Chapter 13 is about ways to store data in long-term storage -- files and databases.\n", 85 | "As an exercise, you can write a program that searches a file system and finds duplicate files.\n", 86 | "\n", 87 | "Chapters 14 through 17 introduce object-oriented programming (OOP), which is a way to organize programs and the data they work with.\n", 88 | "Many Python libraries are written in object-oriented style, so these chapters will help you understand their design -- and define your own objects.\n", 89 | "\n", 90 | "The goal of this book is not to cover the entire Python language.\n", 91 | "Rather, I focus on a subset of the language that provides the greatest capability with the fewest concepts.\n", 92 | "Nevertheless, Python has a lot of features you can use to solve common problems efficiently.\n", 93 | "Chapter 18 presents some of these features.\n", 94 | "\n", 95 | "Finally, Chapter 19 presents my parting thoughts and suggestions for continuing your programming journey." 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "id": "23013838", 101 | "metadata": {}, 102 | "source": [ 103 | "## What's new in the third edition?\n", 104 | "\n", 105 | "The biggest changes in this edition were driven by two new technologies -- Jupyter notebooks and virtual assistants.\n", 106 | "\n", 107 | "Each chapter of this book is a Jupyter notebook, which is a document that contains both ordinary text and code.\n", 108 | "For me, that makes it easier to write the code, test it, and keep it consistent with the text.\n", 109 | "For you, it means you can run the code, modify it, and work on the exercises, all in one place.\n", 110 | "Instructions for working with the notebooks are in the first chapter.\n", 111 | "\n", 112 | "The other big change is that I've added advice for working with virtual assistants like ChatGPT and using them to accelerate your learning.\n", 113 | "When the previous edition of this book was published in 2016, the predecessors of these tools were far less useful and most people were unaware of them. \n", 114 | "Now they are a standard tool for software engineering, and I think they will be a transformational tool for learning to program -- and learning a lot of other things, too.\n", 115 | "\n", 116 | "The other changes in the book were motivated by my regrets about the second edition.\n", 117 | "\n", 118 | "The first is that I did not emphasize software testing.\n", 119 | "That was already a regrettable omission in 2016, but with the advent of virtual assistants, automated testing has become even more important.\n", 120 | "So this edition presents Python's most widely-used testing tools, `doctest` and `unittest`, and includes several exercises where you can practice working with them.\n", 121 | "\n", 122 | "My other regret is that the exercises in the second edition were uneven -- some were more interesting than others and some were too hard.\n", 123 | "Moving to Jupyter notebooks helped me develop and test a more engaging and effective sequence of exercises.\n", 124 | "\n", 125 | "In this revision, the sequence of topics is almost the same, but I rearranged a few of the chapters and compressed two short chapters into one.\n", 126 | "Also, I expanded the coverage of strings to include regular expressions.\n", 127 | "\n", 128 | "A few chapters use turtle graphics.\n", 129 | "In previous editions, I used Python's `turtle` module, but unfortunately it doesn't work in Jupyter notebooks.\n", 130 | "So I replaced it with a new turtle module that should be easier to use.\n", 131 | "\n", 132 | "Finally, I rewrote a substantial fraction of the text, clarifying places that needed it and cutting back in places where I was not as concise as I could be.\n", 133 | "\n", 134 | "I am very proud of this new edition -- I hope you like it!" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "bfb779bb", 140 | "metadata": {}, 141 | "source": [ 142 | "## Getting started\n", 143 | "\n", 144 | "For most programming languages, including Python, there are many tools you can use to write and run programs. \n", 145 | "These tools are called integrated development environments (IDEs).\n", 146 | "In general, there are two kinds of IDEs:\n", 147 | "\n", 148 | "* Some work with files that contain code, so they provide tools for editing and running these files.\n", 149 | "\n", 150 | "* Others work primarily with notebooks, which are documents that contain text and code.\n", 151 | "\n", 152 | "For beginners, I recommend starting with a notebook development environment like Jupyter.\n", 153 | "\n", 154 | "The notebooks for this book are available from an online repository at .\n", 155 | "\n", 156 | "There are two ways to use them:\n", 157 | "\n", 158 | "* You can download the notebooks and run them on your own computer. In that case, you have to install Python and Jupyter, which is not hard, but if you want to learn Python, it can be frustrating to spend a lot of time installing software.\n", 159 | "\n", 160 | "* An alternative is to run the notebooks on Colab, which is a Jupyter environment that runs in a web browser, so you don't have to install anything. Colab is operated by Google, and it is free to use.\n", 161 | "\n", 162 | "If you are just getting started, I strongly recommend you start with Colab." 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "2ebd2412", 168 | "metadata": {}, 169 | "source": [ 170 | "## Resources for Teachers\n", 171 | "\n", 172 | "If you are teaching with this book, here are some resources you might find useful.\n", 173 | "\n", 174 | "* You can find notebooks with solutions to the exercises from , along with links to the additional resources below.\n", 175 | "\n", 176 | "* Quizzes for each chapter, and a summative quiz for the whole book, are available from [COMING SOON]\n", 177 | "\n", 178 | "* *Teaching and Learning with Jupyter* is an online book with suggestions for using Jupyter effectively in the classroom. You can read the book at \n", 179 | "\n", 180 | "* One of the best ways to use notebooks is live coding, where an instructor writes code and students follow along in their own notebooks. To learn about live coding -- and get other great advice about teaching programming -- I recommend the instructor training provided by The Carpentries, at " 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "28e7de55", 186 | "metadata": {}, 187 | "source": [ 188 | "## Acknowledgments\n", 189 | "\n", 190 | "Many thanks to Jeff Elkner, who translated my Java book into Python,\n", 191 | "which got this project started and introduced me to what has turned out\n", 192 | "to be my favorite language.\n", 193 | "Thanks also to Chris Meyers, who contributed several sections to *How to Think Like a Computer Scientist*.\n", 194 | "\n", 195 | "Thanks to the Free Software Foundation for developing the GNU Free Documentation License, which helped make my collaboration with Jeff and Chris possible, and thanks to the Creative Commons for the license I am using now.\n", 196 | "\n", 197 | "Thanks to the developers and maintainers of the Python language and the libraries I used, including the Turtle graphics module; the tools I used to develop the book, including Jupyter and JupyterBook; and the services I used, including ChatGPT, Copilot, Colab and GitHub.\n", 198 | "\n", 199 | "Thanks to the editors at Lulu who worked on *How to Think Like a Computer Scientist* and the editors at O'Reilly Media who worked on *Think Python*.\n", 200 | "\n", 201 | "Special thanks to the technical reviewers for the second edition, Melissa Lewis and Luciano Ramalho, and for the third edition, Sam Lau and Luciano Ramalho (again!).\n", 202 | "I am also grateful to Luciano for developing the turtle graphics module I use in several chapters, called `jupyturtle`.\n", 203 | "\n", 204 | "Thanks to all the students who worked with earlier versions of this book and all the contributors who sent in corrections and suggestions.\n", 205 | "More than 100 sharp-eyed and thoughtful readers have sent in suggestions and corrections over the past few years. Their contributions, and enthusiasm for this project, have been a huge help.\n", 206 | "\n", 207 | "If you have a suggestion or correction, please send email to `feedback@thinkpython.com`.\n", 208 | "If you include at least part of the sentence the error appears in, that\n", 209 | "makes it easy for me to search. Page and section numbers are fine, too,\n", 210 | "but not quite as easy to work with. Thanks!" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "id": "4e31cebe", 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "id": "a7f4edf8", 224 | "metadata": { 225 | "tags": [] 226 | }, 227 | "source": [ 228 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 229 | "\n", 230 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 231 | "\n", 232 | "Code license: [MIT License](https://mit-license.org/)\n", 233 | "\n", 234 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 235 | ] 236 | } 237 | ], 238 | "metadata": { 239 | "kernelspec": { 240 | "display_name": "Python 3 (ipykernel)", 241 | "language": "python", 242 | "name": "python3" 243 | }, 244 | "language_info": { 245 | "codemirror_mode": { 246 | "name": "ipython", 247 | "version": 3 248 | }, 249 | "file_extension": ".py", 250 | "mimetype": "text/x-python", 251 | "name": "python", 252 | "nbconvert_exporter": "python", 253 | "pygments_lexer": "ipython3", 254 | "version": "3.10.11" 255 | } 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 5 259 | } 260 | -------------------------------------------------------------------------------- /blank/chap03.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "id": "103cbe3c", 17 | "metadata": { 18 | "tags": [] 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "from os.path import basename, exists\n", 23 | "\n", 24 | "def download(url):\n", 25 | " filename = basename(url)\n", 26 | " if not exists(filename):\n", 27 | " from urllib.request import urlretrieve\n", 28 | "\n", 29 | " local, _ = urlretrieve(url, filename)\n", 30 | " print(\"Downloaded \" + str(local))\n", 31 | " return filename\n", 32 | "\n", 33 | "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", 34 | "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", 35 | "\n", 36 | "import thinkpython" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "id": "6bd858a8", 42 | "metadata": {}, 43 | "source": [ 44 | "# Functions\n", 45 | "\n", 46 | "In the previous chapter we used several functions provided by Python, like `int` and `float`, and a few provided by the `math` module, like `sqrt` and `pow`.\n", 47 | "In this chapter, you will learn how to create your own functions and run them.\n", 48 | "And we'll see how one function can call another.\n", 49 | "As examples, we'll display lyrics from Monty Python songs.\n", 50 | "These silly examples demonstrate an important feature -- the ability to write your own functions is the foundation of programming.\n", 51 | "\n", 52 | "This chapter also introduces a new statement, the `for` loop, which is used to repeat a computation." 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "id": "b4ea99c5", 58 | "metadata": {}, 59 | "source": [ 60 | "## Defining new functions\n", 61 | "\n", 62 | "A **function definition** specifies the name of a new function and the sequence of statements that run when the function is called. Here's an example:" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 2, 68 | "id": "d28f5c1a", 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "id": "0174fc41", 76 | "metadata": {}, 77 | "source": [ 78 | "`def` is a keyword that indicates that this is a function definition.\n", 79 | "The name of the function is `print_lyrics`.\n", 80 | "Anything that's a legal variable name is also a legal function name.\n", 81 | "\n", 82 | "The empty parentheses after the name indicate that this function doesn't take any arguments.\n", 83 | "\n", 84 | "The first line of the function definition is called the **header** -- the rest is called the **body**.\n", 85 | "The header has to end with a colon and the body has to be indented. By convention, indentation is always four spaces. \n", 86 | "The body of this function is two print statements; in general, the body of a function can contain any number of statements of any kind.\n", 87 | "\n", 88 | "Defining a function creates a **function object**, which we can display like this." 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 3, 94 | "id": "2850a402", 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "id": "12bd0879", 102 | "metadata": {}, 103 | "source": [ 104 | "The output indicates that `print_lyrics` is a function that takes no arguments.\n", 105 | "`__main__` is the name of the module that contains `print_lyrics`.\n", 106 | "\n", 107 | "Now that we've defined a function, we can call it the same way we call built-in functions." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 4, 113 | "id": "9a048657", 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "id": "8f0fc45d", 121 | "metadata": {}, 122 | "source": [ 123 | "When the function runs, it executes the statements in the body, which display the first two lines of \"The Lumberjack Song\"." 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "6d35193e", 129 | "metadata": {}, 130 | "source": [ 131 | "## Parameters\n", 132 | "\n", 133 | "Some of the functions we have seen require arguments; for example, when you call `abs` you pass a number as an argument.\n", 134 | "Some functions take more than one argument; for example, `math.pow` takes two, the base and the exponent.\n", 135 | "\n", 136 | "Here is a definition for a function that takes an argument." 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 5, 142 | "id": "e5d00488", 143 | "metadata": {}, 144 | "outputs": [], 145 | "source": [] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "id": "1716e3dc", 150 | "metadata": {}, 151 | "source": [ 152 | "The variable name in parentheses is a **parameter**.\n", 153 | "When the function is called, the value of the argument is assigned to the parameter.\n", 154 | "For example, we can call `print_twice` like this." 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 6, 160 | "id": "a3ad5f46", 161 | "metadata": {}, 162 | "outputs": [], 163 | "source": [] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "f02be6d2", 168 | "metadata": {}, 169 | "source": [ 170 | "Running this function has the same effect as assigning the argument to the parameter and then executing the body of the function, like this." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 7, 176 | "id": "042dfec1", 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "id": "ea8b8b6e", 184 | "metadata": {}, 185 | "source": [ 186 | "You can also use a variable as an argument." 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 8, 192 | "id": "8f078ad0", 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "id": "5c1884ad", 200 | "metadata": {}, 201 | "source": [ 202 | "In this example, the value of `line` gets assigned to the parameter `string`." 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "id": "a3e5a790", 208 | "metadata": {}, 209 | "source": [ 210 | "## Calling functions\n", 211 | "\n", 212 | "Once you have defined a function, you can use it inside another function.\n", 213 | "To demonstrate, we'll write functions that print the lyrics of \"The Spam Song\" ().\n", 214 | "\n", 215 | "> Spam, Spam, Spam, Spam, \n", 216 | "> Spam, Spam, Spam, Spam, \n", 217 | "> Spam, Spam, \n", 218 | "> (Lovely Spam, Wonderful Spam!) \n", 219 | "> Spam, Spam,\n", 220 | "\n", 221 | "We'll start with the following function, which takes two parameters.\n" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 9, 227 | "id": "e86bb32c", 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "id": "bdd4daa4", 235 | "metadata": {}, 236 | "source": [ 237 | "We can use this function to print the first line of the song, like this." 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 10, 243 | "id": "ec117999", 244 | "metadata": {}, 245 | "outputs": [], 246 | "source": [] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "id": "c6f81e09", 251 | "metadata": {}, 252 | "source": [ 253 | "To display the first two lines, we can define a new function that uses `repeat`." 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 11, 259 | "id": "3731ffd8", 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "id": "8058ffe4", 267 | "metadata": {}, 268 | "source": [ 269 | "And then call it like this." 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 12, 275 | "id": "6792e63b", 276 | "metadata": {}, 277 | "outputs": [], 278 | "source": [] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "id": "07ca432a", 283 | "metadata": {}, 284 | "source": [ 285 | "To display the last three lines, we can define another function, which also uses `repeat`." 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 13, 291 | "id": "2dcb020a", 292 | "metadata": {}, 293 | "outputs": [], 294 | "source": [] 295 | }, 296 | { 297 | "cell_type": "code", 298 | "execution_count": 14, 299 | "id": "9ff8c60e", 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "id": "d6456a19", 307 | "metadata": {}, 308 | "source": [ 309 | "Finally, we can bring it all together with one function that prints the whole verse." 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 15, 315 | "id": "78bf3a7b", 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [] 319 | }, 320 | { 321 | "cell_type": "code", 322 | "execution_count": 16, 323 | "id": "ba5da431", 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "id": "d088fe68", 331 | "metadata": {}, 332 | "source": [ 333 | "When we run `print_verse`, it calls `first_two_lines`, which calls `repeat`, which calls `print`.\n", 334 | "That's a lot of functions.\n", 335 | "\n", 336 | "Of course, we could have done the same thing with fewer functions, but the point of this example is to show how functions can work together." 337 | ] 338 | }, 339 | { 340 | "cell_type": "markdown", 341 | "id": "c3b16e3f", 342 | "metadata": {}, 343 | "source": [ 344 | "## Repetition\n", 345 | "\n", 346 | "If we want to display more than one verse, we can use a `for` statement.\n", 347 | "Here's a simple example." 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": 17, 353 | "id": "29b7eff3", 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "id": "bf320549", 361 | "metadata": {}, 362 | "source": [ 363 | "The first line is a header that ends with a colon.\n", 364 | "The second line is the body, which has to be indented.\n", 365 | "\n", 366 | "The header starts with the keyword `for`, a new variable named `i`, and another keyword, `in`. \n", 367 | "It uses the `range` function to create a sequence of two values, which are `0` and `1`.\n", 368 | "In Python, when we start counting, we usually start from `0`.\n", 369 | "\n", 370 | "When the `for` statement runs, it assigns the first value from `range` to `i` and then runs the `print` function in the body, which displays `0`.\n", 371 | "\n", 372 | "When it gets to the end of the body, it loops back around to the header, which is why this statement is called a **loop**.\n", 373 | "The second time through the loop, it assigns the next value from `range` to `i`, and displays it.\n", 374 | "Then, because that's the last value from `range`, the loop ends.\n", 375 | "\n", 376 | "Here's how we can use a `for` loop to print two verses of the song." 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": 18, 382 | "id": "038ad592", 383 | "metadata": {}, 384 | "outputs": [], 385 | "source": [] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "id": "88a46733", 390 | "metadata": {}, 391 | "source": [ 392 | "You can put a `for` loop inside a function.\n", 393 | "For example, `print_n_verses` takes a parameter named `n`, which has to be an integer, and displays the given number of verses. " 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 19, 399 | "id": "8887637a", 400 | "metadata": {}, 401 | "outputs": [], 402 | "source": [] 403 | }, 404 | { 405 | "cell_type": "markdown", 406 | "id": "ad8060fe", 407 | "metadata": {}, 408 | "source": [ 409 | "In this example, we don't use `i` in the body of the loop, but there has to be a variable name in the header anyway." 410 | ] 411 | }, 412 | { 413 | "cell_type": "markdown", 414 | "id": "b320ec90", 415 | "metadata": {}, 416 | "source": [ 417 | "## Variables and parameters are local\n", 418 | "\n", 419 | "When you create a variable inside a function, it is **local**, which\n", 420 | "means that it only exists inside the function.\n", 421 | "For example, the following function takes two arguments, concatenates them, and prints the result twice." 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 20, 427 | "id": "0db8408e", 428 | "metadata": {}, 429 | "outputs": [], 430 | "source": [] 431 | }, 432 | { 433 | "cell_type": "markdown", 434 | "id": "3a35a6d0", 435 | "metadata": {}, 436 | "source": [ 437 | "Here's an example that uses it:" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": 21, 443 | "id": "1c556e48", 444 | "metadata": {}, 445 | "outputs": [], 446 | "source": [] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "id": "4ab4e008", 451 | "metadata": {}, 452 | "source": [ 453 | "When `cat_twice` runs, it creates a local variable named `cat`, which is destroyed when the function ends.\n", 454 | "If we try to display it, we get a `NameError`:" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": 22, 460 | "id": "73f03eea", 461 | "metadata": { 462 | "tags": [] 463 | }, 464 | "outputs": [], 465 | "source": [] 466 | }, 467 | { 468 | "cell_type": "markdown", 469 | "id": "3ae36c29", 470 | "metadata": {}, 471 | "source": [ 472 | "Outside of the function, `cat` is not defined. \n", 473 | "\n", 474 | "Parameters are also local.\n", 475 | "For example, outside `cat_twice`, there is no such thing as `part1` or `part2`." 476 | ] 477 | }, 478 | { 479 | "cell_type": "markdown", 480 | "id": "eabac8a6", 481 | "metadata": {}, 482 | "source": [ 483 | "## Stack diagrams\n", 484 | "\n", 485 | "To keep track of which variables can be used where, it is sometimes useful to draw a **stack diagram**. \n", 486 | "Like state diagrams, stack diagrams show the value of each variable, but they also show the function each variable belongs to.\n", 487 | "\n", 488 | "Each function is represented by a **frame**.\n", 489 | "A frame is a box with the name of a function on the outside and the parameters and local variables of the function on the inside.\n", 490 | "\n", 491 | "Here's the stack diagram for the previous example." 492 | ] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": 23, 497 | "id": "83df4e32", 498 | "metadata": { 499 | "tags": [] 500 | }, 501 | "outputs": [], 502 | "source": [ 503 | "from diagram import make_frame, Stack\n", 504 | "\n", 505 | "d1 = dict(line1=line1, line2=line2)\n", 506 | "frame1 = make_frame(d1, name='__main__', dy=-0.3, loc='left')\n", 507 | "\n", 508 | "d2 = dict(part1=line1, part2=line2, cat=line1+line2)\n", 509 | "frame2 = make_frame(d2, name='cat_twice', dy=-0.3, \n", 510 | " offsetx=0.03, loc='left')\n", 511 | "\n", 512 | "d3 = dict(string=line1+line2)\n", 513 | "frame3 = make_frame(d3, name='print_twice', \n", 514 | " offsetx=0.04, offsety=-0.3, loc='left')\n", 515 | "\n", 516 | "d4 = {\"?\": line1+line2}\n", 517 | "frame4 = make_frame(d4, name='print', \n", 518 | " offsetx=-0.22, offsety=0, loc='left')\n", 519 | "\n", 520 | "stack = Stack([frame1, frame2, frame3, frame4], dy=-0.8)" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 24, 526 | "id": "bcd5e1df", 527 | "metadata": { 528 | "tags": [] 529 | }, 530 | "outputs": [], 531 | "source": [ 532 | "from diagram import diagram, adjust\n", 533 | "\n", 534 | "\n", 535 | "width, height, x, y = [3.77, 2.9, 1.1, 2.65]\n", 536 | "ax = diagram(width, height)\n", 537 | "bbox = stack.draw(ax, x, y)\n", 538 | "# adjust(x, y, bbox)" 539 | ] 540 | }, 541 | { 542 | "cell_type": "markdown", 543 | "id": "854fee12", 544 | "metadata": {}, 545 | "source": [ 546 | "The frames are arranged in a stack that indicates which function called\n", 547 | "which, and so on. Reading from the bottom, `print` was called by `print_twice`, which was called by `cat_twice`, which was called by `__main__` -- which is a special name for the topmost frame.\n", 548 | "When you create a variable outside of any function, it belongs to `__main__`.\n", 549 | "\n", 550 | "In the frame for `print`, the question mark indicates that we don't know the name of the parameter.\n", 551 | "If you are curious, ask a virtual assistant, \"What are the parameters of the Python print function?\"" 552 | ] 553 | }, 554 | { 555 | "cell_type": "markdown", 556 | "id": "5690cfc0", 557 | "metadata": {}, 558 | "source": [ 559 | "## Tracebacks\n", 560 | "\n", 561 | "When a runtime error occurs in a function, Python displays the name of the function that was running, the name of the function that called it, and so on, up the stack.\n", 562 | "To see an example, I'll define a version of `print_twice` that contains an error -- it tries to print `cat`, which is a local variable in another function." 563 | ] 564 | }, 565 | { 566 | "cell_type": "code", 567 | "execution_count": 25, 568 | "id": "886519cf", 569 | "metadata": {}, 570 | "outputs": [], 571 | "source": [] 572 | }, 573 | { 574 | "cell_type": "markdown", 575 | "id": "d7c0713b", 576 | "metadata": {}, 577 | "source": [ 578 | "Now here's what happens when we run `cat_twice`." 579 | ] 580 | }, 581 | { 582 | "cell_type": "code", 583 | "execution_count": 26, 584 | "id": "1fe8ee82", 585 | "metadata": { 586 | "tags": [] 587 | }, 588 | "outputs": [], 589 | "source": [ 590 | "# This cell tells Jupyter to provide detailed debugging information\n", 591 | "# when a runtime error occurs, including a traceback.\n", 592 | "\n", 593 | "%xmode Verbose" 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": 27, 599 | "id": "d9082f88", 600 | "metadata": { 601 | "tags": [] 602 | }, 603 | "outputs": [], 604 | "source": [] 605 | }, 606 | { 607 | "cell_type": "markdown", 608 | "id": "2f4defcf", 609 | "metadata": {}, 610 | "source": [ 611 | "The error message includes a **traceback**, which shows the function that was running when the error occurred, the function that called it, and so on.\n", 612 | "In this example, it shows that `cat_twice` called `print_twice`, and the error occurred in a `print_twice`.\n", 613 | "\n", 614 | "The order of the functions in the traceback is the same as the order of the frames in the stack diagram.\n", 615 | "The function that was running is at the bottom." 616 | ] 617 | }, 618 | { 619 | "cell_type": "markdown", 620 | "id": "374b4696", 621 | "metadata": {}, 622 | "source": [ 623 | "## Why functions?\n", 624 | "\n", 625 | "It may not be clear yet why it is worth the trouble to divide a program into\n", 626 | "functions.\n", 627 | "There are several reasons:\n", 628 | "\n", 629 | "- Creating a new function gives you an opportunity to name a group of\n", 630 | " statements, which makes your program easier to read and debug.\n", 631 | "\n", 632 | "- Functions can make a program smaller by eliminating repetitive code.\n", 633 | " Later, if you make a change, you only have to make it in one place.\n", 634 | "\n", 635 | "- Dividing a long program into functions allows you to debug the parts\n", 636 | " one at a time and then assemble them into a working whole.\n", 637 | "\n", 638 | "- Well-designed functions are often useful for many programs. Once you\n", 639 | " write and debug one, you can reuse it." 640 | ] 641 | }, 642 | { 643 | "cell_type": "markdown", 644 | "id": "c6dd486e", 645 | "metadata": {}, 646 | "source": [ 647 | "## Debugging\n", 648 | "\n", 649 | "Debugging can be frustrating, but it is also challenging, interesting, and sometimes even fun.\n", 650 | "And it is one of the most important skills you can learn.\n", 651 | "\n", 652 | "In some ways debugging is like detective work.\n", 653 | "You are given clues and you have to infer the events that led to the\n", 654 | "results you see.\n", 655 | "\n", 656 | "Debugging is also like experimental science.\n", 657 | "Once you have an idea about what is going wrong, you modify your program and try again.\n", 658 | "If your hypothesis was correct, you can predict the result of the modification, and you take a step closer to a working program.\n", 659 | "If your hypothesis was wrong, you have to come up with a new one.\n", 660 | "\n", 661 | "For some people, programming and debugging are the same thing; that is, programming is the process of gradually debugging a program until it does what you want.\n", 662 | "The idea is that you should start with a working program and make small modifications, debugging them as you go.\n", 663 | "\n", 664 | "If you find yourself spending a lot of time debugging, that is often a sign that you are writing too much code before you start tests.\n", 665 | "If you take smaller steps, you might find that you can move faster." 666 | ] 667 | }, 668 | { 669 | "cell_type": "markdown", 670 | "id": "d4e95e63", 671 | "metadata": {}, 672 | "source": [ 673 | "## Glossary\n", 674 | "\n", 675 | "**function definition:**\n", 676 | "A statement that creates a function.\n", 677 | "\n", 678 | "**header:**\n", 679 | " The first line of a function definition.\n", 680 | "\n", 681 | "**body:**\n", 682 | " The sequence of statements inside a function definition.\n", 683 | "\n", 684 | "**function object:**\n", 685 | "A value created by a function definition.\n", 686 | "The name of the function is a variable that refers to a function object.\n", 687 | "\n", 688 | "**parameter:**\n", 689 | " A name used inside a function to refer to the value passed as an argument.\n", 690 | "\n", 691 | "**loop:**\n", 692 | " A statement that runs one or more statements, often repeatedly.\n", 693 | "\n", 694 | "**local variable:**\n", 695 | "A variable defined inside a function, and which can only be accessed inside the function.\n", 696 | "\n", 697 | "**stack diagram:**\n", 698 | "A graphical representation of a stack of functions, their variables, and the values they refer to.\n", 699 | "\n", 700 | "**frame:**\n", 701 | " A box in a stack diagram that represents a function call.\n", 702 | " It contains the local variables and parameters of the function.\n", 703 | "\n", 704 | "**traceback:**\n", 705 | " A list of the functions that are executing, printed when an exception occurs." 706 | ] 707 | }, 708 | { 709 | "cell_type": "markdown", 710 | "id": "eca485f2", 711 | "metadata": {}, 712 | "source": [ 713 | "## Exercises" 714 | ] 715 | }, 716 | { 717 | "cell_type": "code", 718 | "execution_count": 28, 719 | "id": "3f77b428", 720 | "metadata": { 721 | "tags": [] 722 | }, 723 | "outputs": [], 724 | "source": [ 725 | "# This cell tells Jupyter to provide detailed debugging information\n", 726 | "# when a runtime error occurs. Run it before working on the exercises.\n", 727 | "\n", 728 | "%xmode Verbose" 729 | ] 730 | }, 731 | { 732 | "cell_type": "markdown", 733 | "id": "82951027", 734 | "metadata": {}, 735 | "source": [ 736 | "### Ask a virtual assistant\n", 737 | "\n", 738 | "The statements in a function or a `for` loop are indented by four spaces, by convention.\n", 739 | "But not everyone agrees with that convention.\n", 740 | "If you are curious about the history of this great debate, ask a virtual assistant to \"tell me about spaces and tabs in Python\".\n", 741 | "\n", 742 | "Virtual assistant are pretty good at writing small functions.\n", 743 | "\n", 744 | "1. Ask your favorite VA to \"Write a function called repeat that takes a string and an integer and prints the string the given number of times.\" \n", 745 | "\n", 746 | "2. If the result uses a `for` loop, you could ask, \"Can you do it without a for loop?\"\n", 747 | "\n", 748 | "3. Pick any other function in this chapter and ask a VA to write it. The challenge is to describe the function precisely enough to get what you want. Use the vocabulary you have learned so far in this book.\n", 749 | "\n", 750 | "Virtual assistants are also pretty good at debugging functions.\n", 751 | "\n", 752 | "1. Ask a VA what's wrong with this version of `print_twice`.\n", 753 | "\n", 754 | " ```\n", 755 | " def print_twice(string):\n", 756 | " print(cat)\n", 757 | " print(cat)\n", 758 | " ```\n", 759 | " \n", 760 | "And if you get stuck on any of the exercises below, consider asking a VA for help." 761 | ] 762 | }, 763 | { 764 | "cell_type": "markdown", 765 | "id": "b7157b09", 766 | "metadata": {}, 767 | "source": [ 768 | "### Exercise\n", 769 | "\n", 770 | "Write a function named `print_right` that takes a string named `text` as a parameter and prints the string with enough leading spaces that the last letter of the string is in the 40th column of the display." 771 | ] 772 | }, 773 | { 774 | "cell_type": "code", 775 | "execution_count": 29, 776 | "id": "a6004271", 777 | "metadata": {}, 778 | "outputs": [], 779 | "source": [] 780 | }, 781 | { 782 | "cell_type": "markdown", 783 | "id": "428fbee5", 784 | "metadata": {}, 785 | "source": [ 786 | "Hint: Use the `len` function, the string concatenation operator (`+`) and the string repetition operator (`*`).\n", 787 | "\n", 788 | "Here's an example that shows how it should work." 789 | ] 790 | }, 791 | { 792 | "cell_type": "code", 793 | "execution_count": 30, 794 | "id": "f142ce6a", 795 | "metadata": { 796 | "tags": [] 797 | }, 798 | "outputs": [], 799 | "source": [ 800 | "print_right(\"Monty\")\n", 801 | "print_right(\"Python's\")\n", 802 | "print_right(\"Flying Circus\")" 803 | ] 804 | }, 805 | { 806 | "cell_type": "markdown", 807 | "id": "b47467fa", 808 | "metadata": {}, 809 | "source": [ 810 | "### Exercise\n", 811 | "\n", 812 | "Write a function called `triangle` that takes a string and an integer and draws a pyramid with the given height, made up using copies of the string. Here's an example of a pyramid with `5` levels, using the string `'L'`." 813 | ] 814 | }, 815 | { 816 | "cell_type": "code", 817 | "execution_count": 31, 818 | "id": "7aa95014", 819 | "metadata": {}, 820 | "outputs": [], 821 | "source": [] 822 | }, 823 | { 824 | "cell_type": "code", 825 | "execution_count": 32, 826 | "id": "b8146a0d", 827 | "metadata": { 828 | "scrolled": true, 829 | "tags": [] 830 | }, 831 | "outputs": [], 832 | "source": [ 833 | "triangle('L', 5)" 834 | ] 835 | }, 836 | { 837 | "cell_type": "markdown", 838 | "id": "4a28f635", 839 | "metadata": {}, 840 | "source": [ 841 | "### Exercise\n", 842 | "\n", 843 | "Write a function called `rectangle` that takes a string and two integers and draws a rectangle with the given width and height, made up using copies of the string. Here's an example of a rectangle with width `5` and height `4`, made up of the string `'H'`." 844 | ] 845 | }, 846 | { 847 | "cell_type": "code", 848 | "execution_count": 33, 849 | "id": "bcedab79", 850 | "metadata": {}, 851 | "outputs": [], 852 | "source": [] 853 | }, 854 | { 855 | "cell_type": "code", 856 | "execution_count": 34, 857 | "id": "73b0c0f6", 858 | "metadata": { 859 | "scrolled": true, 860 | "tags": [] 861 | }, 862 | "outputs": [], 863 | "source": [ 864 | "rectangle('H', 5, 4)" 865 | ] 866 | }, 867 | { 868 | "cell_type": "markdown", 869 | "id": "44a5de6f", 870 | "metadata": {}, 871 | "source": [ 872 | "### Exercise\n", 873 | "\n", 874 | "The song \"99 Bottles of Beer\" starts with this verse:\n", 875 | "\n", 876 | "> 99 bottles of beer on the wall \n", 877 | "> 99 bottles of beer \n", 878 | "> Take one down, pass it around \n", 879 | "> 98 bottles of beer on the wall \n", 880 | "\n", 881 | "Then the second verse is the same, except that it starts with 98 bottles and ends with 97. The song continues -- for a very long time -- until there are 0 bottles of beer.\n", 882 | "\n", 883 | "Write a function called `bottle_verse` that takes a number as a parameter and displays the verse that starts with the given number of bottles.\n", 884 | "\n", 885 | "Hint: Consider starting with a function that can print the first, second, or last line of the verse, and then use it to write `bottle_verse`." 886 | ] 887 | }, 888 | { 889 | "cell_type": "code", 890 | "execution_count": 35, 891 | "id": "53424b43", 892 | "metadata": {}, 893 | "outputs": [], 894 | "source": [] 895 | }, 896 | { 897 | "cell_type": "code", 898 | "execution_count": 36, 899 | "id": "61010ffb", 900 | "metadata": {}, 901 | "outputs": [], 902 | "source": [] 903 | }, 904 | { 905 | "cell_type": "markdown", 906 | "id": "ee0076dd", 907 | "metadata": { 908 | "tags": [] 909 | }, 910 | "source": [ 911 | "Use this function call to display the first verse." 912 | ] 913 | }, 914 | { 915 | "cell_type": "code", 916 | "execution_count": 37, 917 | "id": "47a91c7d", 918 | "metadata": { 919 | "tags": [] 920 | }, 921 | "outputs": [], 922 | "source": [ 923 | "bottle_verse(99)" 924 | ] 925 | }, 926 | { 927 | "cell_type": "markdown", 928 | "id": "42c237c6", 929 | "metadata": { 930 | "tags": [] 931 | }, 932 | "source": [ 933 | "If you want to print the whole song, you can use this `for` loop, which counts down from `99` to `1`.\n", 934 | "You don't have to completely understand this example---we'll learn more about `for` loops and the `range` function later." 935 | ] 936 | }, 937 | { 938 | "cell_type": "code", 939 | "execution_count": 38, 940 | "id": "336cdfa2", 941 | "metadata": { 942 | "tags": [] 943 | }, 944 | "outputs": [], 945 | "source": [ 946 | "for n in range(99, 0, -1):\n", 947 | " bottle_verse(n)\n", 948 | " print()" 949 | ] 950 | }, 951 | { 952 | "cell_type": "code", 953 | "execution_count": null, 954 | "id": "4b02510c", 955 | "metadata": {}, 956 | "outputs": [], 957 | "source": [] 958 | }, 959 | { 960 | "cell_type": "markdown", 961 | "id": "a7f4edf8", 962 | "metadata": { 963 | "tags": [] 964 | }, 965 | "source": [ 966 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 967 | "\n", 968 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 969 | "\n", 970 | "Code license: [MIT License](https://mit-license.org/)\n", 971 | "\n", 972 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 973 | ] 974 | } 975 | ], 976 | "metadata": { 977 | "celltoolbar": "Tags", 978 | "kernelspec": { 979 | "display_name": "Python 3 (ipykernel)", 980 | "language": "python", 981 | "name": "python3" 982 | }, 983 | "language_info": { 984 | "codemirror_mode": { 985 | "name": "ipython", 986 | "version": 3 987 | }, 988 | "file_extension": ".py", 989 | "mimetype": "text/x-python", 990 | "name": "python", 991 | "nbconvert_exporter": "python", 992 | "pygments_lexer": "ipython3", 993 | "version": "3.10.11" 994 | } 995 | }, 996 | "nbformat": 4, 997 | "nbformat_minor": 5 998 | } 999 | -------------------------------------------------------------------------------- /blank/chap15.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "id": "3161b50b", 17 | "metadata": { 18 | "tags": [] 19 | }, 20 | "outputs": [], 21 | "source": [] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "fa22117f", 26 | "metadata": {}, 27 | "source": [ 28 | "# Classes and Methods\n", 29 | "\n", 30 | "Python is an **object-oriented language** -- that is, it provides features that support object-oriented programming, which has these defining characteristics:\n", 31 | "\n", 32 | "- Most of the computation is expressed in terms of operations on objects.\n", 33 | "\n", 34 | "- Objects often represent things in the real world, and methods often correspond to the ways things in the real world interact.\n", 35 | "\n", 36 | "- Programs include class and method definitions.\n", 37 | "\n", 38 | "For example, in the previous chapter we defined a `Time` class that corresponds to the way people record the time of day, and we defined functions that correspond to the kinds of things people do with times.\n", 39 | "But there was no explicit connection between the definition of the `Time` class and the function definitions that follow.\n", 40 | "We can make the connection explicit by rewriting a function as a **method**, which is defined inside a class definition." 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "id": "9857823a", 46 | "metadata": {}, 47 | "source": [ 48 | "## Defining methods\n", 49 | "\n", 50 | "In the previous chapter we defined a class named `Time` and wrote a function named `print_time` that displays a time of day." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 2, 56 | "id": "ee093ca4", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "a89ddf58", 64 | "metadata": {}, 65 | "source": [ 66 | "To make `print_time` a method, all we have to do is move the function\n", 67 | "definition inside the class definition. Notice the change in\n", 68 | "indentation.\n", 69 | "\n", 70 | "At the same time, we'll change the name of the parameter from `time` to `self`.\n", 71 | "This change is not necessary, but it is conventional for the first parameter of a method to be named `self`." 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 3, 77 | "id": "fd26a1bc", 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "id": "8da4079c", 85 | "metadata": {}, 86 | "source": [ 87 | "To call this method, you have to pass a `Time` object as an argument.\n", 88 | "Here's the function we'll use to make a `Time` object." 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 4, 94 | "id": "5fc157ea", 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "id": "c6ad4e12", 102 | "metadata": {}, 103 | "source": [ 104 | "And here's a `Time` instance." 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 5, 110 | "id": "35acd8e6", 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "id": "bbbcd333", 118 | "metadata": {}, 119 | "source": [ 120 | "Now there are two ways to call `print_time`. The first (and less common)\n", 121 | "way is to use function syntax." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 6, 127 | "id": "f755081c", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "id": "2eb0847e", 135 | "metadata": {}, 136 | "source": [ 137 | "In this version, `Time` is the name of the class, `print_time` is the name of the method, and `start` is passed as a parameter.\n", 138 | "The second (and more idiomatic) way is to use method syntax:" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 7, 144 | "id": "d6f91aec", 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "id": "c80c40f0", 152 | "metadata": {}, 153 | "source": [ 154 | "In this version, `start` is the object the method is invoked on, which is called the **receiver**, based on the analogy that invoking a method is like sending a message to an object.\n", 155 | "\n", 156 | "Regardless of the syntax, the behavior of the method is the same.\n", 157 | "The receiver is assigned to the first parameter, so inside the method, `self` refers to the same object as `start`." 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "8deb6c34", 163 | "metadata": {}, 164 | "source": [ 165 | "## Another method\n", 166 | "\n", 167 | "Here's the `time_to_int` function from the previous chapter." 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 8, 173 | "id": "24c4c985", 174 | "metadata": {}, 175 | "outputs": [], 176 | "source": [] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "id": "144e043f", 181 | "metadata": {}, 182 | "source": [ 183 | "And here's a version rewritten as a method.\n" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 9, 189 | "id": "dde6f15f", 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "id": "e3a721ab", 197 | "metadata": {}, 198 | "source": [ 199 | "The first line uses the special command `add_method_to`, which adds a method to a previously-defined class.\n", 200 | "This command works in a Jupyter notebook, but it is not part of Python, so it won't work in other environments.\n", 201 | "Normally, all methods of a class are inside the class definition, so they get defined at the same time as the class.\n", 202 | "But for this book, it is helpful to define one method at a time.\n", 203 | "\n", 204 | "As in the previous example, the method definition is indented and the name of the parameter is `self`.\n", 205 | "Other than that, the method is identical to the function.\n", 206 | "Here's how we invoke it." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 10, 212 | "id": "8943fa0a", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "id": "14565505", 220 | "metadata": {}, 221 | "source": [ 222 | "It is common to say that we \"call\" a function and \"invoke\" a method, but they mean the same thing." 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "id": "7bc24683", 228 | "metadata": {}, 229 | "source": [ 230 | "## Static methods\n", 231 | "\n", 232 | "As another example, let's consider the `int_to_time` function.\n", 233 | "Here's the version from the previous chapter." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 11, 239 | "id": "8547b1c2", 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "id": "2b77c2a0", 247 | "metadata": {}, 248 | "source": [ 249 | "This function takes `seconds` as a parameter and returns a new `Time` object.\n", 250 | "If we transform it into a method of the `Time` class, we have to invoke it on a `Time` object.\n", 251 | "But if we're trying to create a new `Time` object, what are we supposed to invoke it on?\n", 252 | "\n", 253 | "We can solve this chicken-and-egg problem using a **static method**, which is a method that does not require an instance of the class to be invoked.\n", 254 | "Here's how we rewrite this function as a static method." 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 12, 260 | "id": "b233669c", 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "id": "a7e2e788", 268 | "metadata": {}, 269 | "source": [ 270 | "Because it is a static method, it does not have `self` as a parameter.\n", 271 | "To invoke it, we use `Time`, which is the class object." 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 13, 277 | "id": "7e88f06b", 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [] 281 | }, 282 | { 283 | "cell_type": "markdown", 284 | "id": "d2f4fd5a", 285 | "metadata": {}, 286 | "source": [ 287 | "The result is a new object that represents 9:40." 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": 14, 293 | "id": "8c9f66b0", 294 | "metadata": {}, 295 | "outputs": [], 296 | "source": [] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "id": "e6a18c76", 301 | "metadata": {}, 302 | "source": [ 303 | "Now that we have `Time.from_seconds`, we can use it to write `add_time` as a method.\n", 304 | "Here's the function from the previous chapter." 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 15, 310 | "id": "c600d536", 311 | "metadata": {}, 312 | "outputs": [], 313 | "source": [] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "id": "8e56da48", 318 | "metadata": {}, 319 | "source": [ 320 | "And here's a version rewritten as a method." 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 16, 326 | "id": "c6fa0176", 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [] 330 | }, 331 | { 332 | "cell_type": "markdown", 333 | "id": "b784a4ea", 334 | "metadata": {}, 335 | "source": [ 336 | "`add_time` has `self` as a parameter because it is not a static method.\n", 337 | "It is an ordinary method -- also called an **instance method**.\n", 338 | "To invoke it, we need a `Time` instance." 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": 17, 344 | "id": "e17b2ad7", 345 | "metadata": {}, 346 | "outputs": [], 347 | "source": [] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "id": "f1c806a9", 352 | "metadata": {}, 353 | "source": [ 354 | "## Comparing Time objects\n", 355 | "\n", 356 | "As one more example, let's write `is_after` as a method.\n", 357 | "Here's the `is_after` function, which is a solution to an exercise in the previous chapter." 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 18, 363 | "id": "971eebbb", 364 | "metadata": {}, 365 | "outputs": [], 366 | "source": [] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "id": "8e7153e8", 371 | "metadata": {}, 372 | "source": [ 373 | "And here it is as a method." 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": 19, 379 | "id": "90d7234d", 380 | "metadata": {}, 381 | "outputs": [], 382 | "source": [] 383 | }, 384 | { 385 | "cell_type": "markdown", 386 | "id": "50815aec", 387 | "metadata": {}, 388 | "source": [ 389 | "Because we're comparing two objects, and the first parameter is `self`, we'll call the second parameter `other`.\n", 390 | "To use this method, we have to invoke it on one object and pass the\n", 391 | "other as an argument." 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 20, 397 | "id": "19e3d639", 398 | "metadata": {}, 399 | "outputs": [], 400 | "source": [] 401 | }, 402 | { 403 | "cell_type": "markdown", 404 | "id": "cf97e358", 405 | "metadata": {}, 406 | "source": [ 407 | "One nice thing about this syntax is that it almost reads like a question,\n", 408 | "\"`end` is after `start`?\"" 409 | ] 410 | }, 411 | { 412 | "cell_type": "markdown", 413 | "id": "15a17fce", 414 | "metadata": {}, 415 | "source": [ 416 | "## The `__str__` method\n", 417 | "\n", 418 | "When you write a method, you can choose almost any name you want.\n", 419 | "However, some names have special meanings.\n", 420 | "For example, if an object has a method named `__str__`, Python uses that method to convert the object to a string.\n", 421 | "For example, here is a `__str__` method for a time object." 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 21, 427 | "id": "f935a999", 428 | "metadata": {}, 429 | "outputs": [], 430 | "source": [] 431 | }, 432 | { 433 | "cell_type": "markdown", 434 | "id": "b056b729", 435 | "metadata": {}, 436 | "source": [ 437 | "This method is similar to `print_time`, from the previous chapter, except that it returns the string rather than printing it.\n", 438 | "\n", 439 | "You can invoke this method in the usual way." 440 | ] 441 | }, 442 | { 443 | "cell_type": "code", 444 | "execution_count": 22, 445 | "id": "61d7275d", 446 | "metadata": {}, 447 | "outputs": [], 448 | "source": [] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "id": "76092a0c", 453 | "metadata": {}, 454 | "source": [ 455 | "But Python can also invoke it for you.\n", 456 | "If you use the built-in function `str` to convert a `Time` object to a string, Python uses the `__str__` method in the `Time` class." 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 23, 462 | "id": "b6dcc0c2", 463 | "metadata": {}, 464 | "outputs": [], 465 | "source": [] 466 | }, 467 | { 468 | "cell_type": "markdown", 469 | "id": "8a26caa8", 470 | "metadata": {}, 471 | "source": [ 472 | "And it does the same if you print a `Time` object." 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": 24, 478 | "id": "6e1e6fb3", 479 | "metadata": {}, 480 | "outputs": [], 481 | "source": [] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "id": "97eb30c2", 486 | "metadata": {}, 487 | "source": [ 488 | "Methods like `__str__` are called **special methods**.\n", 489 | "You can identify them because their names begin and end with two underscores." 490 | ] 491 | }, 492 | { 493 | "cell_type": "markdown", 494 | "id": "e01e9673", 495 | "metadata": {}, 496 | "source": [ 497 | "## The __init__ method\n", 498 | "\n", 499 | "The most special of the special methods is `__init__`, so-called because it initializes the attributes of a new object.\n", 500 | "An `__init__` method for the `Time` class might look like this:" 501 | ] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": 25, 506 | "id": "7ddcca8a", 507 | "metadata": {}, 508 | "outputs": [], 509 | "source": [] 510 | }, 511 | { 512 | "cell_type": "markdown", 513 | "id": "8ba624c3", 514 | "metadata": {}, 515 | "source": [ 516 | "Now when we instantiate a `Time` object, Python invokes `__init__`, and passes along the arguments.\n", 517 | "So we can create an object and initialize the attributes at the same time." 518 | ] 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": 26, 523 | "id": "afd652c6", 524 | "metadata": {}, 525 | "outputs": [], 526 | "source": [] 527 | }, 528 | { 529 | "cell_type": "markdown", 530 | "id": "55e0e296", 531 | "metadata": {}, 532 | "source": [ 533 | "In this example, the parameters are optional, so if you call `Time` with no arguments,\n", 534 | "you get the default values." 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": 27, 540 | "id": "8a852588", 541 | "metadata": {}, 542 | "outputs": [], 543 | "source": [] 544 | }, 545 | { 546 | "cell_type": "markdown", 547 | "id": "bacb036d", 548 | "metadata": {}, 549 | "source": [ 550 | "If you provide one argument, it overrides `hour`:" 551 | ] 552 | }, 553 | { 554 | "cell_type": "code", 555 | "execution_count": 28, 556 | "id": "0ff75ace", 557 | "metadata": {}, 558 | "outputs": [], 559 | "source": [] 560 | }, 561 | { 562 | "cell_type": "markdown", 563 | "id": "37edb221", 564 | "metadata": {}, 565 | "source": [ 566 | "If you provide two arguments, they override `hour` and `minute`." 567 | ] 568 | }, 569 | { 570 | "cell_type": "code", 571 | "execution_count": 29, 572 | "id": "b8e948bc", 573 | "metadata": {}, 574 | "outputs": [], 575 | "source": [] 576 | }, 577 | { 578 | "cell_type": "markdown", 579 | "id": "277de217", 580 | "metadata": {}, 581 | "source": [ 582 | "And if you provide three arguments, they override all three default\n", 583 | "values.\n", 584 | "\n", 585 | "When I write a new class, I almost always start by writing `__init__`, which makes it easier to create objects, and `__str__`, which is useful for debugging." 586 | ] 587 | }, 588 | { 589 | "cell_type": "markdown", 590 | "id": "94bbbd7d", 591 | "metadata": {}, 592 | "source": [ 593 | "## Operator overloading\n", 594 | "\n", 595 | "By defining other special methods, you can specify the behavior of\n", 596 | "operators on programmer-defined types. For example, if you define a\n", 597 | "method named `__add__` for the `Time` class, you can use the `+`\n", 598 | "operator on Time objects.\n", 599 | "\n", 600 | "Here is an `__add__` method." 601 | ] 602 | }, 603 | { 604 | "cell_type": "code", 605 | "execution_count": 30, 606 | "id": "0d140036", 607 | "metadata": {}, 608 | "outputs": [], 609 | "source": [] 610 | }, 611 | { 612 | "cell_type": "markdown", 613 | "id": "0221c9ad", 614 | "metadata": {}, 615 | "source": [ 616 | "We can use it like this." 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": 31, 622 | "id": "280acfce", 623 | "metadata": {}, 624 | "outputs": [], 625 | "source": [] 626 | }, 627 | { 628 | "cell_type": "markdown", 629 | "id": "7cc7866e", 630 | "metadata": {}, 631 | "source": [ 632 | "There is a lot happening when we run these three lines of code:\n", 633 | "\n", 634 | "* When we instantiate a `Time` object, the `__init__` method is invoked.\n", 635 | "\n", 636 | "* When we use the `+` operator with a `Time` object, its `__add__` method is invoked.\n", 637 | "\n", 638 | "* And when we print a `Time` object, its `__str__` method is invoked.\n", 639 | "\n", 640 | "Changing the behavior of an operator so that it works with programmer-defined types is called **operator overloading**.\n", 641 | "For every operator, like `+`, there is a corresponding special method, like `__add__`. " 642 | ] 643 | }, 644 | { 645 | "cell_type": "markdown", 646 | "id": "b7299e62", 647 | "metadata": {}, 648 | "source": [ 649 | "## Debugging\n", 650 | "\n", 651 | "A `Time` object is valid if the values of `minute` and `second` are between `0` and `60` -- including `0` but not `60` -- and if `hour` is positive.\n", 652 | "Also, `hour` and `minute` should be integer values, but we might allow `second` to have a fraction part.\n", 653 | "Requirements like these are called **invariants** because they should always be true.\n", 654 | "To put it a different way, if they are not true, something has gone wrong.\n", 655 | "\n", 656 | "Writing code to check invariants can help detect errors and find their causes.\n", 657 | "For example, you might have a method like `is_valid` that takes a Time object and returns `False` if it violates an invariant." 658 | ] 659 | }, 660 | { 661 | "cell_type": "code", 662 | "execution_count": 32, 663 | "id": "6eb34442", 664 | "metadata": {}, 665 | "outputs": [], 666 | "source": [] 667 | }, 668 | { 669 | "cell_type": "markdown", 670 | "id": "a10ad3db", 671 | "metadata": {}, 672 | "source": [ 673 | "Then, at the beginning of each method you can check the arguments to make sure they are valid." 674 | ] 675 | }, 676 | { 677 | "cell_type": "code", 678 | "execution_count": 33, 679 | "id": "57d86843", 680 | "metadata": {}, 681 | "outputs": [], 682 | "source": [] 683 | }, 684 | { 685 | "cell_type": "markdown", 686 | "id": "e7c78e9a", 687 | "metadata": {}, 688 | "source": [ 689 | "The `assert` statement evaluates the expression that follows. If the result is `True`, it does nothing; if the result is `False`, it causes an `AssertionError`.\n", 690 | "Here's an example." 691 | ] 692 | }, 693 | { 694 | "cell_type": "code", 695 | "execution_count": 34, 696 | "id": "5452888b", 697 | "metadata": {}, 698 | "outputs": [], 699 | "source": [] 700 | }, 701 | { 702 | "cell_type": "code", 703 | "execution_count": 35, 704 | "id": "56680d97", 705 | "metadata": { 706 | "tags": [] 707 | }, 708 | "outputs": [], 709 | "source": [] 710 | }, 711 | { 712 | "cell_type": "markdown", 713 | "id": "18bd34ad", 714 | "metadata": {}, 715 | "source": [ 716 | "`assert` statements are useful because they distinguish code that deals with normal conditions from code that checks for errors." 717 | ] 718 | }, 719 | { 720 | "cell_type": "markdown", 721 | "id": "58b86fbe", 722 | "metadata": {}, 723 | "source": [ 724 | "## Glossary\n", 725 | "\n", 726 | "**object-oriented language:**\n", 727 | "A language that provides features to support object-oriented programming, notably user-defined types.\n", 728 | "\n", 729 | "**method:**\n", 730 | "A function that is defined inside a class definition and is invoked on instances of that class.\n", 731 | "\n", 732 | "**receiver:**\n", 733 | "The object a method is invoked on.\n", 734 | "\n", 735 | "**static method:**\n", 736 | "A method that can be invoked without an object as receiver.\n", 737 | "\n", 738 | "**instance method:**\n", 739 | "A method that must be invoked with an object as receiver.\n", 740 | "\n", 741 | "**special method:**\n", 742 | "A method that changes the way operators and some functions work with an object.\n", 743 | "\n", 744 | "**operator overloading:**\n", 745 | "The process of using special methods to change the way operators with with user-defined types.\n", 746 | "\n", 747 | "**invariant:**\n", 748 | " A condition that should always be true during the execution of a program." 749 | ] 750 | }, 751 | { 752 | "cell_type": "markdown", 753 | "id": "796adf5c", 754 | "metadata": {}, 755 | "source": [ 756 | "## Exercises" 757 | ] 758 | }, 759 | { 760 | "cell_type": "code", 761 | "execution_count": null, 762 | "id": "3115ea33", 763 | "metadata": { 764 | "tags": [] 765 | }, 766 | "outputs": [], 767 | "source": [] 768 | }, 769 | { 770 | "cell_type": "markdown", 771 | "id": "25cd6888", 772 | "metadata": {}, 773 | "source": [ 774 | "### Ask a virtual assistant\n", 775 | "\n", 776 | "For more information about static methods, ask a virtual assistant:\n", 777 | "\n", 778 | "* \"What's the difference between an instance method and a static method?\"\n", 779 | "\n", 780 | "* \"Why are static methods called static?\"\n", 781 | "\n", 782 | "If you ask a virtual assistant to generate a static method, the result will probably begin with `@staticmethod`, which is a \"decorator\" that indicates that it is a static method.\n", 783 | "Decorators are not covered in this book, but if you are curious, you can ask a VA for more information.\n", 784 | "\n", 785 | "In this chapter we rewrote several functions as methods.\n", 786 | "Virtual assistants are generally good at this kind of code transformation.\n", 787 | "As an example, paste the following function into a VA and ask it, \"Rewrite this function as a method of the `Time` class.\"" 788 | ] 789 | }, 790 | { 791 | "cell_type": "code", 792 | "execution_count": 36, 793 | "id": "133d7679", 794 | "metadata": {}, 795 | "outputs": [], 796 | "source": [] 797 | }, 798 | { 799 | "cell_type": "markdown", 800 | "id": "fc9f135b-e242-4ef6-83eb-8e028235c07b", 801 | "metadata": {}, 802 | "source": [ 803 | "### Exercise\n", 804 | "\n", 805 | "In the previous chapter, a series of exercises asked you to write a `Date` class and several functions that work with `Date` objects.\n", 806 | "Now let's practice rewriting those functions as methods.\n", 807 | "\n", 808 | "1. Write a definition for a `Date` class that represents a date -- that is, a year, month, and day of the month.\n", 809 | "\n", 810 | "2. Write an `__init__` method that takes `year`, `month`, and `day` as parameters and assigns the parameters to attributes. Create an object that represents June 22, 1933.\n", 811 | "\n", 812 | "2. Write `__str__` method that uses an f-string to format the attributes and returns the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", 813 | "\n", 814 | "3. Write a method called `is_after` that takes two `Date` objects and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", 815 | "\n", 816 | "Hint: You might find it useful write a method called `to_tuple` that returns a tuple that contains the attributes of a `Date` object in year-month-day order." 817 | ] 818 | }, 819 | { 820 | "cell_type": "code", 821 | "execution_count": 37, 822 | "id": "3c9f3777-4869-481e-9f4e-4223d6028913", 823 | "metadata": {}, 824 | "outputs": [], 825 | "source": [] 826 | }, 827 | { 828 | "cell_type": "markdown", 829 | "id": "1122620d-f3f6-4746-8675-13ce0b7f3ee9", 830 | "metadata": { 831 | "tags": [] 832 | }, 833 | "source": [ 834 | "You can use these examples to test your solution." 835 | ] 836 | }, 837 | { 838 | "cell_type": "code", 839 | "execution_count": 38, 840 | "id": "fd4b2521-aa71-45da-97eb-ce62ce2714ad", 841 | "metadata": { 842 | "tags": [] 843 | }, 844 | "outputs": [], 845 | "source": [] 846 | }, 847 | { 848 | "cell_type": "code", 849 | "execution_count": 39, 850 | "id": "ee3f1294-cad1-406b-a574-045ad2b84294", 851 | "metadata": { 852 | "tags": [] 853 | }, 854 | "outputs": [], 855 | "source": [] 856 | }, 857 | { 858 | "cell_type": "code", 859 | "execution_count": 40, 860 | "id": "ac093f7b-83cf-4488-8842-5c71bcfa35ec", 861 | "metadata": { 862 | "tags": [] 863 | }, 864 | "outputs": [], 865 | "source": [] 866 | }, 867 | { 868 | "cell_type": "code", 869 | "execution_count": 41, 870 | "id": "7e7cb5e1-631f-4b1e-874f-eb16d4792625", 871 | "metadata": { 872 | "tags": [] 873 | }, 874 | "outputs": [], 875 | "source": [] 876 | }, 877 | { 878 | "cell_type": "code", 879 | "execution_count": null, 880 | "id": "5b92712d", 881 | "metadata": {}, 882 | "outputs": [], 883 | "source": [] 884 | }, 885 | { 886 | "cell_type": "markdown", 887 | "id": "a7f4edf8", 888 | "metadata": { 889 | "tags": [] 890 | }, 891 | "source": [ 892 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 893 | "\n", 894 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 895 | "\n", 896 | "Code license: [MIT License](https://mit-license.org/)\n", 897 | "\n", 898 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 899 | ] 900 | } 901 | ], 902 | "metadata": { 903 | "celltoolbar": "Tags", 904 | "kernelspec": { 905 | "display_name": "Python 3 (ipykernel)", 906 | "language": "python", 907 | "name": "python3" 908 | }, 909 | "language_info": { 910 | "codemirror_mode": { 911 | "name": "ipython", 912 | "version": 3 913 | }, 914 | "file_extension": ".py", 915 | "mimetype": "text/x-python", 916 | "name": "python", 917 | "nbconvert_exporter": "python", 918 | "pygments_lexer": "ipython3", 919 | "version": "3.10.11" 920 | } 921 | }, 922 | "nbformat": 4, 923 | "nbformat_minor": 5 924 | } 925 | -------------------------------------------------------------------------------- /blank/chap19.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "171aca73", 16 | "metadata": {}, 17 | "source": [ 18 | "# Final thoughts" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "id": "4d551c99", 24 | "metadata": {}, 25 | "source": [ 26 | "Learning to program is not easy, but if you made it this far, you are off to a good start.\n", 27 | "Now I have some suggestions for ways you can keep learning and apply what you have learned.\n", 28 | "\n", 29 | "This book is meant to be a general introduction to programming, so we have not focused on specific applications.\n", 30 | "Depending on your interests, there are any number of areas where you can apply your new skills.\n", 31 | "\n", 32 | "If you are interested in Data Science, there are three books of mine you might like:\n", 33 | "\n", 34 | "* *Think Stats: Exploratory Data Analysis*, O'Reilly Media, 2014.\n", 35 | "\n", 36 | "* *Think Bayes: Bayesian Statistics in Python*, O'Reilly Media, 2021.\n", 37 | "\n", 38 | "* *Think DSP: Digital Signal Processing in Python*, O'Reilly Media, 2016." 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "cceabe36", 44 | "metadata": {}, 45 | "source": [ 46 | "If you are interested in physical modeling and complex systems, you might like:\n", 47 | "\n", 48 | "* *Modeling and Simulation in Python: An Introduction for Scientists and Engineers*, No Starch Press, 2023.\n", 49 | "\n", 50 | "* *Think Complexity: Complexity Science and Computational Modeling*, O'Reilly Media, 2018.\n", 51 | "\n", 52 | "These use NumPy, SciPy, pandas, and other Python libraries for data science and scientific computing." 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "id": "54a39121", 58 | "metadata": {}, 59 | "source": [ 60 | "This book tries to find a balance between general principles of programming and details of Python.\n", 61 | "As a result, it does not include every feature of the Python language.\n", 62 | "For more about Python, and good advice about how to use it, I recommend *Fluent Python: Clear, Concise, and Effective Programming*, second edition by Luciano Ramalho, O'Reilly Media, 2022.\n", 63 | "\n", 64 | "After an introduction to programming, a common next step is to learn about data structures and algorithms.\n", 65 | "I have a work in progress on this topic, called *Data Structures and Information Retrieval in Python*.\n", 66 | "A free electronic version is available from Green Tea Press at ." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "a1598510", 72 | "metadata": {}, 73 | "source": [ 74 | "As you work on more complex programs, you will encounter new challenges.\n", 75 | "You might find it helpful to review the sections in this book about debugging.\n", 76 | "In particular, remember the Six R's of debugging from [Chapter 12](section_debugging_12): reading, running, ruminating, rubber-ducking, retreating, and resting.\n", 77 | "\n", 78 | "This book suggests tools to help with debugging, including the `print` and `repr` functions, the `structshape` function in [Chapter 11](section_debugging_11) -- and the built-in functions `isinstance`, `hasattr`, and `vars` in [Chapter 14](section_debugging_14)." 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "fb4dd345", 84 | "metadata": {}, 85 | "source": [ 86 | "It also suggests tools for testing programs, including the `assert` statement, the `doctest` module, and the `unittest` module.\n", 87 | "Including tests in your programs is one of the best ways to prevent and detect errors, and save time debugging.\n", 88 | "\n", 89 | "But the best kind of debugging is the kind you don't have to do.\n", 90 | "If you use an incremental development process as described in [Chapter 6](section_incremental) -- and test as you go -- you will make fewer errors and find them more quickly when you do.\n", 91 | "Also, remember encapsulation and generalization from [Chapter 4](section_encapsulation), which is particularly useful when you are developing code in Jupyter notebooks." 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "id": "0d29933e", 97 | "metadata": {}, 98 | "source": [ 99 | "Throughout this book, I've suggested ways to use virtual assistants to help you learn, program, and debug.\n", 100 | "I hope you are finding these tools useful.\n", 101 | "\n", 102 | "In additional to virtual assistants like ChatGPT, you might also want to use a tool like Copilot that autocompletes code as you type.\n", 103 | "I did not recommend using these tools, initially, because they can be overwhelming for beginners.\n", 104 | "But you might want to explore them now.\n", 105 | "\n", 106 | "Using AI tools effectively requires some experimentation and reflection to find a flow that works for you.\n", 107 | "If you think it's a nuisance to copy code from ChatGPT to Jupyter, you might prefer something like Copilot.\n", 108 | "But the cognitive work you do to compose a prompt and interpret the response can be as valuable as the code the tool generates, in the same vein as rubber duck debugging." 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "c28d6815", 114 | "metadata": {}, 115 | "source": [ 116 | "As you gain programming experience, you might want to explore other development environments.\n", 117 | "I think Jupyter notebooks are a good place to start, but they are relatively new and not as widely-used as conventional integrated development environments (IDE).\n", 118 | "For Python, the most popular IDEs include PyCharm and Spyder -- and Thonny, which is often recommended for beginners.\n", 119 | "Other IDEs, like Visual Studio Code and Eclipse, work with other programming languages as well.\n", 120 | "Or, as a simpler alternative, you can write Python programs using any text editor you like.\n", 121 | "\n", 122 | "As you continue your programming journey, you don't have to go alone!\n", 123 | "If you live in or near a city, there's a good chance there is a Python user group you can join.\n", 124 | "These groups are usually friendly to beginners, so don't be afraid.\n", 125 | "If there is no group near you, you might be able to join events remotely.\n", 126 | "Also, keep an eye out for regional Python conferences." 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "28cb22bf", 132 | "metadata": {}, 133 | "source": [ 134 | "One of the best ways to improve your programming skills is to learn another language.\n", 135 | "If you are interested in statistics and data science, you might want to learn R.\n", 136 | "But I particularly recommend learning a functional language like Racket or Elixir.\n", 137 | "Functional programming requires a different kind of thinking, which changes the way you think about programs.\n", 138 | "\n", 139 | "Good luck!" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "id": "e2783577", 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "id": "a7f4edf8", 153 | "metadata": { 154 | "tags": [] 155 | }, 156 | "source": [ 157 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 158 | "\n", 159 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 160 | "\n", 161 | "Code license: [MIT License](https://mit-license.org/)\n", 162 | "\n", 163 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 164 | ] 165 | } 166 | ], 167 | "metadata": { 168 | "kernelspec": { 169 | "display_name": "Python 3 (ipykernel)", 170 | "language": "python", 171 | "name": "python3" 172 | }, 173 | "language_info": { 174 | "codemirror_mode": { 175 | "name": "ipython", 176 | "version": 3 177 | }, 178 | "file_extension": ".py", 179 | "mimetype": "text/x-python", 180 | "name": "python", 181 | "nbconvert_exporter": "python", 182 | "pygments_lexer": "ipython3", 183 | "version": "3.10.14" 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 5 188 | } 189 | -------------------------------------------------------------------------------- /chapters/build.sh: -------------------------------------------------------------------------------- 1 | # Build the notebook folder and zip file 2 | 3 | # copy notebooks from soln 4 | 5 | cp ../ThinkPythonSolutions/soln/chap[01][0-9]*.ipynb . 6 | cp ../ThinkPythonSolutions/soln/jupyter_intro.ipynb . 7 | cp ../ThinkPythonSolutions/soln/thinkpython.py . 8 | cp ../ThinkPythonSolutions/soln/diagram.py . 9 | cp ../ThinkPythonSolutions/soln/structshape.py . 10 | cp ../ThinkPythonSolutions/soln/words.txt . 11 | cp ../ThinkPythonSolutions/soln/photos.zip . 12 | 13 | # remove the solutions 14 | python prep_notebooks.py 15 | 16 | # pip install pytest nbmake 17 | 18 | # run nbmake 19 | # TODO: can we get the %%expect magic to work? 20 | # pytest --nbmake chap*.ipynb 21 | 22 | # commit the changes 23 | git add jupyter_intro.ipynb 24 | git add chap*.ipynb 25 | git commit -m "Updating the notebooks" 26 | 27 | # build the zip file 28 | cd ../..; zip -r ThinkPythonNotebooks.zip \ 29 | ThinkPython/chapters/jupyter_intro.ipynb \ 30 | ThinkPython/chapters/chap*.ipynb \ 31 | ThinkPython/thinkpython.py \ 32 | ThinkPython/diagram.py \ 33 | ThinkPython/structshape.py \ 34 | ThinkPython/words.txt \ 35 | ThinkPython/photos.zip 36 | 37 | # add and commit it 38 | mv ThinkPythonNotebooks.zip ThinkPython 39 | cd ThinkPython 40 | 41 | git add ThinkPythonNotebooks.zip 42 | git commit -m "Updating the zip file" 43 | 44 | git push 45 | -------------------------------------------------------------------------------- /chapters/chap00.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "d9724920", 16 | "metadata": {}, 17 | "source": [ 18 | "# Preface\n", 19 | "\n" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "id": "b76f38c6", 25 | "metadata": {}, 26 | "source": [ 27 | "## Who Is This Book For?\n", 28 | "\n", 29 | "If you want to learn to program, you have come to the right place.\n", 30 | "Python is one of the best programming languages for beginners -- and it is also one of the most in-demand skills.\n", 31 | "\n", 32 | "You have also come at the right time, because learning to program now is probably easier than ever.\n", 33 | "With virtual assistants like ChatGPT, you don't have to learn alone.\n", 34 | "Throughout this book, I'll suggest ways you can use these tools to accelerate your learning.\n", 35 | "\n", 36 | "This book is primarily for people who have never programmed before and people who have some experience in another programming language.\n", 37 | "If you have substantial experience in Python, you might find the first few chapters too slow.\n", 38 | "\n", 39 | "One of the challenges of learning to program is that you have to learn *two* languages: one is the programming language itself; the other is the vocabulary we use to talk about programs.\n", 40 | "If you learn only the programming language, you are likely to have problems when you need to interpret an error message, read documentation, talk to another person, or use virtual assistants.\n", 41 | "If you have done some programming, but you have not also learned this second language, I hope you find this book helpful." 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "b4dd57bc", 47 | "metadata": {}, 48 | "source": [ 49 | "## Goals of the Book\n", 50 | "\n", 51 | "Writing this book, I tried to be careful with the vocabulary.\n", 52 | "I define each term when it first appears.\n", 53 | "And there is a glossary that the end of each chapter that reviews the terms that were introduced.\n", 54 | "\n", 55 | "I also tried to be concise.\n", 56 | "The less mental effort it takes to read the book, the more capacity you will have for programming.\n", 57 | "\n", 58 | "But you can't learn to program just by reading a book -- you have to practice.\n", 59 | "For that reason, this book includes exercises at the end of every chapter where you can practice what you have learned.\n", 60 | "\n", 61 | "If you read carefully and work on exercises consistently, you will make progress.\n", 62 | "But I'll warn you now -- learning to program is not easy, and even for experienced programmers it can be frustrating.\n", 63 | "As we go, I will suggest strategies to help you write correct programs and fix incorrect ones." 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "6516d914", 69 | "metadata": {}, 70 | "source": [ 71 | "## Navigating the Book\n", 72 | "\n", 73 | "Each chapter in this book builds on the previous ones, so you should read them in order and take time to work on the exercises before you move on.\n", 74 | "\n", 75 | "The first six chapters introduce basic elements like arithmetic, conditionals, and loops.\n", 76 | "They also introduce the most important concept in programming, functions, and a powerful way to use them, recursion.\n", 77 | "\n", 78 | "Chapters 7 and 8 introduce strings -- which can represent letter, words, and sentences -- and algorithms for working with them.\n", 79 | "\n", 80 | "Chapters 9 through 12 introduce Python's core data structures -- lists, dictionaries, and tuples -- which are powerful tools for writing efficient programs.\n", 81 | "Chapter 12 presents algorithms for analyzing text and randomly generating new text.\n", 82 | "Algorithms like these are at the core of large language models (LLMs), so this chapter will give you an idea of how tools like ChatGPT work.\n", 83 | "\n", 84 | "Chapter 13 is about ways to store data in long-term storage -- files and databases.\n", 85 | "As an exercise, you can write a program that searches a file system and finds duplicate files.\n", 86 | "\n", 87 | "Chapters 14 through 17 introduce object-oriented programming (OOP), which is a way to organize programs and the data they work with.\n", 88 | "Many Python libraries are written in object-oriented style, so these chapters will help you understand their design -- and define your own objects.\n", 89 | "\n", 90 | "The goal of this book is not to cover the entire Python language.\n", 91 | "Rather, I focus on a subset of the language that provides the greatest capability with the fewest concepts.\n", 92 | "Nevertheless, Python has a lot of features you can use to solve common problems efficiently.\n", 93 | "Chapter 18 presents some of these features.\n", 94 | "\n", 95 | "Finally, Chapter 19 presents my parting thoughts and suggestions for continuing your programming journey." 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "id": "23013838", 101 | "metadata": {}, 102 | "source": [ 103 | "## What's new in the third edition?\n", 104 | "\n", 105 | "The biggest changes in this edition were driven by two new technologies -- Jupyter notebooks and virtual assistants.\n", 106 | "\n", 107 | "Each chapter of this book is a Jupyter notebook, which is a document that contains both ordinary text and code.\n", 108 | "For me, that makes it easier to write the code, test it, and keep it consistent with the text.\n", 109 | "For you, it means you can run the code, modify it, and work on the exercises, all in one place.\n", 110 | "Instructions for working with the notebooks are in the first chapter.\n", 111 | "\n", 112 | "The other big change is that I've added advice for working with virtual assistants like ChatGPT and using them to accelerate your learning.\n", 113 | "When the previous edition of this book was published in 2016, the predecessors of these tools were far less useful and most people were unaware of them. \n", 114 | "Now they are a standard tool for software engineering, and I think they will be a transformational tool for learning to program -- and learning a lot of other things, too.\n", 115 | "\n", 116 | "The other changes in the book were motivated by my regrets about the second edition.\n", 117 | "\n", 118 | "The first is that I did not emphasize software testing.\n", 119 | "That was already a regrettable omission in 2016, but with the advent of virtual assistants, automated testing has become even more important.\n", 120 | "So this edition presents Python's most widely-used testing tools, `doctest` and `unittest`, and includes several exercises where you can practice working with them.\n", 121 | "\n", 122 | "My other regret is that the exercises in the second edition were uneven -- some were more interesting than others and some were too hard.\n", 123 | "Moving to Jupyter notebooks helped me develop and test a more engaging and effective sequence of exercises.\n", 124 | "\n", 125 | "In this revision, the sequence of topics is almost the same, but I rearranged a few of the chapters and compressed two short chapters into one.\n", 126 | "Also, I expanded the coverage of strings to include regular expressions.\n", 127 | "\n", 128 | "A few chapters use turtle graphics.\n", 129 | "In previous editions, I used Python's `turtle` module, but unfortunately it doesn't work in Jupyter notebooks.\n", 130 | "So I replaced it with a new turtle module that should be easier to use.\n", 131 | "\n", 132 | "Finally, I rewrote a substantial fraction of the text, clarifying places that needed it and cutting back in places where I was not as concise as I could be.\n", 133 | "\n", 134 | "I am very proud of this new edition -- I hope you like it!" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "bfb779bb", 140 | "metadata": {}, 141 | "source": [ 142 | "## Getting started\n", 143 | "\n", 144 | "For most programming languages, including Python, there are many tools you can use to write and run programs. \n", 145 | "These tools are called integrated development environments (IDEs).\n", 146 | "In general, there are two kinds of IDEs:\n", 147 | "\n", 148 | "* Some work with files that contain code, so they provide tools for editing and running these files.\n", 149 | "\n", 150 | "* Others work primarily with notebooks, which are documents that contain text and code.\n", 151 | "\n", 152 | "For beginners, I recommend starting with a notebook development environment like Jupyter.\n", 153 | "\n", 154 | "The notebooks for this book are available from an online repository at .\n", 155 | "\n", 156 | "There are two ways to use them:\n", 157 | "\n", 158 | "* You can download the notebooks and run them on your own computer. In that case, you have to install Python and Jupyter, which is not hard, but if you want to learn Python, it can be frustrating to spend a lot of time installing software.\n", 159 | "\n", 160 | "* An alternative is to run the notebooks on Colab, which is a Jupyter environment that runs in a web browser, so you don't have to install anything. Colab is operated by Google, and it is free to use.\n", 161 | "\n", 162 | "If you are just getting started, I strongly recommend you start with Colab." 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "id": "2ebd2412", 168 | "metadata": {}, 169 | "source": [ 170 | "## Resources for Teachers\n", 171 | "\n", 172 | "If you are teaching with this book, here are some resources you might find useful.\n", 173 | "\n", 174 | "* You can find notebooks with solutions to the exercises at , along with links to the additional resources below.\n", 175 | "\n", 176 | "* Quizzes for each chapter, and a summative quiz for the whole book, are available on request.\n", 177 | "\n", 178 | "* *Teaching and Learning with Jupyter* is an online book with suggestions for using Jupyter effectively in the classroom. You can read the book at \n", 179 | "\n", 180 | "* One of the best ways to use notebooks is live coding, where an instructor writes code and students follow along in their own notebooks. To learn about live coding -- and get other great advice about teaching programming -- I recommend the instructor training provided by The Carpentries, at " 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "28e7de55", 186 | "metadata": {}, 187 | "source": [ 188 | "## Acknowledgments\n", 189 | "\n", 190 | "Many thanks to Jeff Elkner, who translated my Java book into Python,\n", 191 | "which got this project started and introduced me to what has turned out\n", 192 | "to be my favorite language.\n", 193 | "Thanks also to Chris Meyers, who contributed several sections to *How to Think Like a Computer Scientist*.\n", 194 | "\n", 195 | "Thanks to the Free Software Foundation for developing the GNU Free Documentation License, which helped make my collaboration with Jeff and Chris possible, and thanks to the Creative Commons for the license I am using now.\n", 196 | "\n", 197 | "Thanks to the developers and maintainers of the Python language and the libraries I used, including the Turtle graphics module; the tools I used to develop the book, including Jupyter and JupyterBook; and the services I used, including ChatGPT, Copilot, Colab and GitHub.\n", 198 | "\n", 199 | "Thanks to the editors at Lulu who worked on *How to Think Like a Computer Scientist* and the editors at O'Reilly Media who worked on *Think Python*.\n", 200 | "\n", 201 | "Special thanks to the technical reviewers for the second edition, Melissa Lewis and Luciano Ramalho, and for the third edition, Sam Lau and Luciano Ramalho (again!).\n", 202 | "I am also grateful to Luciano for developing the turtle graphics module I use in several chapters, called `jupyturtle`.\n", 203 | "\n", 204 | "Thanks to all the students who worked with earlier versions of this book and all the contributors who sent in corrections and suggestions.\n", 205 | "More than 100 sharp-eyed and thoughtful readers have sent in suggestions and corrections over the past few years. Their contributions, and enthusiasm for this project, have been a huge help.\n", 206 | "\n", 207 | "If you have a suggestion or correction, please send email to `feedback@thinkpython.com`.\n", 208 | "If you include at least part of the sentence the error appears in, that\n", 209 | "makes it easy for me to search. Page and section numbers are fine, too,\n", 210 | "but not quite as easy to work with. Thanks!" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": null, 216 | "id": "4e31cebe", 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "id": "a7f4edf8", 224 | "metadata": { 225 | "tags": [] 226 | }, 227 | "source": [ 228 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 229 | "\n", 230 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 231 | "\n", 232 | "Code license: [MIT License](https://mit-license.org/)\n", 233 | "\n", 234 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 235 | ] 236 | } 237 | ], 238 | "metadata": { 239 | "kernelspec": { 240 | "display_name": "Python 3 (ipykernel)", 241 | "language": "python", 242 | "name": "python3" 243 | }, 244 | "language_info": { 245 | "codemirror_mode": { 246 | "name": "ipython", 247 | "version": 3 248 | }, 249 | "file_extension": ".py", 250 | "mimetype": "text/x-python", 251 | "name": "python", 252 | "nbconvert_exporter": "python", 253 | "pygments_lexer": "ipython3", 254 | "version": "3.10.11" 255 | } 256 | }, 257 | "nbformat": 4, 258 | "nbformat_minor": 5 259 | } 260 | -------------------------------------------------------------------------------- /chapters/chap15.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "id": "3161b50b", 17 | "metadata": { 18 | "tags": [] 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "from os.path import basename, exists\n", 23 | "\n", 24 | "def download(url):\n", 25 | " filename = basename(url)\n", 26 | " if not exists(filename):\n", 27 | " from urllib.request import urlretrieve\n", 28 | "\n", 29 | " local, _ = urlretrieve(url, filename)\n", 30 | " print(\"Downloaded \" + str(local))\n", 31 | " return filename\n", 32 | "\n", 33 | "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", 34 | "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", 35 | "\n", 36 | "import thinkpython" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "id": "fa22117f", 42 | "metadata": {}, 43 | "source": [ 44 | "# Classes and Methods\n", 45 | "\n", 46 | "Python is an **object-oriented language** -- that is, it provides features that support object-oriented programming, which has these defining characteristics:\n", 47 | "\n", 48 | "- Most of the computation is expressed in terms of operations on objects.\n", 49 | "\n", 50 | "- Objects often represent things in the real world, and methods often correspond to the ways things in the real world interact.\n", 51 | "\n", 52 | "- Programs include class and method definitions.\n", 53 | "\n", 54 | "For example, in the previous chapter we defined a `Time` class that corresponds to the way people record the time of day, and we defined functions that correspond to the kinds of things people do with times.\n", 55 | "But there was no explicit connection between the definition of the `Time` class and the function definitions that follow.\n", 56 | "We can make the connection explicit by rewriting a function as a **method**, which is defined inside a class definition." 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "id": "9857823a", 62 | "metadata": {}, 63 | "source": [ 64 | "## Defining methods\n", 65 | "\n", 66 | "In the previous chapter we defined a class named `Time` and wrote a function named `print_time` that displays a time of day." 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "id": "ee093ca4", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "class Time:\n", 77 | " \"\"\"Represents the time of day.\"\"\"\n", 78 | "\n", 79 | "def print_time(time):\n", 80 | " s = f'{time.hour:02d}:{time.minute:02d}:{time.second:02d}'\n", 81 | " print(s)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "id": "a89ddf58", 87 | "metadata": {}, 88 | "source": [ 89 | "To make `print_time` a method, all we have to do is move the function\n", 90 | "definition inside the class definition. Notice the change in\n", 91 | "indentation.\n", 92 | "\n", 93 | "At the same time, we'll change the name of the parameter from `time` to `self`.\n", 94 | "This change is not necessary, but it is conventional for the first parameter of a method to be named `self`." 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 3, 100 | "id": "fd26a1bc", 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "class Time:\n", 105 | " \"\"\"Represents the time of day.\"\"\" \n", 106 | "\n", 107 | " def print_time(self):\n", 108 | " s = f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'\n", 109 | " print(s)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "id": "8da4079c", 115 | "metadata": {}, 116 | "source": [ 117 | "To call this method, you have to pass a `Time` object as an argument.\n", 118 | "Here's the function we'll use to make a `Time` object." 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 4, 124 | "id": "5fc157ea", 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "def make_time(hour, minute, second):\n", 129 | " time = Time()\n", 130 | " time.hour = hour\n", 131 | " time.minute = minute\n", 132 | " time.second = second\n", 133 | " return time" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "c6ad4e12", 139 | "metadata": {}, 140 | "source": [ 141 | "And here's a `Time` instance." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 5, 147 | "id": "35acd8e6", 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "start = make_time(9, 40, 0)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "bbbcd333", 157 | "metadata": {}, 158 | "source": [ 159 | "Now there are two ways to call `print_time`. The first (and less common)\n", 160 | "way is to use function syntax." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 6, 166 | "id": "f755081c", 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "Time.print_time(start)" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "2eb0847e", 176 | "metadata": {}, 177 | "source": [ 178 | "In this version, `Time` is the name of the class, `print_time` is the name of the method, and `start` is passed as a parameter.\n", 179 | "The second (and more idiomatic) way is to use method syntax:" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 7, 185 | "id": "d6f91aec", 186 | "metadata": {}, 187 | "outputs": [], 188 | "source": [ 189 | "start.print_time()" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "id": "c80c40f0", 195 | "metadata": {}, 196 | "source": [ 197 | "In this version, `start` is the object the method is invoked on, which is called the **receiver**, based on the analogy that invoking a method is like sending a message to an object.\n", 198 | "\n", 199 | "Regardless of the syntax, the behavior of the method is the same.\n", 200 | "The receiver is assigned to the first parameter, so inside the method, `self` refers to the same object as `start`." 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "id": "8deb6c34", 206 | "metadata": {}, 207 | "source": [ 208 | "## Another method\n", 209 | "\n", 210 | "Here's the `time_to_int` function from the previous chapter." 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 8, 216 | "id": "24c4c985", 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "def time_to_int(time):\n", 221 | " minutes = time.hour * 60 + time.minute\n", 222 | " seconds = minutes * 60 + time.second\n", 223 | " return seconds" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "id": "144e043f", 229 | "metadata": {}, 230 | "source": [ 231 | "And here's a version rewritten as a method.\n" 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": 9, 237 | "id": "dde6f15f", 238 | "metadata": {}, 239 | "outputs": [], 240 | "source": [ 241 | "%%add_method_to Time\n", 242 | "\n", 243 | " def time_to_int(self):\n", 244 | " minutes = self.hour * 60 + self.minute\n", 245 | " seconds = minutes * 60 + self.second\n", 246 | " return seconds" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "id": "e3a721ab", 252 | "metadata": {}, 253 | "source": [ 254 | "The first line uses the special command `add_method_to`, which adds a method to a previously-defined class.\n", 255 | "This command works in a Jupyter notebook, but it is not part of Python, so it won't work in other environments.\n", 256 | "Normally, all methods of a class are inside the class definition, so they get defined at the same time as the class.\n", 257 | "But for this book, it is helpful to define one method at a time.\n", 258 | "\n", 259 | "As in the previous example, the method definition is indented and the name of the parameter is `self`.\n", 260 | "Other than that, the method is identical to the function.\n", 261 | "Here's how we invoke it." 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 10, 267 | "id": "8943fa0a", 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "start.time_to_int()" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "id": "14565505", 277 | "metadata": {}, 278 | "source": [ 279 | "It is common to say that we \"call\" a function and \"invoke\" a method, but they mean the same thing." 280 | ] 281 | }, 282 | { 283 | "cell_type": "markdown", 284 | "id": "7bc24683", 285 | "metadata": {}, 286 | "source": [ 287 | "## Static methods\n", 288 | "\n", 289 | "As another example, let's consider the `int_to_time` function.\n", 290 | "Here's the version from the previous chapter." 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 11, 296 | "id": "8547b1c2", 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "def int_to_time(seconds):\n", 301 | " minute, second = divmod(seconds, 60)\n", 302 | " hour, minute = divmod(minute, 60)\n", 303 | " return make_time(hour, minute, second)" 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "id": "2b77c2a0", 309 | "metadata": {}, 310 | "source": [ 311 | "This function takes `seconds` as a parameter and returns a new `Time` object.\n", 312 | "If we transform it into a method of the `Time` class, we have to invoke it on a `Time` object.\n", 313 | "But if we're trying to create a new `Time` object, what are we supposed to invoke it on?\n", 314 | "\n", 315 | "We can solve this chicken-and-egg problem using a **static method**, which is a method that does not require an instance of the class to be invoked.\n", 316 | "Here's how we rewrite this function as a static method." 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 12, 322 | "id": "b233669c", 323 | "metadata": {}, 324 | "outputs": [], 325 | "source": [ 326 | "%%add_method_to Time\n", 327 | "\n", 328 | " def int_to_time(seconds):\n", 329 | " minute, second = divmod(seconds, 60)\n", 330 | " hour, minute = divmod(minute, 60)\n", 331 | " return make_time(hour, minute, second)" 332 | ] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "id": "a7e2e788", 337 | "metadata": {}, 338 | "source": [ 339 | "Because it is a static method, it does not have `self` as a parameter.\n", 340 | "To invoke it, we use `Time`, which is the class object." 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 13, 346 | "id": "7e88f06b", 347 | "metadata": {}, 348 | "outputs": [], 349 | "source": [ 350 | "start = Time.int_to_time(34800)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "id": "d2f4fd5a", 356 | "metadata": {}, 357 | "source": [ 358 | "The result is a new object that represents 9:40." 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 14, 364 | "id": "8c9f66b0", 365 | "metadata": {}, 366 | "outputs": [], 367 | "source": [ 368 | "start.print_time()" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "id": "e6a18c76", 374 | "metadata": {}, 375 | "source": [ 376 | "Now that we have `Time.from_seconds`, we can use it to write `add_time` as a method.\n", 377 | "Here's the function from the previous chapter." 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": 15, 383 | "id": "c600d536", 384 | "metadata": {}, 385 | "outputs": [], 386 | "source": [ 387 | "def add_time(time, hours, minutes, seconds):\n", 388 | " duration = make_time(hours, minutes, seconds)\n", 389 | " seconds = time_to_int(time) + time_to_int(duration)\n", 390 | " return int_to_time(seconds)" 391 | ] 392 | }, 393 | { 394 | "cell_type": "markdown", 395 | "id": "8e56da48", 396 | "metadata": {}, 397 | "source": [ 398 | "And here's a version rewritten as a method." 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 16, 404 | "id": "c6fa0176", 405 | "metadata": {}, 406 | "outputs": [], 407 | "source": [ 408 | "%%add_method_to Time\n", 409 | "\n", 410 | " def add_time(self, hours, minutes, seconds):\n", 411 | " duration = make_time(hours, minutes, seconds)\n", 412 | " seconds = time_to_int(self) + time_to_int(duration)\n", 413 | " return Time.int_to_time(seconds)" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "id": "b784a4ea", 419 | "metadata": {}, 420 | "source": [ 421 | "`add_time` has `self` as a parameter because it is not a static method.\n", 422 | "It is an ordinary method -- also called an **instance method**.\n", 423 | "To invoke it, we need a `Time` instance." 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 17, 429 | "id": "e17b2ad7", 430 | "metadata": {}, 431 | "outputs": [], 432 | "source": [ 433 | "end = start.add_time(1, 32, 0)\n", 434 | "print_time(end)" 435 | ] 436 | }, 437 | { 438 | "cell_type": "markdown", 439 | "id": "f1c806a9", 440 | "metadata": {}, 441 | "source": [ 442 | "## Comparing Time objects\n", 443 | "\n", 444 | "As one more example, let's write `is_after` as a method.\n", 445 | "Here's the `is_after` function, which is a solution to an exercise in the previous chapter." 446 | ] 447 | }, 448 | { 449 | "cell_type": "code", 450 | "execution_count": 18, 451 | "id": "971eebbb", 452 | "metadata": {}, 453 | "outputs": [], 454 | "source": [ 455 | "def is_after(t1, t2):\n", 456 | " return time_to_int(t1) > time_to_int(t2)" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "id": "8e7153e8", 462 | "metadata": {}, 463 | "source": [ 464 | "And here it is as a method." 465 | ] 466 | }, 467 | { 468 | "cell_type": "code", 469 | "execution_count": 19, 470 | "id": "90d7234d", 471 | "metadata": {}, 472 | "outputs": [], 473 | "source": [ 474 | "%%add_method_to Time\n", 475 | "\n", 476 | " def is_after(self, other):\n", 477 | " return self.time_to_int() > other.time_to_int()" 478 | ] 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "id": "50815aec", 483 | "metadata": {}, 484 | "source": [ 485 | "Because we're comparing two objects, and the first parameter is `self`, we'll call the second parameter `other`.\n", 486 | "To use this method, we have to invoke it on one object and pass the\n", 487 | "other as an argument." 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 20, 493 | "id": "19e3d639", 494 | "metadata": {}, 495 | "outputs": [], 496 | "source": [ 497 | "end.is_after(start)" 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "id": "cf97e358", 503 | "metadata": {}, 504 | "source": [ 505 | "One nice thing about this syntax is that it almost reads like a question,\n", 506 | "\"`end` is after `start`?\"" 507 | ] 508 | }, 509 | { 510 | "cell_type": "markdown", 511 | "id": "15a17fce", 512 | "metadata": {}, 513 | "source": [ 514 | "## The `__str__` method\n", 515 | "\n", 516 | "When you write a method, you can choose almost any name you want.\n", 517 | "However, some names have special meanings.\n", 518 | "For example, if an object has a method named `__str__`, Python uses that method to convert the object to a string.\n", 519 | "For example, here is a `__str__` method for a time object." 520 | ] 521 | }, 522 | { 523 | "cell_type": "code", 524 | "execution_count": 21, 525 | "id": "f935a999", 526 | "metadata": {}, 527 | "outputs": [], 528 | "source": [ 529 | "%%add_method_to Time\n", 530 | "\n", 531 | " def __str__(self):\n", 532 | " s = f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'\n", 533 | " return s" 534 | ] 535 | }, 536 | { 537 | "cell_type": "markdown", 538 | "id": "b056b729", 539 | "metadata": {}, 540 | "source": [ 541 | "This method is similar to `print_time`, from the previous chapter, except that it returns the string rather than printing it.\n", 542 | "\n", 543 | "You can invoke this method in the usual way." 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": 22, 549 | "id": "61d7275d", 550 | "metadata": {}, 551 | "outputs": [], 552 | "source": [ 553 | "end.__str__()" 554 | ] 555 | }, 556 | { 557 | "cell_type": "markdown", 558 | "id": "76092a0c", 559 | "metadata": {}, 560 | "source": [ 561 | "But Python can also invoke it for you.\n", 562 | "If you use the built-in function `str` to convert a `Time` object to a string, Python uses the `__str__` method in the `Time` class." 563 | ] 564 | }, 565 | { 566 | "cell_type": "code", 567 | "execution_count": 23, 568 | "id": "b6dcc0c2", 569 | "metadata": {}, 570 | "outputs": [], 571 | "source": [ 572 | "str(end)" 573 | ] 574 | }, 575 | { 576 | "cell_type": "markdown", 577 | "id": "8a26caa8", 578 | "metadata": {}, 579 | "source": [ 580 | "And it does the same if you print a `Time` object." 581 | ] 582 | }, 583 | { 584 | "cell_type": "code", 585 | "execution_count": 24, 586 | "id": "6e1e6fb3", 587 | "metadata": {}, 588 | "outputs": [], 589 | "source": [ 590 | "print(end)" 591 | ] 592 | }, 593 | { 594 | "cell_type": "markdown", 595 | "id": "97eb30c2", 596 | "metadata": {}, 597 | "source": [ 598 | "Methods like `__str__` are called **special methods**.\n", 599 | "You can identify them because their names begin and end with two underscores." 600 | ] 601 | }, 602 | { 603 | "cell_type": "markdown", 604 | "id": "e01e9673", 605 | "metadata": {}, 606 | "source": [ 607 | "## The init method\n", 608 | "\n", 609 | "The most special of the special methods is `__init__`, so-called because it initializes the attributes of a new object.\n", 610 | "An `__init__` method for the `Time` class might look like this:" 611 | ] 612 | }, 613 | { 614 | "cell_type": "code", 615 | "execution_count": 25, 616 | "id": "7ddcca8a", 617 | "metadata": {}, 618 | "outputs": [], 619 | "source": [ 620 | "%%add_method_to Time\n", 621 | "\n", 622 | " def __init__(self, hour=0, minute=0, second=0):\n", 623 | " self.hour = hour\n", 624 | " self.minute = minute\n", 625 | " self.second = second" 626 | ] 627 | }, 628 | { 629 | "cell_type": "markdown", 630 | "id": "8ba624c3", 631 | "metadata": {}, 632 | "source": [ 633 | "Now when we instantiate a `Time` object, Python invokes `__init__`, and passes along the arguments.\n", 634 | "So we can create an object and initialize the attributes at the same time." 635 | ] 636 | }, 637 | { 638 | "cell_type": "code", 639 | "execution_count": 26, 640 | "id": "afd652c6", 641 | "metadata": {}, 642 | "outputs": [], 643 | "source": [ 644 | "time = Time(9, 40, 0)\n", 645 | "print(time)" 646 | ] 647 | }, 648 | { 649 | "cell_type": "markdown", 650 | "id": "55e0e296", 651 | "metadata": {}, 652 | "source": [ 653 | "In this example, the parameters are optional, so if you call `Time` with no arguments,\n", 654 | "you get the default values." 655 | ] 656 | }, 657 | { 658 | "cell_type": "code", 659 | "execution_count": 27, 660 | "id": "8a852588", 661 | "metadata": {}, 662 | "outputs": [], 663 | "source": [ 664 | "time = Time()\n", 665 | "print(time)" 666 | ] 667 | }, 668 | { 669 | "cell_type": "markdown", 670 | "id": "bacb036d", 671 | "metadata": {}, 672 | "source": [ 673 | "If you provide one argument, it overrides `hour`:" 674 | ] 675 | }, 676 | { 677 | "cell_type": "code", 678 | "execution_count": 28, 679 | "id": "0ff75ace", 680 | "metadata": {}, 681 | "outputs": [], 682 | "source": [ 683 | "time = Time(9)\n", 684 | "print(time)" 685 | ] 686 | }, 687 | { 688 | "cell_type": "markdown", 689 | "id": "37edb221", 690 | "metadata": {}, 691 | "source": [ 692 | "If you provide two arguments, they override `hour` and `minute`." 693 | ] 694 | }, 695 | { 696 | "cell_type": "code", 697 | "execution_count": 29, 698 | "id": "b8e948bc", 699 | "metadata": {}, 700 | "outputs": [], 701 | "source": [ 702 | "time = Time(9, 45)\n", 703 | "print(time)" 704 | ] 705 | }, 706 | { 707 | "cell_type": "markdown", 708 | "id": "277de217", 709 | "metadata": {}, 710 | "source": [ 711 | "And if you provide three arguments, they override all three default\n", 712 | "values.\n", 713 | "\n", 714 | "When I write a new class, I almost always start by writing `__init__`, which makes it easier to create objects, and `__str__`, which is useful for debugging." 715 | ] 716 | }, 717 | { 718 | "cell_type": "markdown", 719 | "id": "94bbbd7d", 720 | "metadata": {}, 721 | "source": [ 722 | "## Operator overloading\n", 723 | "\n", 724 | "By defining other special methods, you can specify the behavior of\n", 725 | "operators on programmer-defined types. For example, if you define a\n", 726 | "method named `__add__` for the `Time` class, you can use the `+`\n", 727 | "operator on Time objects.\n", 728 | "\n", 729 | "Here is an `__add__` method." 730 | ] 731 | }, 732 | { 733 | "cell_type": "code", 734 | "execution_count": 30, 735 | "id": "0d140036", 736 | "metadata": {}, 737 | "outputs": [], 738 | "source": [ 739 | "%%add_method_to Time\n", 740 | "\n", 741 | " def __add__(self, other):\n", 742 | " seconds = self.time_to_int() + other.time_to_int()\n", 743 | " return Time.int_to_time(seconds)" 744 | ] 745 | }, 746 | { 747 | "cell_type": "markdown", 748 | "id": "0221c9ad", 749 | "metadata": {}, 750 | "source": [ 751 | "We can use it like this." 752 | ] 753 | }, 754 | { 755 | "cell_type": "code", 756 | "execution_count": 31, 757 | "id": "280acfce", 758 | "metadata": {}, 759 | "outputs": [], 760 | "source": [ 761 | "duration = Time(1, 32)\n", 762 | "end = start + duration\n", 763 | "print(end)" 764 | ] 765 | }, 766 | { 767 | "cell_type": "markdown", 768 | "id": "7cc7866e", 769 | "metadata": {}, 770 | "source": [ 771 | "There is a lot happening when we run these three lines of code:\n", 772 | "\n", 773 | "* When we instantiate a `Time` object, the `__init__` method is invoked.\n", 774 | "\n", 775 | "* When we use the `+` operator with a `Time` object, its `__add__` method is invoked.\n", 776 | "\n", 777 | "* And when we print a `Time` object, its `__str__` method is invoked.\n", 778 | "\n", 779 | "Changing the behavior of an operator so that it works with programmer-defined types is called **operator overloading**.\n", 780 | "For every operator, like `+`, there is a corresponding special method, like `__add__`. " 781 | ] 782 | }, 783 | { 784 | "cell_type": "markdown", 785 | "id": "b7299e62", 786 | "metadata": {}, 787 | "source": [ 788 | "## Debugging\n", 789 | "\n", 790 | "A `Time` object is valid if the values of `minute` and `second` are between `0` and `60` -- including `0` but not `60` -- and if `hour` is positive.\n", 791 | "Also, `hour` and `minute` should be integer values, but we might allow `second` to have a fraction part.\n", 792 | "Requirements like these are called **invariants** because they should always be true.\n", 793 | "To put it a different way, if they are not true, something has gone wrong.\n", 794 | "\n", 795 | "Writing code to check invariants can help detect errors and find their causes.\n", 796 | "For example, you might have a method like `is_valid` that takes a Time object and returns `False` if it violates an invariant." 797 | ] 798 | }, 799 | { 800 | "cell_type": "code", 801 | "execution_count": 32, 802 | "id": "6eb34442", 803 | "metadata": {}, 804 | "outputs": [], 805 | "source": [ 806 | "%%add_method_to Time\n", 807 | "\n", 808 | " def is_valid(self):\n", 809 | " if self.hour < 0 or self.minute < 0 or self.second < 0:\n", 810 | " return False\n", 811 | " if self.minute >= 60 or self.second >= 60:\n", 812 | " return False\n", 813 | " if not isinstance(self.hour, int):\n", 814 | " return False\n", 815 | " if not isinstance(self.minute, int):\n", 816 | " return False\n", 817 | " return True" 818 | ] 819 | }, 820 | { 821 | "cell_type": "markdown", 822 | "id": "a10ad3db", 823 | "metadata": {}, 824 | "source": [ 825 | "Then, at the beginning of each method you can check the arguments to make sure they are valid." 826 | ] 827 | }, 828 | { 829 | "cell_type": "code", 830 | "execution_count": 33, 831 | "id": "57d86843", 832 | "metadata": {}, 833 | "outputs": [], 834 | "source": [ 835 | "%%add_method_to Time\n", 836 | "\n", 837 | " def is_after(self, other):\n", 838 | " assert self.is_valid(), 'self is not a valid Time'\n", 839 | " assert other.is_valid(), 'self is not a valid Time'\n", 840 | " return self.time_to_int() > other.time_to_int()" 841 | ] 842 | }, 843 | { 844 | "cell_type": "markdown", 845 | "id": "e7c78e9a", 846 | "metadata": {}, 847 | "source": [ 848 | "The `assert` statement evaluates the expression that follows. If the result is `True`, it does nothing; if the result is `False`, it causes an `AssertionError`.\n", 849 | "Here's an example." 850 | ] 851 | }, 852 | { 853 | "cell_type": "code", 854 | "execution_count": 34, 855 | "id": "5452888b", 856 | "metadata": {}, 857 | "outputs": [], 858 | "source": [ 859 | "duration = Time(minute=132)\n", 860 | "print(duration)" 861 | ] 862 | }, 863 | { 864 | "cell_type": "code", 865 | "execution_count": 35, 866 | "id": "56680d97", 867 | "metadata": { 868 | "tags": [] 869 | }, 870 | "outputs": [], 871 | "source": [ 872 | "%%expect AssertionError\n", 873 | "\n", 874 | "start.is_after(duration)" 875 | ] 876 | }, 877 | { 878 | "cell_type": "markdown", 879 | "id": "18bd34ad", 880 | "metadata": {}, 881 | "source": [ 882 | "`assert` statements are useful because they distinguish code that deals with normal conditions from code that checks for errors." 883 | ] 884 | }, 885 | { 886 | "cell_type": "markdown", 887 | "id": "58b86fbe", 888 | "metadata": {}, 889 | "source": [ 890 | "## Glossary\n", 891 | "\n", 892 | "**object-oriented language:**\n", 893 | "A language that provides features to support object-oriented programming, notably user-defined types.\n", 894 | "\n", 895 | "**method:**\n", 896 | "A function that is defined inside a class definition and is invoked on instances of that class.\n", 897 | "\n", 898 | "**receiver:**\n", 899 | "The object a method is invoked on.\n", 900 | "\n", 901 | "**static method:**\n", 902 | "A method that can be invoked without an object as receiver.\n", 903 | "\n", 904 | "**instance method:**\n", 905 | "A method that must be invoked with an object as receiver.\n", 906 | "\n", 907 | "**special method:**\n", 908 | "A method that changes the way operators and some functions work with an object.\n", 909 | "\n", 910 | "**operator overloading:**\n", 911 | "The process of using special methods to change the way operators with with user-defined types.\n", 912 | "\n", 913 | "**invariant:**\n", 914 | " A condition that should always be true during the execution of a program." 915 | ] 916 | }, 917 | { 918 | "cell_type": "markdown", 919 | "id": "796adf5c", 920 | "metadata": {}, 921 | "source": [ 922 | "## Exercises" 923 | ] 924 | }, 925 | { 926 | "cell_type": "code", 927 | "execution_count": null, 928 | "id": "3115ea33", 929 | "metadata": { 930 | "tags": [] 931 | }, 932 | "outputs": [], 933 | "source": [ 934 | "# This cell tells Jupyter to provide detailed debugging information\n", 935 | "# when a runtime error occurs. Run it before working on the exercises.\n", 936 | "\n", 937 | "%xmode Verbose" 938 | ] 939 | }, 940 | { 941 | "cell_type": "markdown", 942 | "id": "25cd6888", 943 | "metadata": {}, 944 | "source": [ 945 | "### Ask a virtual assistant\n", 946 | "\n", 947 | "For more information about static methods, ask a virtual assistant:\n", 948 | "\n", 949 | "* \"What's the difference between an instance method and a static method?\"\n", 950 | "\n", 951 | "* \"Why are static methods called static?\"\n", 952 | "\n", 953 | "If you ask a virtual assistant to generate a static method, the result will probably begin with `@staticmethod`, which is a \"decorator\" that indicates that it is a static method.\n", 954 | "Decorators are not covered in this book, but if you are curious, you can ask a VA for more information.\n", 955 | "\n", 956 | "In this chapter we rewrote several functions as methods.\n", 957 | "Virtual assistants are generally good at this kind of code transformation.\n", 958 | "As an example, paste the following function into a VA and ask it, \"Rewrite this function as a method of the `Time` class.\"" 959 | ] 960 | }, 961 | { 962 | "cell_type": "code", 963 | "execution_count": 36, 964 | "id": "133d7679", 965 | "metadata": {}, 966 | "outputs": [], 967 | "source": [ 968 | "def subtract_time(t1, t2):\n", 969 | " return time_to_int(t1) - time_to_int(t2)" 970 | ] 971 | }, 972 | { 973 | "cell_type": "markdown", 974 | "id": "fc9f135b-e242-4ef6-83eb-8e028235c07b", 975 | "metadata": {}, 976 | "source": [ 977 | "### Exercise\n", 978 | "\n", 979 | "In the previous chapter, a series of exercises asked you to write a `Date` class and several functions that work with `Date` objects.\n", 980 | "Now let's practice rewriting those functions as methods.\n", 981 | "\n", 982 | "1. Write a definition for a `Date` class that represents a date -- that is, a year, month, and day of the month.\n", 983 | "\n", 984 | "2. Write an `__init__` method that takes `year`, `month`, and `day` as parameters and assigns the parameters to attributes. Create an object that represents June 22, 1933.\n", 985 | "\n", 986 | "3. Write `__str__` method that uses an f-string to format the attributes and returns the result. If you test it with the `Date` you created, the result should be `1933-06-22`.\n", 987 | "\n", 988 | "4. Write a method called `is_after` that takes two `Date` objects and returns `True` if the first comes after the second. Create a second object that represents September 17, 1933, and check whether it comes after the first object.\n", 989 | "\n", 990 | "Hint: You might find it useful write a method called `to_tuple` that returns a tuple that contains the attributes of a `Date` object in year-month-day order." 991 | ] 992 | }, 993 | { 994 | "cell_type": "code", 995 | "execution_count": 37, 996 | "id": "3c9f3777-4869-481e-9f4e-4223d6028913", 997 | "metadata": {}, 998 | "outputs": [], 999 | "source": [ 1000 | "# Solution goes here" 1001 | ] 1002 | }, 1003 | { 1004 | "cell_type": "markdown", 1005 | "id": "1122620d-f3f6-4746-8675-13ce0b7f3ee9", 1006 | "metadata": { 1007 | "tags": [] 1008 | }, 1009 | "source": [ 1010 | "You can use these examples to test your solution." 1011 | ] 1012 | }, 1013 | { 1014 | "cell_type": "code", 1015 | "execution_count": 38, 1016 | "id": "fd4b2521-aa71-45da-97eb-ce62ce2714ad", 1017 | "metadata": { 1018 | "tags": [] 1019 | }, 1020 | "outputs": [], 1021 | "source": [ 1022 | "birthday1 = Date(1933, 6, 22)\n", 1023 | "print(birthday1)" 1024 | ] 1025 | }, 1026 | { 1027 | "cell_type": "code", 1028 | "execution_count": 39, 1029 | "id": "ee3f1294-cad1-406b-a574-045ad2b84294", 1030 | "metadata": { 1031 | "tags": [] 1032 | }, 1033 | "outputs": [], 1034 | "source": [ 1035 | "birthday2 = Date(1933, 9, 17)\n", 1036 | "print(birthday2)" 1037 | ] 1038 | }, 1039 | { 1040 | "cell_type": "code", 1041 | "execution_count": 40, 1042 | "id": "ac093f7b-83cf-4488-8842-5c71bcfa35ec", 1043 | "metadata": { 1044 | "tags": [] 1045 | }, 1046 | "outputs": [], 1047 | "source": [ 1048 | "birthday1.is_after(birthday2) # should be False" 1049 | ] 1050 | }, 1051 | { 1052 | "cell_type": "code", 1053 | "execution_count": 41, 1054 | "id": "7e7cb5e1-631f-4b1e-874f-eb16d4792625", 1055 | "metadata": { 1056 | "tags": [] 1057 | }, 1058 | "outputs": [], 1059 | "source": [ 1060 | "birthday2.is_after(birthday1) # should be True" 1061 | ] 1062 | }, 1063 | { 1064 | "cell_type": "code", 1065 | "execution_count": null, 1066 | "id": "5b92712d", 1067 | "metadata": {}, 1068 | "outputs": [], 1069 | "source": [] 1070 | }, 1071 | { 1072 | "cell_type": "markdown", 1073 | "id": "a7f4edf8", 1074 | "metadata": { 1075 | "tags": [] 1076 | }, 1077 | "source": [ 1078 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 1079 | "\n", 1080 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 1081 | "\n", 1082 | "Code license: [MIT License](https://mit-license.org/)\n", 1083 | "\n", 1084 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 1085 | ] 1086 | } 1087 | ], 1088 | "metadata": { 1089 | "celltoolbar": "Tags", 1090 | "kernelspec": { 1091 | "display_name": "Python 3 (ipykernel)", 1092 | "language": "python", 1093 | "name": "python3" 1094 | }, 1095 | "language_info": { 1096 | "codemirror_mode": { 1097 | "name": "ipython", 1098 | "version": 3 1099 | }, 1100 | "file_extension": ".py", 1101 | "mimetype": "text/x-python", 1102 | "name": "python", 1103 | "nbconvert_exporter": "python", 1104 | "pygments_lexer": "ipython3", 1105 | "version": "3.10.11" 1106 | } 1107 | }, 1108 | "nbformat": 4, 1109 | "nbformat_minor": 5 1110 | } 1111 | -------------------------------------------------------------------------------- /chapters/chap19.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1331faa1", 6 | "metadata": {}, 7 | "source": [ 8 | "You can order print and ebook versions of *Think Python 3e* from\n", 9 | "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", 10 | "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "171aca73", 16 | "metadata": {}, 17 | "source": [ 18 | "# Final thoughts" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "id": "4d551c99", 24 | "metadata": {}, 25 | "source": [ 26 | "Learning to program is not easy, but if you made it this far, you are off to a good start.\n", 27 | "Now I have some suggestions for ways you can keep learning and apply what you have learned.\n", 28 | "\n", 29 | "This book is meant to be a general introduction to programming, so we have not focused on specific applications.\n", 30 | "Depending on your interests, there are any number of areas where you can apply your new skills.\n", 31 | "\n", 32 | "If you are interested in Data Science, there are three books of mine you might like:\n", 33 | "\n", 34 | "* *Think Stats: Exploratory Data Analysis*, O'Reilly Media, 2014.\n", 35 | "\n", 36 | "* *Think Bayes: Bayesian Statistics in Python*, O'Reilly Media, 2021.\n", 37 | "\n", 38 | "* *Think DSP: Digital Signal Processing in Python*, O'Reilly Media, 2016." 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "cceabe36", 44 | "metadata": {}, 45 | "source": [ 46 | "If you are interested in physical modeling and complex systems, you might like:\n", 47 | "\n", 48 | "* *Modeling and Simulation in Python: An Introduction for Scientists and Engineers*, No Starch Press, 2023.\n", 49 | "\n", 50 | "* *Think Complexity: Complexity Science and Computational Modeling*, O'Reilly Media, 2018.\n", 51 | "\n", 52 | "These use NumPy, SciPy, pandas, and other Python libraries for data science and scientific computing." 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "id": "54a39121", 58 | "metadata": {}, 59 | "source": [ 60 | "This book tries to find a balance between general principles of programming and details of Python.\n", 61 | "As a result, it does not include every feature of the Python language.\n", 62 | "For more about Python, and good advice about how to use it, I recommend *Fluent Python: Clear, Concise, and Effective Programming*, second edition by Luciano Ramalho, O'Reilly Media, 2022.\n", 63 | "\n", 64 | "After an introduction to programming, a common next step is to learn about data structures and algorithms.\n", 65 | "I have a work in progress on this topic, called *Data Structures and Information Retrieval in Python*.\n", 66 | "A free electronic version is available from Green Tea Press at ." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "a1598510", 72 | "metadata": {}, 73 | "source": [ 74 | "As you work on more complex programs, you will encounter new challenges.\n", 75 | "You might find it helpful to review the sections in this book about debugging.\n", 76 | "In particular, remember the Six R's of debugging from [Chapter 12](section_debugging_12): reading, running, ruminating, rubber-ducking, retreating, and resting.\n", 77 | "\n", 78 | "This book suggests tools to help with debugging, including the `print` and `repr` functions, the `structshape` function in [Chapter 11](section_debugging_11) -- and the built-in functions `isinstance`, `hasattr`, and `vars` in [Chapter 14](section_debugging_14)." 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "fb4dd345", 84 | "metadata": {}, 85 | "source": [ 86 | "It also suggests tools for testing programs, including the `assert` statement, the `doctest` module, and the `unittest` module.\n", 87 | "Including tests in your programs is one of the best ways to prevent and detect errors, and save time debugging.\n", 88 | "\n", 89 | "But the best kind of debugging is the kind you don't have to do.\n", 90 | "If you use an incremental development process as described in [Chapter 6](section_incremental) -- and test as you go -- you will make fewer errors and find them more quickly when you do.\n", 91 | "Also, remember encapsulation and generalization from [Chapter 4](section_encapsulation), which is particularly useful when you are developing code in Jupyter notebooks." 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "id": "0d29933e", 97 | "metadata": {}, 98 | "source": [ 99 | "Throughout this book, I've suggested ways to use virtual assistants to help you learn, program, and debug.\n", 100 | "I hope you are finding these tools useful.\n", 101 | "\n", 102 | "In additional to virtual assistants like ChatGPT, you might also want to use a tool like Copilot that autocompletes code as you type.\n", 103 | "I did not recommend using these tools, initially, because they can be overwhelming for beginners.\n", 104 | "But you might want to explore them now.\n", 105 | "\n", 106 | "Using AI tools effectively requires some experimentation and reflection to find a flow that works for you.\n", 107 | "If you think it's a nuisance to copy code from ChatGPT to Jupyter, you might prefer something like Copilot.\n", 108 | "But the cognitive work you do to compose a prompt and interpret the response can be as valuable as the code the tool generates, in the same vein as rubber duck debugging." 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "id": "c28d6815", 114 | "metadata": {}, 115 | "source": [ 116 | "As you gain programming experience, you might want to explore other development environments.\n", 117 | "I think Jupyter notebooks are a good place to start, but they are relatively new and not as widely-used as conventional integrated development environments (IDE).\n", 118 | "For Python, the most popular IDEs include PyCharm and Spyder -- and Thonny, which is often recommended for beginners.\n", 119 | "Other IDEs, like Visual Studio Code and Eclipse, work with other programming languages as well.\n", 120 | "Or, as a simpler alternative, you can write Python programs using any text editor you like.\n", 121 | "\n", 122 | "As you continue your programming journey, you don't have to go alone!\n", 123 | "If you live in or near a city, there's a good chance there is a Python user group you can join.\n", 124 | "These groups are usually friendly to beginners, so don't be afraid.\n", 125 | "If there is no group near you, you might be able to join events remotely.\n", 126 | "Also, keep an eye out for regional Python conferences." 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "28cb22bf", 132 | "metadata": {}, 133 | "source": [ 134 | "One of the best ways to improve your programming skills is to learn another language.\n", 135 | "If you are interested in statistics and data science, you might want to learn R.\n", 136 | "But I particularly recommend learning a functional language like Racket or Elixir.\n", 137 | "Functional programming requires a different kind of thinking, which changes the way you think about programs.\n", 138 | "\n", 139 | "Good luck!" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "id": "e2783577", 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "id": "a7f4edf8", 153 | "metadata": { 154 | "tags": [] 155 | }, 156 | "source": [ 157 | "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", 158 | "\n", 159 | "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", 160 | "\n", 161 | "Code license: [MIT License](https://mit-license.org/)\n", 162 | "\n", 163 | "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 164 | ] 165 | } 166 | ], 167 | "metadata": { 168 | "kernelspec": { 169 | "display_name": "Python 3 (ipykernel)", 170 | "language": "python", 171 | "name": "python3" 172 | }, 173 | "language_info": { 174 | "codemirror_mode": { 175 | "name": "ipython", 176 | "version": 3 177 | }, 178 | "file_extension": ".py", 179 | "mimetype": "text/x-python", 180 | "name": "python", 181 | "nbconvert_exporter": "python", 182 | "pygments_lexer": "ipython3", 183 | "version": "3.10.14" 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 5 188 | } 189 | -------------------------------------------------------------------------------- /chapters/jupyter_intro.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "WzCwnbY17x0O", 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# *Think Python* on Jupyter\n", 11 | "\n", 12 | "This is an introduction to Jupyter notebooks for people reading the third edition of [*Think Python*](https://greenteapress.com/wp/think-python-3rd-edition) by Allen B. Downey.\n", 13 | "\n", 14 | "A Jupyter notebook is a document that contains text, code, and results from running the code.\n", 15 | "You can read a notebook like a book, but you can also run the code, modify it, and develop new programs.\n", 16 | "\n", 17 | "Jupyter notebooks run in a web browser, so you can run them without installing any new software.\n", 18 | "But they have to connect to a Jupyter server.\n", 19 | "\n", 20 | "You can install and run a server yourself, but to get started it is easier to use a service like [Colab](https://colab.research.google.com/), which is operated by Google.\n", 21 | "\n", 22 | "[On the starting page for the book](https://allendowney.github.io/ThinkPython) you will find a link for each chapter.\n", 23 | "If you click on one of these links, it opens a notebook on Colab." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": { 29 | "id": "WzCwnbY17x0O", 30 | "tags": [] 31 | }, 32 | "source": [ 33 | "If you are reading this notebook on Colab, you should see an orange logo in the upper left that looks like the letters `CO`.\n", 34 | "\n", 35 | "If you are not running this notebook on Colab, [you can click here to open it on Colab](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/jupyter_intro.ipynb)." 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": { 41 | "id": "WzCwnbY17x0O", 42 | "tags": [] 43 | }, 44 | "source": [ 45 | "## What is a notebook?\n", 46 | "\n", 47 | "A Jupyter notebook is made up of cells, where each cell contains either text or code.\n", 48 | "This cell contains text. \n", 49 | "\n", 50 | "The following cell contains code." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 2, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "Hello\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "print('Hello')" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": { 73 | "id": "WzCwnbY17x0O", 74 | "tags": [] 75 | }, 76 | "source": [ 77 | "Click on the previous cell to select it.\n", 78 | "You should see a button on the left with a triangle inside a circle, which is the icon for \"Play\".\n", 79 | "If you press this button, Jupyter runs the code in the cell and displays the result.\n", 80 | "\n", 81 | "When you run code in a notebook for the first time, it might take a few seconds to start.\n", 82 | "And if it's a notebook you didn't write, you might get a warning message.\n", 83 | "If you are running a notebook from a source you trust, which I hope includes me, you can press \"Run Anyway\"." 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": { 89 | "id": "WzCwnbY17x0O", 90 | "tags": [] 91 | }, 92 | "source": [ 93 | "Instead of clicking the \"Play\" button, you can also run the code in a cell by holding down `Shift` and pressing `Enter`.\n", 94 | "\n", 95 | "If you are running this notebook on Colab, you should see buttons in the top left that say \"+ Code\" and \"+ Text\". The first one adds a code cell and the second adds a text cell.\n", 96 | "If you want to try them out, select this cell by clicking on it, then press the \"+ Text\" button.\n", 97 | "A new cell should appear below this one." 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": { 103 | "id": "WzCwnbY17x0O", 104 | "tags": [] 105 | }, 106 | "source": [ 107 | "Add some text to the cell.\n", 108 | "You can use the buttons to format it, or you can mark up the text using [Markdown](https://www.markdownguide.org/basic-syntax/).\n", 109 | "When you are done, hold down `Shift` and press `Enter`, which will format the text you just typed and then move to the next cell." 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "At any time Jupyter is in one of two modes:\n", 117 | "\n", 118 | "* In **command mode**, you can perform operations that affect cells, like adding and removing entire cells.\n", 119 | "\n", 120 | "* In **edit mode**, you can edit the contents of a cell.\n", 121 | "\n", 122 | "With text cells, it is obvious which mode you are in.\n", 123 | "In edit mode, the cell is split vertically, with the text you are editing on the left and the formatted text on the right.\n", 124 | "And you'll see text editing tools across the top.\n", 125 | "In command mode, you see only the formatted text.\n", 126 | "\n", 127 | "With code cells, the difference is more subtle, but if there's a cursor in the cell, you are in edit mode.\n", 128 | "\n", 129 | "To go from edit mode to command mode, press `ESC`.\n", 130 | "To go from command mode to edit mode, press `Enter`." 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "When you are done working on a notebook, you can close the window, but any changes you made will disappear.\n", 138 | "If you make any changes you want to keep, open the File menu in the upper left.\n", 139 | "You'll see several ways you can save the notebook.\n", 140 | "\n", 141 | "* If you have a Google account, you can save the notebook in your Drive.\n", 142 | "\n", 143 | "* If you have a GitHub account, you can save it on GitHub.\n", 144 | "\n", 145 | "* Or if you want to save the notebook on your computer, select \"Download\" and then \"Download .ipynb\" The suffix \".ipynb\" indicates that it is a notebook file, as opposed to \".py\", which indicates a file that contains Python code only." 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "## Code for *Think Python*\n", 153 | "\n", 154 | "At the beginning of each notebook, you'll see a cell with code like this:" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 2, 160 | "metadata": {}, 161 | "outputs": [], 162 | "source": [ 163 | "from os.path import basename, exists\n", 164 | "\n", 165 | "def download(url):\n", 166 | " filename = basename(url)\n", 167 | " if not exists(filename):\n", 168 | " from urllib.request import urlretrieve\n", 169 | "\n", 170 | " local, _ = urlretrieve(url, filename)\n", 171 | " print(\"Downloaded \" + str(local))\n", 172 | " return filename\n", 173 | "\n", 174 | "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/thinkpython.py')\n", 175 | "\n", 176 | "import thinkpython" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "You don't need to know how this code works, but when you get to the end of the book, most of it will make sense.\n", 184 | "As you might guess, it downloads a file -- specifically, it downloads `thinkpython.py`, which contains Python code provided specifically for this book.\n", 185 | "The last line \"imports\" this code, which means we can use the code in the notebook.\n", 186 | "\n", 187 | "In other chapters, you will see code that downloads `diagram.py`, which is used to generated the diagrams in the book, and `jupyturtle.py`, which is used in several chapters to create turtle graphics.\n", 188 | "\n", 189 | "In some places you will see a cell like this that begins with `%%expect`." 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 3, 195 | "metadata": {}, 196 | "outputs": [ 197 | { 198 | "ename": "SyntaxError", 199 | "evalue": "invalid syntax (3827346253.py, line 1)", 200 | "output_type": "error", 201 | "traceback": [ 202 | "\u001b[0;36m Cell \u001b[0;32mIn[3], line 1\u001b[0;36m\u001b[0m\n\u001b[0;31m abs 42\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "%%expect SyntaxError\n", 208 | "\n", 209 | "abs 42" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "`%%expect` is not part of Python -- it is a Jupyter \"magic command\" that indicates that we expect the cell to product an error.\n", 217 | "When you see this command, it means that the error is deliberate, usually intended to warn you about a common pitfall." 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "For more about running Jupyter notebooks on Colab, [click here](https://colab.research.google.com/notebooks/basic_features_overview.ipynb).\n", 225 | "\n", 226 | "Or, if you are ready to get started, [click here to read Chapter 1](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/chap01.ipynb)." 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": { 232 | "id": "M9yF11G47x0l", 233 | "tags": [] 234 | }, 235 | "source": [ 236 | "*Think Python*, 3rd edition.\n", 237 | "\n", 238 | "Copyright 2023 [Allen B. Downey](https://allendowney.com)\n", 239 | "\n", 240 | "License: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "code", 245 | "execution_count": null, 246 | "metadata": { 247 | "id": "Cq6EYo057x0l" 248 | }, 249 | "outputs": [], 250 | "source": [] 251 | } 252 | ], 253 | "metadata": { 254 | "celltoolbar": "Tags", 255 | "colab": { 256 | "provenance": [] 257 | }, 258 | "kernelspec": { 259 | "display_name": "Python 3 (ipykernel)", 260 | "language": "python", 261 | "name": "python3" 262 | }, 263 | "language_info": { 264 | "codemirror_mode": { 265 | "name": "ipython", 266 | "version": 3 267 | }, 268 | "file_extension": ".py", 269 | "mimetype": "text/x-python", 270 | "name": "python", 271 | "nbconvert_exporter": "python", 272 | "pygments_lexer": "ipython3", 273 | "version": "3.10.11" 274 | } 275 | }, 276 | "nbformat": 4, 277 | "nbformat_minor": 1 278 | } 279 | -------------------------------------------------------------------------------- /diagram.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.patches as patches 3 | 4 | from matplotlib.transforms import Bbox, TransformedBbox 5 | 6 | # TODO: Study this https://matplotlib.org/stable/tutorials/text/annotations.html#sphx-glr-tutorials-text-annotations-py 7 | 8 | 9 | def override(d1, **d2): 10 | """Add key-value pairs to d. 11 | 12 | d1: dictionary 13 | d2: keyword args to add to d 14 | 15 | returns: new dict 16 | """ 17 | d = d1.copy() 18 | d.update(d2) 19 | return d 20 | 21 | def underride(d1, **d2): 22 | """Add key-value pairs to d only if key is not in d. 23 | 24 | d1: dictionary 25 | d2: keyword args to add to d 26 | 27 | returns: new dict 28 | """ 29 | d = d2.copy() 30 | d.update(d1) 31 | return d 32 | 33 | def diagram(width=5, height=1, **options): 34 | fig, ax = plt.subplots(**options) 35 | 36 | # TODO: dpi in the notebook should be 100, in the book it should be 300 or 600 37 | # fig.set_dpi(100) 38 | 39 | # Set figure size 40 | fig.set_size_inches(width, height) 41 | 42 | plt.rc('font', size=8) 43 | 44 | # Set axes position 45 | ax.set_position([0, 0, 1, 1]) 46 | 47 | # Set x and y limits 48 | ax.set_xlim(0, width) 49 | ax.set_ylim(0, height) 50 | 51 | # Remove the spines, ticks, and labels 52 | despine(ax) 53 | return ax 54 | 55 | def despine(ax): 56 | # Remove the spines 57 | ax.spines['right'].set_visible(False) 58 | ax.spines['top'].set_visible(False) 59 | ax.spines['bottom'].set_visible(False) 60 | ax.spines['left'].set_visible(False) 61 | 62 | # Remove the axis labels 63 | ax.set_xticklabels([]) 64 | ax.set_yticklabels([]) 65 | 66 | # Remove the tick marks 67 | ax.tick_params(axis='both', which='both', length=0, width=0) 68 | 69 | def adjust(x, y, bbox): 70 | """Adjust the coordinates of a point based on a bounding box. 71 | 72 | x: x coordinate 73 | y: y coordinate 74 | bbox: Bbox object 75 | 76 | returns: tuple of coordinates 77 | """ 78 | width = bbox.width 79 | height = bbox.height + 0.2 80 | t = width, height, x - bbox.x0, y - bbox.y0 + 0.1 81 | return [round(x, 2) for x in t] 82 | 83 | def get_bbox(ax, handle): 84 | bbox = handle.get_window_extent() 85 | transformed = TransformedBbox(bbox, ax.transData.inverted()) 86 | return transformed 87 | 88 | def draw_bbox(ax, bbox, **options): 89 | options = underride(options, facecolor='gray', alpha=0.1, linewidth=0) 90 | rect = patches.Rectangle((bbox.xmin, bbox.ymin), bbox.width, bbox.height, **options) 91 | handle = ax.add_patch(rect) 92 | bbox = get_bbox(ax, handle) 93 | return bbox 94 | 95 | def draw_box_around(ax, bboxes, **options): 96 | bbox = Bbox.union(bboxes) 97 | return draw_bbox(ax, padded(bbox), **options) 98 | 99 | def padded(bbox, dx=0.1, dy=0.1): 100 | """Add padding to a bounding box. 101 | """ 102 | [x0, y0], [x1, y1] = bbox.get_points() 103 | return Bbox([[x0-dx, y0-dy], [x1+dx, y1+dy]]) 104 | 105 | def make_binding(name, value, **options): 106 | """Make a binding between a name and a value. 107 | 108 | name: string 109 | value: any type 110 | 111 | returns: Binding object 112 | """ 113 | if not isinstance(value, Frame): 114 | value = Value(repr(value)) 115 | 116 | return Binding(Value(name), value, **options) 117 | 118 | def make_mapping(key, value, **options): 119 | """Make a binding between a key and a value. 120 | 121 | key: any type 122 | value: any type 123 | 124 | returns: Binding object 125 | """ 126 | return Binding(Value(repr(key)), Value(repr(value)), **options) 127 | 128 | def make_dict(d, name='dict', **options): 129 | """Make a Frame that represents a dictionary. 130 | 131 | d: dictionary 132 | name: string 133 | options: passed to Frame 134 | """ 135 | mappings = [make_mapping(key, value) for key, value in d.items()] 136 | return Frame(mappings, name=name, **options) 137 | 138 | def make_frame(d, name='frame', **options): 139 | """Make a Frame that represents a stack frame. 140 | 141 | d: dictionary 142 | name: string 143 | options: passed to Frame 144 | """ 145 | bindings = [make_binding(key, value) for key, value in d.items()] 146 | return Frame(bindings, name=name, **options) 147 | 148 | class Binding(object): 149 | def __init__(self, name, value=None, **options): 150 | """ Represents a binding between a name and a value. 151 | 152 | name: Value object 153 | value: Value object 154 | """ 155 | self.name = name 156 | self.value = value 157 | self.options = options 158 | 159 | def draw(self, ax, x, y, **options): 160 | options = override(self.options, **options) 161 | dx = options.pop('dx', 0.4) 162 | dy = options.pop('dy', 0) 163 | draw_value = options.pop('draw_value', True) 164 | 165 | bbox1 = self.name.draw(ax, x, y, ha='right') 166 | bboxes = [bbox1] 167 | 168 | arrow = Arrow(dx=dx, dy=dy, **options) 169 | bbox2 = arrow.draw(ax, x, y) 170 | 171 | if draw_value: 172 | bbox3 = self.value.draw(ax, x+dx, y+dy) 173 | # only include the arrow if we drew the value 174 | bboxes.extend([bbox2, bbox3]) 175 | 176 | bbox = Bbox.union(bboxes) 177 | # draw_bbox(ax, self.bbox) 178 | self.bbox = bbox 179 | return bbox 180 | 181 | 182 | class Element(object): 183 | def __init__(self, index, value, **options): 184 | """ Represents a an element of a list. 185 | 186 | index: integer 187 | value: Value object 188 | """ 189 | self.index = index 190 | self.value = value 191 | self.options = options 192 | 193 | def draw(self, ax, x, y, dx=0.15, **options): 194 | options = override(self.options, **options) 195 | draw_value = options.pop('draw_value', True) 196 | 197 | bbox1 = self.index.draw(ax, x, y, ha='right', fontsize=6, color='gray') 198 | bboxes = [bbox1] 199 | 200 | if draw_value: 201 | bbox2 = self.value.draw(ax, x+dx, y) 202 | bboxes.append(bbox2) 203 | 204 | bbox = Bbox.union(bboxes) 205 | self.bbox = bbox 206 | # draw_bbox(ax, self.bbox) 207 | return bbox 208 | 209 | 210 | class Value(object): 211 | def __init__(self, value): 212 | self.value = value 213 | self.options = dict(ha='left', va='center') 214 | self.bbox = None 215 | 216 | def draw(self, ax, x, y, **options): 217 | options = override(self.options, **options) 218 | 219 | handle = ax.text(x, y, self.value, **options) 220 | bbox = self.bbox = get_bbox(ax, handle) 221 | # draw_bbox(ax, bbox) 222 | self.bbox = bbox 223 | return bbox 224 | 225 | 226 | class Arrow(object): 227 | def __init__(self, **options): 228 | # Note for the future about dotted arrows 229 | # self.arrowprops = dict(arrowstyle="->", ls=':') 230 | arrowprops = dict(arrowstyle="->", color='gray') 231 | options = underride(options, arrowprops=arrowprops) 232 | self.options = options 233 | 234 | def draw(self, ax, x, y, **options): 235 | options = override(self.options, **options) 236 | dx = options.pop('dx', 0.5) 237 | dy = options.pop('dy', 0) 238 | shim = options.pop('shim', 0.02) 239 | 240 | handle = ax.annotate("", [x+dx, y+dy], [x+shim, y], **options) 241 | bbox = get_bbox(ax, handle) 242 | self.bbox = bbox 243 | return bbox 244 | 245 | 246 | class ReturnArrow(object): 247 | def __init__(self, **options): 248 | style = "Simple, tail_width=0.5, head_width=4, head_length=8" 249 | options = underride(options, arrowstyle=style, color="gray") 250 | self.options = options 251 | 252 | def draw(self, ax, x, y, **options): 253 | options = override(self.options, **options) 254 | value = options.pop('value', None) 255 | dx = options.pop('dx', 0) 256 | dy = options.pop('dy', 0.4) 257 | shim = options.pop('shim', 0.02) 258 | 259 | x += shim 260 | arrow = patches.FancyArrowPatch((x, y), (x+dx, y+dy), 261 | connectionstyle="arc3,rad=.6", **options) 262 | handle = ax.add_patch(arrow) 263 | bbox = get_bbox(ax, handle) 264 | 265 | if value is not None: 266 | handle = plt.text(x+0.15, y+dy/2, str(value), ha='left', va='center') 267 | bbox2 = get_bbox(ax, handle) 268 | bbox = Bbox.union([bbox, bbox2]) 269 | 270 | self.bbox = bbox 271 | return bbox 272 | 273 | 274 | class Frame(object): 275 | def __init__(self, bindings, **options): 276 | self.bindings = bindings 277 | self.options = options 278 | 279 | def draw(self, ax, x, y, **options): 280 | options = override(self.options, **options) 281 | name = options.pop('name', '') 282 | value = options.pop('value', None) 283 | dx = options.pop('dx', 0) 284 | dy = options.pop('dy', 0) 285 | offsetx = options.pop('offsetx', 0) 286 | offsety = options.pop('offsety', 0) 287 | shim = options.pop('shim', 0) 288 | loc = options.pop('loc', 'top') 289 | box_around = options.pop('box_around', None) 290 | 291 | x += offsetx 292 | y += offsety 293 | save_y = y 294 | 295 | if len(self.bindings) == 0: 296 | bbox = Bbox([[x, y], [x, y]]) 297 | bboxes = [bbox] 298 | else: 299 | bboxes = [] 300 | 301 | # draw the bindings 302 | for binding in self.bindings: 303 | bbox = binding.draw(ax, x, y) 304 | bboxes.append(bbox) 305 | x += dx 306 | y += dy 307 | 308 | if box_around: 309 | bbox1 = draw_bbox(ax, box_around, **options) 310 | else: 311 | bbox1 = draw_box_around(ax, bboxes, **options) 312 | bboxes.append(bbox1) 313 | 314 | if value is not None: 315 | arrow = ReturnArrow(value=value) 316 | x = bbox1.xmax + shim 317 | bbox2 = arrow.draw(ax, x, save_y, value=value) 318 | bboxes.append(bbox2) 319 | 320 | if name: 321 | if loc == 'top': 322 | x = bbox1.xmin 323 | y = bbox1.ymax + 0.02 324 | handle = plt.text(x, y, name, ha='left', va='bottom') 325 | elif loc == 'left': 326 | x = bbox1.xmin - 0.1 327 | y = save_y 328 | handle = plt.text(x, y, name, ha='right', va='center') 329 | bbox3 = get_bbox(ax, handle) 330 | bboxes.append(bbox3) 331 | 332 | bbox = Bbox.union(bboxes) 333 | self.bbox = bbox 334 | return bbox 335 | 336 | 337 | class Stack(object): 338 | def __init__(self, frames, **options): 339 | self.frames = frames 340 | self.options = options 341 | 342 | def draw(self, ax, x, y, **options): 343 | options = override(self.options, **options) 344 | dx = options.pop('dx', 0) 345 | dy = options.pop('dy', -0.4) 346 | 347 | # draw the frames 348 | bboxes = [] 349 | for frame in self.frames: 350 | bbox = frame.draw(ax, x, y) 351 | bboxes.append(bbox) 352 | x += dx 353 | y += dy 354 | 355 | bbox = Bbox.union(bboxes) 356 | self.bbox = bbox 357 | return bbox 358 | 359 | def make_rebind(name, seq): 360 | bindings = [] 361 | for i, value in enumerate(seq): 362 | dy = dy=-0.3*i 363 | if i == len(seq)-1: 364 | binding = make_binding(name, value, dy=dy) 365 | else: 366 | arrowprops = dict(arrowstyle="->", color='gray', ls=':') 367 | binding = make_binding('', value, dy=dy, arrowprops=arrowprops) 368 | bindings.append(binding) 369 | 370 | return bindings 371 | 372 | def make_element(index, value): 373 | return Element(Value(index), Value(repr(value))) 374 | 375 | def make_list(seq, name='list', **options): 376 | elements = [make_element(index, value) for index, value in enumerate(seq)] 377 | return Frame(elements, name=name, **options) 378 | 379 | def draw_bindings(bindings, ax, x, y): 380 | bboxes = [] 381 | for binding in bindings: 382 | bbox = binding.draw(ax, x, y) 383 | bboxes.append(bbox) 384 | 385 | bbox = Bbox.union(bboxes) 386 | return bbox -------------------------------------------------------------------------------- /jb/_config.yml: -------------------------------------------------------------------------------- 1 | # Book settings 2 | title: Think Python 3 | author: Allen B. Downey 4 | 5 | latex: 6 | latex_documents: 7 | targetname: book.tex 8 | 9 | execute: 10 | execute_notebooks: 'off' 11 | 12 | repository: 13 | url: https://github.com/AllenDowney/ThinkPython/tree/v3 14 | html: 15 | use_repository_button: true 16 | 17 | extra_footer: | 18 | 29 | 30 | parse: 31 | # myst_extended_syntax: true # instead enable individual features below 32 | myst_enable_extensions: # https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html 33 | - amsmath 34 | # - attrs_inline # causing the conflict with dollarmath 35 | - colon_fence 36 | - deflist 37 | - dollarmath 38 | - fieldlist 39 | - html_admonition 40 | - html_image 41 | - linkify 42 | - replacements 43 | - smartquotes 44 | - strikethrough 45 | - substitution 46 | - tasklist 47 | 48 | sphinx: 49 | config: 50 | mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 51 | mathjax_config: 52 | tex2jax: 53 | inlineMath: [["$","$"], ["\\(", "\\)"]] 54 | myst_update_mathjax: false 55 | -------------------------------------------------------------------------------- /jb/_toc.yml: -------------------------------------------------------------------------------- 1 | format: jb-book 2 | root: index 3 | parts: 4 | - caption: Front Matter 5 | chapters: 6 | - file: chap00 7 | - caption: Chapters 8 | numbered: True 9 | chapters: 10 | - file: chap01 11 | - file: chap02 12 | - file: chap03 13 | - file: chap04 14 | - file: chap05 15 | - file: chap06 16 | - file: chap07 17 | - file: chap08 18 | - file: chap09 19 | - file: chap10 20 | - file: chap11 21 | - file: chap12 22 | - file: chap13 23 | - file: chap14 24 | - file: chap15 25 | - file: chap16 26 | - file: chap17 27 | - file: chap18 28 | - file: chap19 29 | - caption: End Matter 30 | chapters: 31 | - file: blank 32 | -------------------------------------------------------------------------------- /jb/build.sh: -------------------------------------------------------------------------------- 1 | # pip install jupyter-book ghp-import 2 | 3 | # Build the Jupyter book version 4 | 5 | # copy the notebooks 6 | cp ../ThinkPythonSolutions/soln/chap[01][0-9]*.ipynb . 7 | 8 | # add tags to hide the solutions 9 | python prep_notebooks.py 10 | 11 | # build the HTML version 12 | jb build . 13 | 14 | # push it to GitHub 15 | ghp-import -n -p -f _build/html 16 | -------------------------------------------------------------------------------- /jb/prep_notebooks.py: -------------------------------------------------------------------------------- 1 | import nbformat as nbf 2 | from glob import glob 3 | 4 | 5 | def process_cell(cell): 6 | # get tags 7 | tags = cell['metadata'].get('tags', []) 8 | 9 | # add hide-cell tag to solutions 10 | if cell['cell_type'] == 'code': 11 | source = cell['source'] 12 | 13 | # remove solutions 14 | if source.startswith('# Solution') or 'solution' in tags: 15 | cell['source'] = [] 16 | 17 | # remove %%expect cell magic 18 | if source.startswith('%%expect'): 19 | t = source.split('\n')[1:] 20 | cell['source'] = '\n'.join(t) 21 | 22 | # add reference label 23 | for tag in tags: 24 | if tag.startswith('chapter') or tag.startswith('section'): 25 | # print(tag) 26 | label = f'({tag})=\n' 27 | cell['source'] = label + cell['source'] 28 | 29 | 30 | def process_notebook(path): 31 | ntbk = nbf.read(path, nbf.NO_CONVERT) 32 | 33 | for cell in ntbk.cells: 34 | process_cell(cell) 35 | 36 | nbf.write(ntbk, path) 37 | 38 | 39 | # Collect a list of the notebooks in the content folder 40 | paths = glob("chap*.ipynb") 41 | 42 | for path in sorted(paths): 43 | print('prepping', path) 44 | process_notebook(path) 45 | -------------------------------------------------------------------------------- /jupyturtle_flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/jupyturtle_flower.png -------------------------------------------------------------------------------- /jupyturtle_pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/jupyturtle_pie.png -------------------------------------------------------------------------------- /jupyturtle_pie.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 76 | 78 | 80 | 82 | 84 | 86 | 88 | 90 | 92 | 94 | 96 | 98 | 100 | 102 | 104 | 106 | 108 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /photos.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/photos.zip -------------------------------------------------------------------------------- /structshape.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides one function, structshape(), which takes 3 | an object of any type and returns a string that summarizes the 4 | "shape" of the data structure; that is, the type, size and 5 | composition. 6 | 7 | Copyright 2012 Allen B. Downey 8 | Distributed under the GNU General Public License at gnu.org/licenses/gpl.html. 9 | 10 | """ 11 | 12 | def structshape(ds): 13 | """Returns a string that describes the shape of a data structure. 14 | 15 | ds: any Python object 16 | 17 | Returns: string 18 | """ 19 | typename = type(ds).__name__ 20 | 21 | # handle sequences 22 | sequence = (list, tuple, set, type(iter(''))) 23 | if isinstance(ds, sequence): 24 | t = [] 25 | for i, x in enumerate(ds): 26 | t.append(structshape(x)) 27 | rep = '%s of %s' % (typename, listrep(t)) 28 | return rep 29 | 30 | # handle dictionaries 31 | elif isinstance(ds, dict): 32 | keys = set() 33 | vals = set() 34 | for k, v in ds.items(): 35 | keys.add(structshape(k)) 36 | vals.add(structshape(v)) 37 | rep = '%s of %d %s->%s' % (typename, len(ds), 38 | setrep(keys), setrep(vals)) 39 | return rep 40 | 41 | # handle other types 42 | else: 43 | if hasattr(ds, '__class__'): 44 | return ds.__class__.__name__ 45 | else: 46 | return typename 47 | 48 | 49 | def listrep(t): 50 | """Returns a string representation of a list of type strings. 51 | 52 | t: list of strings 53 | 54 | Returns: string 55 | """ 56 | current = t[0] 57 | count = 0 58 | res = [] 59 | for x in t: 60 | if x == current: 61 | count += 1 62 | else: 63 | append(res, current, count) 64 | current = x 65 | count = 1 66 | append(res, current, count) 67 | return setrep(res) 68 | 69 | 70 | def setrep(s): 71 | """Returns a string representation of a set of type strings. 72 | 73 | s: set of strings 74 | 75 | Returns: string 76 | """ 77 | rep = ', '.join(s) 78 | if len(s) == 1: 79 | return rep 80 | else: 81 | return '(' + rep + ')' 82 | return 83 | 84 | 85 | def append(res, typestr, count): 86 | """Adds a new element to a list of type strings. 87 | 88 | Modifies res. 89 | 90 | res: list of type strings 91 | typestr: the new type string 92 | count: how many of the new type there are 93 | 94 | Returns: None 95 | """ 96 | if count == 1: 97 | rep = typestr 98 | else: 99 | rep = '%d %s' % (count, typestr) 100 | res.append(rep) 101 | 102 | 103 | if __name__ == '__main__': 104 | 105 | t = [1,2,3] 106 | print(structshape(t)) 107 | 108 | t2 = [[1,2], [3,4], [5,6]] 109 | print(structshape(t2)) 110 | 111 | t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9] 112 | print(structshape(t3)) 113 | 114 | class Point: 115 | """trivial object type""" 116 | 117 | t4 = [Point(), Point()] 118 | print(structshape(t4)) 119 | 120 | s = set('abc') 121 | print(structshape(s)) 122 | 123 | lt = zip(t, s) 124 | print(structshape(lt)) 125 | 126 | d = dict(lt) 127 | print(structshape(d)) 128 | 129 | it = iter('abc') 130 | print(structshape(it)) 131 | -------------------------------------------------------------------------------- /think_python_3e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AllenDowney/ThinkPython/38462ce925df86fb8015cc1e0c4873f5e237f9b2/think_python_3e.jpg -------------------------------------------------------------------------------- /thinkpython.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import io 3 | import re 4 | 5 | 6 | def extract_function_name(text): 7 | """Find a function definition and return its name. 8 | 9 | text: String 10 | 11 | returns: String or None 12 | """ 13 | pattern = r"def\s+(\w+)\s*\(" 14 | match = re.search(pattern, text) 15 | if match: 16 | func_name = match.group(1) 17 | return func_name 18 | else: 19 | return None 20 | 21 | 22 | # the functions that define cell magic commands are only defined 23 | # if we're running in Jupyter. 24 | 25 | try: 26 | from IPython.core.magic import register_cell_magic 27 | from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring 28 | 29 | @register_cell_magic 30 | def add_method_to(args, cell): 31 | 32 | # get the name of the function defined in this cell 33 | func_name = extract_function_name(cell) 34 | if func_name is None: 35 | return f"This cell doesn't define any new functions." 36 | 37 | # get the class we're adding it to 38 | namespace = get_ipython().user_ns 39 | class_name = args 40 | cls = namespace.get(class_name, None) 41 | if cls is None: 42 | return f"Class '{class_name}' not found." 43 | 44 | # save the old version of the function if it was already defined 45 | old_func = namespace.get(func_name, None) 46 | if old_func is not None: 47 | del namespace[func_name] 48 | 49 | # Execute the cell to define the function 50 | get_ipython().run_cell(cell) 51 | 52 | # get the newly defined function 53 | new_func = namespace.get(func_name, None) 54 | if new_func is None: 55 | return f"This cell didn't define {func_name}." 56 | 57 | # add the function to the class and remove it from the namespace 58 | setattr(cls, func_name, new_func) 59 | del namespace[func_name] 60 | 61 | # restore the old function to the namespace 62 | if old_func is not None: 63 | namespace[func_name] = old_func 64 | 65 | @register_cell_magic 66 | def expect_error(line, cell): 67 | try: 68 | get_ipython().run_cell(cell) 69 | except Exception as e: 70 | get_ipython().run_cell("%tb") 71 | 72 | @magic_arguments() 73 | @argument("exception", help="Type of exception to catch") 74 | @register_cell_magic 75 | def expect(line, cell): 76 | args = parse_argstring(expect, line) 77 | exception = eval(args.exception) 78 | try: 79 | get_ipython().run_cell(cell) 80 | except exception as e: 81 | get_ipython().run_cell("%tb") 82 | 83 | def traceback(mode): 84 | """Set the traceback mode. 85 | 86 | mode: string 87 | """ 88 | with contextlib.redirect_stdout(io.StringIO()): 89 | get_ipython().run_cell(f"%xmode {mode}") 90 | 91 | traceback("Minimal") 92 | except (ImportError, NameError): 93 | print("Warning: IPython is not available, cell magic not defined.") 94 | --------------------------------------------------------------------------------