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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/jupyturtle_pie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------