From c4b2852f746d67fe688b12c0b22297127bc57e02 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 2 May 2020 17:32:30 -0700 Subject: [PATCH 001/310] Initial commit --- LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..02f66970 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Samuel Huang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..61c25f0d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ultimate-python +Ultimate Python study guide, influenced by ultimate-go From 613aad6b1e3cea30d72ebd1f2d3d3c21207be748 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 2 May 2020 17:33:12 -0700 Subject: [PATCH 002/310] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 61c25f0d..5ba9c214 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# ultimate-python +# Ultimate Python study guide + Ultimate Python study guide, influenced by ultimate-go From 4f6329dc88eab15119c91a9b162085051ef6cbef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 2 May 2020 17:33:31 -0700 Subject: [PATCH 003/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ba9c214..072f5738 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Ultimate Python study guide -Ultimate Python study guide, influenced by ultimate-go +Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go) From 85902420ee27137084461538015325e3fa47d31f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 16:13:41 -0700 Subject: [PATCH 004/310] Create some initial content --- .gitignore | 3 +++ README.md | 4 ++++ ultimatepython/expression.py | 19 +++++++++++++++++++ ultimatepython/variable.py | 15 +++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 .gitignore create mode 100644 ultimatepython/expression.py create mode 100644 ultimatepython/variable.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a5d8a88f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +*.pyc +.DS_Store diff --git a/README.md b/README.md index 072f5738..150df182 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # Ultimate Python study guide Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go) + +- **Language Mechanics** + - Variable: [Built-in types](ultimatepython/variable.py) + - Expression: [Numeric operations](ultimatepython/expression.py) diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py new file mode 100644 index 00000000..866ee5ca --- /dev/null +++ b/ultimatepython/expression.py @@ -0,0 +1,19 @@ +def main(): + # This is a simple integer + x = 1 + + # Its value can used as part of expressions + print(x + 1) + print(x * 2 * 2) + + # Divison is a bit tricky in Python because it returns a result + # of type 'float' by default + print(x / 2) + + # If you want to compute an integer division, then add + # an extra slash to the expression + print(x // 2) + + +if __name__ == "__main__": + main() diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py new file mode 100644 index 00000000..84d060ec --- /dev/null +++ b/ultimatepython/variable.py @@ -0,0 +1,15 @@ +def main(): + # There are a couple different literal types + a = 1 + b = 2.0 + c = True + d = "hello" + + print(a, type(a)) # + print(b, type(b)) # + print(c, type(c)) # + print(d, type(d)) # + + +if __name__ == "__main__": + main() From f95201e8cfe0b5469b3bace1c7471e576fe9b96d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 16:32:21 -0700 Subject: [PATCH 005/310] Ignore .idea files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a5d8a88f..cbf0b2ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ venv/ *.pyc .DS_Store From 441e9aaed46c376a4a94f1a5d745155d0e45ebc0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 16:32:58 -0700 Subject: [PATCH 006/310] Fix typo in expression.py --- ultimatepython/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py index 866ee5ca..ec9ddb85 100644 --- a/ultimatepython/expression.py +++ b/ultimatepython/expression.py @@ -6,7 +6,7 @@ def main(): print(x + 1) print(x * 2 * 2) - # Divison is a bit tricky in Python because it returns a result + # Division is a bit tricky in Python because it returns a result # of type 'float' by default print(x / 2) From 54446b7e008b42a0c497286d3e83d2153fe48c01 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 16:36:14 -0700 Subject: [PATCH 007/310] Fix the first statement up --- ultimatepython/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 84d060ec..887f3bc6 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -1,5 +1,5 @@ def main(): - # There are a couple different literal types + # Here are the main literal types to be aware of a = 1 b = 2.0 c = True From 4ec7fa74dc861f394cdeb830bca49755d9ad0cae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 16:38:53 -0700 Subject: [PATCH 008/310] Revise wording on last statement in expression --- ultimatepython/expression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py index ec9ddb85..805138ed 100644 --- a/ultimatepython/expression.py +++ b/ultimatepython/expression.py @@ -10,8 +10,8 @@ def main(): # of type 'float' by default print(x / 2) - # If you want to compute an integer division, then add - # an extra slash to the expression + # If an integer division is desired, then an extra slash + # must be added to the expression print(x // 2) From e99148276dd78478f783030a5d10ce2ed62f3e09 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 17:37:56 -0700 Subject: [PATCH 009/310] Add more 2s to expression --- ultimatepython/expression.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py index 805138ed..872f064a 100644 --- a/ultimatepython/expression.py +++ b/ultimatepython/expression.py @@ -4,7 +4,9 @@ def main(): # Its value can used as part of expressions print(x + 1) - print(x * 2 * 2) + + # An expression can be chained indefinitely + print(x * 2 * 2 * 2) # Division is a bit tricky in Python because it returns a result # of type 'float' by default From f31946a1a4b6f247b30f0f559da9325b36abed47 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 17:51:36 -0700 Subject: [PATCH 010/310] Add goals and table of contents header --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 150df182..cf1c1218 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ # Ultimate Python study guide -Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go) +Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go). + +## Goals + +Here are the primary goals of creating this guide: + +**Serve as a resource** for newcomers who prefer to learn hands-on. This +repository has a collection of Python files which can be run in an IDE or a +terminal. Most of the lines have comments which help guide a reader +through what the programs are doing. + +**Serve as a pure guide** for those who want to revisit core Python concepts. +As such, only builtin libraries are leveraged; popular open-source libraries +and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. + +## Table Of Contents - **Language Mechanics** - Variable: [Built-in types](ultimatepython/variable.py) From ed86833ff5bdc9d26da4b8cf7ea27f313443364f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 17:54:04 -0700 Subject: [PATCH 011/310] Revise goals content a bit more --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cf1c1218..7ab396e0 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Here are the primary goals of creating this guide: **Serve as a resource** for newcomers who prefer to learn hands-on. This repository has a collection of Python files which can be run in an IDE or a -terminal. Most of the lines have comments which help guide a reader -through what the programs are doing. +terminal. Most lines have carefully crafted comments which guide a reader +through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. As such, only builtin libraries are leveraged; popular open-source libraries From e663708c33b1f01ecf367a6ddb8e38d318a399fd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:13:18 -0700 Subject: [PATCH 012/310] Add conditional lesson --- README.md | 1 + ultimatepython/conditional.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 ultimatepython/conditional.py diff --git a/README.md b/README.md index 7ab396e0..18226b0d 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,4 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - **Language Mechanics** - Variable: [Built-in types](ultimatepython/variable.py) - Expression: [Numeric operations](ultimatepython/expression.py) + - Conditional: [Conditionals](ultimatepython/conditional.py) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py new file mode 100644 index 00000000..8418cca8 --- /dev/null +++ b/ultimatepython/conditional.py @@ -0,0 +1,34 @@ +def main(): + x = 1 + x_add_two = x + 2 + + # Test that the obvious is true + if x_add_two == 3: + print("math wins") # ran + + # Test that a double-negative is true. This + # could also be written as the following: + # ... if x + 1 != 1 + if not x_add_two == 1: + print("math wins here too") # ran + + # There are else statements as well, which get run + # if the initial condition fails + if x_add_two == 1: + print("math lost here...") # skipped + else: + print("math wins otherwise") # ran + + # ...or gets run after multiple conditions fail + if x_add_two == 1: + print("nope not this one...") # skipped + elif x_add_two == 2: + print("nope not this one either...") # skipped + elif x_add_two < 3: + print("nope not quite...") # skipped + else: + print("math wins finally") # ran + + +if __name__ == "__main__": + main() From da87c329e834905627fd2e40bc08884150184f15 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:14:38 -0700 Subject: [PATCH 013/310] Update TOC content --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18226b0d..f1ec61be 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,6 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Table Of Contents - **Language Mechanics** - - Variable: [Built-in types](ultimatepython/variable.py) + - Variable: [Built-in literals](ultimatepython/variable.py) - Expression: [Numeric operations](ultimatepython/expression.py) - - Conditional: [Conditionals](ultimatepython/conditional.py) + - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) From 127f39d99297462dd39ed08293a2c54f73915062 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:16:41 -0700 Subject: [PATCH 014/310] Adjust ToC header --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1ec61be..7cf422d5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ through what the programs are doing step-by-step. As such, only builtin libraries are leveraged; popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. -## Table Of Contents +## Table of Contents - **Language Mechanics** - Variable: [Built-in literals](ultimatepython/variable.py) From ad724cb233c27983cfbfc1d138eab63b534f95b3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:27:29 -0700 Subject: [PATCH 015/310] Add additional resources --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cf422d5..d334d844 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,17 @@ through what the programs are doing step-by-step. As such, only builtin libraries are leveraged; popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. -## Table of Contents +## Table of contents - **Language Mechanics** - Variable: [Built-in literals](ultimatepython/variable.py) - Expression: [Numeric operations](ultimatepython/expression.py) - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) + +## Additional resources + +Here are some repositories to visit after going through the content above: + +- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) +- [faif/python-patterns](https://github.com/faif/python-patterns) +- [geekcomputers/Python](https://github.com/geekcomputers/Python) From f7dbc015d335b07c26b0e82e91f4b859cdb31633 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:45:44 -0700 Subject: [PATCH 016/310] Add more content to README --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d334d844..0553ed92 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ terminal. Most lines have carefully crafted comments which guide a reader through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. -As such, only builtin libraries are leveraged; popular open-source libraries +Only builtin libraries are leveraged so that concepts can be conveyed without +the overhead of domain-specific concepts. As such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Table of contents @@ -24,7 +25,14 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Additional resources -Here are some repositories to visit after going through the content above: +Going through the content above will give you, the reader, a solid +understanding of core Python fundamentals. + +As you start applying Python fundamentals to the real world, +consider reading over best practices and examples in other +well-regarded resources. + +Here are some repositories to get you started: - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) - [faif/python-patterns](https://github.com/faif/python-patterns) From 64504340ac0069fe1702261c14075d393bbf6aa1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 18:46:42 -0700 Subject: [PATCH 017/310] Remove extraneous content --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0553ed92..16cd008d 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,11 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Additional resources -Going through the content above will give you, the reader, a solid -understanding of core Python fundamentals. - As you start applying Python fundamentals to the real world, -consider reading over best practices and examples in other -well-regarded resources. +read over best practices and examples from other well-regarded +resources. -Here are some repositories to get you started: +Here are some repositories to get started: - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) - [faif/python-patterns](https://github.com/faif/python-patterns) From d3ab039ab0241f218aa998fd9e774bed2721cdb9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 19:00:00 -0700 Subject: [PATCH 018/310] Fix typo in conditional --- ultimatepython/conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 8418cca8..0b660bcd 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -8,7 +8,7 @@ def main(): # Test that a double-negative is true. This # could also be written as the following: - # ... if x + 1 != 1 + # ... if x_add_two != 1 if not x_add_two == 1: print("math wins here too") # ran From d7b105a197c671350ab19ac4e94be9e29141a2fe Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 19:02:48 -0700 Subject: [PATCH 019/310] Fixup grammar in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16cd008d..f46a3ac9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ terminal. Most lines have carefully crafted comments which guide a reader through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. -Only builtin libraries are leveraged so that concepts can be conveyed without +Only builtin libraries are leveraged so that these concepts can be conveyed without the overhead of domain-specific concepts. As such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. From 118f27aa999a5c3cecceafee89f5f70ddaac2e7e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 19:36:58 -0700 Subject: [PATCH 020/310] Make conditional more readable --- ultimatepython/conditional.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 0b660bcd..9119a5b0 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -2,30 +2,28 @@ def main(): x = 1 x_add_two = x + 2 - # Test that the obvious is true + # This condition is obviously true if x_add_two == 3: print("math wins") # ran - # Test that a double-negative is true. This - # could also be written as the following: - # ... if x_add_two != 1 + # A negated condition can also be true if not x_add_two == 1: print("math wins here too") # ran # There are else statements as well, which get run # if the initial condition fails if x_add_two == 1: - print("math lost here...") # skipped + print("math lost here...") else: print("math wins otherwise") # ran # ...or gets run after multiple conditions fail if x_add_two == 1: - print("nope not this one...") # skipped + print("nope not this one...") elif x_add_two == 2: - print("nope not this one either...") # skipped - elif x_add_two < 3: - print("nope not quite...") # skipped + print("nope not this one either...") + elif x_add_two < 3 or x_add_two > 3: + print("nope not quite...") else: print("math wins finally") # ran From aab125162455af2a751b4a063aa5ce84ef6a007d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 19:40:41 -0700 Subject: [PATCH 021/310] Fixup comment in conditional --- ultimatepython/conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 9119a5b0..1d0383c7 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -17,7 +17,7 @@ def main(): else: print("math wins otherwise") # ran - # ...or gets run after multiple conditions fail + # Or it gets run after every other condition fails if x_add_two == 1: print("nope not this one...") elif x_add_two == 2: From 8f19fc39674f5d79ce9a652bab6674647cd3a4ee Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 20:42:53 -0700 Subject: [PATCH 022/310] Add loop guide --- README.md | 1 + ultimatepython/loop.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 ultimatepython/loop.py diff --git a/README.md b/README.md index f46a3ac9..9f8e3162 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Variable: [Built-in literals](ultimatepython/variable.py) - Expression: [Numeric operations](ultimatepython/expression.py) - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) + - Loop: [for-loop | while-loop](ultimatepython/loop.py) ## Additional resources diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py new file mode 100644 index 00000000..31017a56 --- /dev/null +++ b/ultimatepython/loop.py @@ -0,0 +1,45 @@ +def main(): + # This is a for loop that iterates on values 0..4 + # and adds each value to total + total = 0 + for i in range(5): + total += i + + # The answer is...10! + print(f"Sum(0..4) = {total}") + + # This is a for loop that iterates on values 5..1 + # and multiplies each value to fib + fib = 1 + for i in range(5, 0, -1): + fib *= i + + # The answer is...120! + print(f"Fibonacci(5..1) = {fib}") + + # This is a simple while loop, similar to a for loop + i = 0 + while i < 5: + print(f"While {i} < 5") + + # Manually increment the counter + i += 2 + + # This is a while loop that is terminated with break + i = 2 + while True: + print(f"Do While {i} < 5") + + # Putting this code block after the print statement + # makes the loop look like a do-while block in other + # programming languages + if i >= 5: + print(f"Break out! {i} is no longer < 5") + break + + # Manually multiply the counter + i *= 2 + + +if __name__ == "__main__": + main() From 8d9abcc5b2a78795408cd4231cb6a56a529dba96 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 20:51:14 -0700 Subject: [PATCH 023/310] Improve the docs in the loop --- ultimatepython/loop.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index 31017a56..89b68250 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -18,14 +18,17 @@ def main(): print(f"Fibonacci(5..1) = {fib}") # This is a simple while loop, similar to a for loop + # except that the counter is declared outside of the + # loop and its state is explicitly managed inside of + # the loop i = 0 while i < 5: print(f"While {i} < 5") - - # Manually increment the counter i += 2 # This is a while loop that is terminated with break + # and its counter is multiplied in the loop, showing + # that you can do anything to the counter i = 2 while True: print(f"Do While {i} < 5") @@ -37,7 +40,6 @@ def main(): print(f"Break out! {i} is no longer < 5") break - # Manually multiply the counter i *= 2 From a286a673915487d9443d58add4da0d82c67115c0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 22:45:22 -0700 Subject: [PATCH 024/310] Using better terms in loop --- ultimatepython/loop.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index 89b68250..c6729cfe 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -20,7 +20,8 @@ def main(): # This is a simple while loop, similar to a for loop # except that the counter is declared outside of the # loop and its state is explicitly managed inside of - # the loop + # the loop. The loop will continue until the counter + # exceeds 5 i = 0 while i < 5: print(f"While {i} < 5") @@ -28,12 +29,14 @@ def main(): # This is a while loop that is terminated with break # and its counter is multiplied in the loop, showing - # that you can do anything to the counter + # that you can do anything to the counter. Like the + # previous while loop, this one also terminates when + # the counter exceeds 5 i = 2 while True: print(f"Do While {i} < 5") - # Putting this code block after the print statement + # Putting this conditional after the print statement # makes the loop look like a do-while block in other # programming languages if i >= 5: From a8532a40bae3526d01718405619f0714eea558c7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 22:49:05 -0700 Subject: [PATCH 025/310] Revise the ordering of do-while --- ultimatepython/loop.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index c6729cfe..a152318c 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -32,19 +32,18 @@ def main(): # that you can do anything to the counter. Like the # previous while loop, this one also terminates when # the counter exceeds 5 - i = 2 + i = 1 while True: print(f"Do While {i} < 5") + i *= 2 # Putting this conditional after the print statement - # makes the loop look like a do-while block in other + # makes the loop look like the do-while loop from other # programming languages if i >= 5: print(f"Break out! {i} is no longer < 5") break - i *= 2 - if __name__ == "__main__": main() From 5f74b5e53a34771419f15f8ed5118e9734989aea Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 23:05:10 -0700 Subject: [PATCH 026/310] Add continue statement in loop --- ultimatepython/loop.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index a152318c..a98fbda7 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -42,7 +42,17 @@ def main(): # programming languages if i >= 5: print(f"Break out! {i} is no longer < 5") + + # The break statement terminates the while loop + # that it's running under break + else: + # The continue statement returns straight + # back to the start of the while loop + continue + + # Skipped because of continue and break + print("I will never get called") if __name__ == "__main__": From f808a12582844054de3d7db491bc32f82e616254 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 23:23:17 -0700 Subject: [PATCH 027/310] Add simple content for function --- README.md | 1 + ultimatepython/function.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 ultimatepython/function.py diff --git a/README.md b/README.md index 9f8e3162..26da08cc 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Expression: [Numeric operations](ultimatepython/expression.py) - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) - Loop: [for-loop | while-loop](ultimatepython/loop.py) + - Function: [Simple functions](ultimatepython/function.py) ## Additional resources diff --git a/ultimatepython/function.py b/ultimatepython/function.py new file mode 100644 index 00000000..e66d75a8 --- /dev/null +++ b/ultimatepython/function.py @@ -0,0 +1,24 @@ +def add(x, y): + """Add two objects together to produce a new object. + + This add function is a function, just like main is. Two + differences between add and main are that: + + A) It accepts input parameters + B) It returns a value + """ + return x + y + + +def main(): + # The add function can be used for numbers as expected + add_result_int = add(1, 2) + print(f"Add(1, 2) = {add_result_int}") + + # The add function can be used for strings as well + add_result_string = add('hello', ' world') + print(f"Add('hello', ' world') = '{add_result_string}'") + + +if __name__ == '__main__': + main() From a4961b0c24ce41cb9cbd249fc1040801bedfe0ad Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 23:49:15 -0700 Subject: [PATCH 028/310] Add a function wrapper --- ultimatepython/function.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ultimatepython/function.py b/ultimatepython/function.py index e66d75a8..a1b12df1 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -10,15 +10,38 @@ def add(x, y): return x + y +def do_until(fn, n): + """Run a function for n times in a row. + + A function is provided as input. This shows how it is + a plain old Python object, just like any other thing + is an object. In fact, everything is an object in + Python. + + We treat the passed-in function like a + piece of data that can be called with an integer. + Therefore, the input function must accept one number + as its argument. + """ + for i in range(n): + fn(i) + + def main(): # The add function can be used for numbers as expected add_result_int = add(1, 2) print(f"Add(1, 2) = {add_result_int}") # The add function can be used for strings as well - add_result_string = add('hello', ' world') + add_result_string = add("hello", " world") print(f"Add('hello', ' world') = '{add_result_string}'") + # Run the same function twice in a row. Notice that + # we make use of lambda definitions to create a + # function that accepts one input and prints + # something to the screen + do_until(lambda n: print(f"hello at {n}"), 2) + if __name__ == '__main__': main() From 619ea289066bc3eeddbc737169f8ff4c94f83ca0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 16 Aug 2020 23:52:18 -0700 Subject: [PATCH 029/310] Fixup README ToC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26da08cc..92d627fe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Expression: [Numeric operations](ultimatepython/expression.py) - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) - Loop: [for-loop | while-loop](ultimatepython/loop.py) - - Function: [Simple functions](ultimatepython/function.py) + - Function: [def | lambda](ultimatepython/function.py) ## Additional resources From 393163e30f19ba622c89799d1a8783c031519e14 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 07:53:18 -0700 Subject: [PATCH 030/310] Add more content to function --- ultimatepython/function.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ultimatepython/function.py b/ultimatepython/function.py index a1b12df1..02d16b2f 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -10,18 +10,16 @@ def add(x, y): return x + y -def do_until(fn, n): - """Run a function for n times in a row. - - A function is provided as input. This shows how it is - a plain old Python object, just like any other thing - is an object. In fact, everything is an object in - Python. - - We treat the passed-in function like a - piece of data that can be called with an integer. - Therefore, the input function must accept one number - as its argument. +def run_until(fn, n): + """Run a function from 0 until n - 1. + + A function is provided as its first input and an integer + as its second input. This demonstrates that anything can + be passed into the function parameters. + + This leads to an important point: everything in Python + is an object. That includes the function that is being + defined here. """ for i in range(n): fn(i) @@ -36,11 +34,14 @@ def main(): add_result_string = add("hello", " world") print(f"Add('hello', ' world') = '{add_result_string}'") - # Run the same function twice in a row. Notice that - # we make use of lambda definitions to create a - # function that accepts one input and prints - # something to the screen - do_until(lambda n: print(f"hello at {n}"), 2) + # Run the input function twice. Notice that we make + # use of lambda to create an anonymous function (i.e. + # a function without a name) that accepts one input + # and does something with it. Anonymous functions + # are powerful because they allow one to write + # functions inline and not have to declare + # them explicitly, unlike add and run_until + run_until(lambda i: print(f"hello at {i}"), 2) if __name__ == '__main__': From d8e39b6bd4e0da483ba5c6ac1eb68b1806a4b579 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:10:40 -0700 Subject: [PATCH 031/310] Improve wording and emphasize objects --- ultimatepython/conditional.py | 2 +- ultimatepython/function.py | 6 +++--- ultimatepython/loop.py | 10 ++++++---- ultimatepython/variable.py | 3 +++ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 1d0383c7..9d0c123e 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -17,7 +17,7 @@ def main(): else: print("math wins otherwise") # ran - # Or it gets run after every other condition fails + # Or they get run once every other condition fails if x_add_two == 1: print("nope not this one...") elif x_add_two == 2: diff --git a/ultimatepython/function.py b/ultimatepython/function.py index 02d16b2f..d0a4bc12 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -17,9 +17,9 @@ def run_until(fn, n): as its second input. This demonstrates that anything can be passed into the function parameters. - This leads to an important point: everything in Python - is an object. That includes the function that is being - defined here. + This leads to an important point that was brought up in + the variable lesson: everything in Python is an object, + and that includes this docstring from run_until. """ for i in range(n): fn(i) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index a98fbda7..36b2d3a3 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -27,10 +27,10 @@ def main(): print(f"While {i} < 5") i += 2 - # This is a while loop that is terminated with break + # This is a while loop that is stopped with break # and its counter is multiplied in the loop, showing # that you can do anything to the counter. Like the - # previous while loop, this one also terminates when + # previous while loop, this one continues until # the counter exceeds 5 i = 1 while True: @@ -43,8 +43,10 @@ def main(): if i >= 5: print(f"Break out! {i} is no longer < 5") - # The break statement terminates the while loop - # that it's running under + # The break statement stops the while loop + # that it's running inside of. If there was + # another loop above it, the break statement + # will not terminate that loop break else: # The continue statement returns straight diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 887f3bc6..4aef8dcf 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -5,6 +5,9 @@ def main(): c = True d = "hello" + # Notice that each type is a 'class'. This leads + # to an important point: in Python, everything + # is an object print(a, type(a)) # print(b, type(b)) # print(c, type(c)) # From e347fc8c73cf98b1a475d2fd994832ca4ed6c3ca Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:32:30 -0700 Subject: [PATCH 032/310] Drill the object concept again --- ultimatepython/function.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ultimatepython/function.py b/ultimatepython/function.py index d0a4bc12..0739e196 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -13,13 +13,12 @@ def add(x, y): def run_until(fn, n): """Run a function from 0 until n - 1. - A function is provided as its first input and an integer - as its second input. This demonstrates that anything can - be passed into the function parameters. + This expects a function to be provided as its first + input and an integer as its second input. - This leads to an important point that was brought up in - the variable lesson: everything in Python is an object, - and that includes this docstring from run_until. + This leads to an important point that was introduced before: + everything in Python is an object, and that includes + this docstring from run_until! """ for i in range(n): fn(i) @@ -39,10 +38,15 @@ def main(): # a function without a name) that accepts one input # and does something with it. Anonymous functions # are powerful because they allow one to write - # functions inline and not have to declare - # them explicitly, unlike add and run_until + # functions inline, unlike add and run_until run_until(lambda i: print(f"hello at {i}"), 2) + # Did you want to see the run_until docstring? Well + # you can with the __doc__ magic attribute! Remember + # this one point - everything in Python is + # an object + print(run_until.__doc__) + if __name__ == '__main__': main() From b9a178244c508c3a74b0ea209e94facebd9f4390 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:41:27 -0700 Subject: [PATCH 033/310] Add more text about classes and instances --- ultimatepython/variable.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 4aef8dcf..ffe7136f 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -5,9 +5,10 @@ def main(): c = True d = "hello" - # Notice that each type is a 'class'. This leads - # to an important point: in Python, everything - # is an object + # Notice that each type is a 'class'. Each variable + # refers to an 'instance' of the class they belong + # to. This leads to an important point: everything + # is an object in Python print(a, type(a)) # print(b, type(b)) # print(c, type(c)) # From 9f312af1b24a1b5c02e45eb2bcf178ad66775851 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:44:44 -0700 Subject: [PATCH 034/310] Fixup first goal in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 92d627fe..4c7aa545 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoan Here are the primary goals of creating this guide: -**Serve as a resource** for newcomers who prefer to learn hands-on. This -repository has a collection of Python files which can be run in an IDE or a -terminal. Most lines have carefully crafted comments which guide a reader -through what the programs are doing step-by-step. +**Serve as a resource** for Python newcomers who prefer to learn hands-on. +This repository has a collection of standalone modules which can be run +in an IDE or a terminal. Most lines have carefully crafted comments which +guide a reader through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without From 32a5e334ccb00226d50406679bb3d7a7056c736d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:46:24 -0700 Subject: [PATCH 035/310] Fix grammar in last sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c7aa545..3fdf3e37 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ As you start applying Python fundamentals to the real world, read over best practices and examples from other well-regarded resources. -Here are some repositories to get started: +Here are some repositories to get started with: - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) - [faif/python-patterns](https://github.com/faif/python-patterns) From ec5201ea219e8540c843efbc2636dcf267ecbfec Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:50:42 -0700 Subject: [PATCH 036/310] Adjust the wording in loop --- ultimatepython/loop.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index 36b2d3a3..ecb945c7 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -43,14 +43,13 @@ def main(): if i >= 5: print(f"Break out! {i} is no longer < 5") - # The break statement stops the while loop - # that it's running inside of. If there was - # another loop above it, the break statement - # will not terminate that loop + # The break statement stops the current while loop. + # If this while loop was nested in another loop, + # this statement would not stop the parent loop break else: - # The continue statement returns straight - # back to the start of the while loop + # The continue statement returns to the + # start of the current while loop continue # Skipped because of continue and break From dd4e7e6c0b13f33f06c692d968474c63ce9ec82a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 17 Aug 2020 08:56:43 -0700 Subject: [PATCH 037/310] Fix grammar in variable --- ultimatepython/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index ffe7136f..a0dedb94 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -6,7 +6,7 @@ def main(): d = "hello" # Notice that each type is a 'class'. Each variable - # refers to an 'instance' of the class they belong + # refers to an 'instance' of the class it belongs # to. This leads to an important point: everything # is an object in Python print(a, type(a)) # From 97c09f32999035f76e6700fc60da98cff6b4767c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 07:27:07 -0700 Subject: [PATCH 038/310] Comment out skipped line for PyCharm --- ultimatepython/loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/loop.py b/ultimatepython/loop.py index ecb945c7..f4673b77 100644 --- a/ultimatepython/loop.py +++ b/ultimatepython/loop.py @@ -53,7 +53,7 @@ def main(): continue # Skipped because of continue and break - print("I will never get called") + # print("I will never get called") if __name__ == "__main__": From 06199f91b4682b82ef21925358e9eb4d1f107bfe Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 07:50:08 -0700 Subject: [PATCH 039/310] Add more content to variable --- ultimatepython/variable.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index a0dedb94..6232ebbf 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -6,13 +6,18 @@ def main(): d = "hello" # Notice that each type is a 'class'. Each variable - # refers to an 'instance' of the class it belongs - # to. This leads to an important point: everything + # refers to an 'instance' of the class it belongs to + a_type = type(a) # + b_type = type(b) # + c_type = type(c) # + d_type = type(d) # + + # This leads to an important point: everything # is an object in Python - print(a, type(a)) # - print(b, type(b)) # - print(c, type(c)) # - print(d, type(d)) # + print(isinstance(a, object) and issubclass(a_type, object)) + print(isinstance(b, object) and issubclass(b_type, object)) + print(isinstance(c, object) and issubclass(c_type, object)) + print(isinstance(d, object) and issubclass(d_type, object)) if __name__ == "__main__": From f48aac14f0a40d85e65ebe5d8e590fb039488485 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 07:55:55 -0700 Subject: [PATCH 040/310] Expand on the object theme in variable --- ultimatepython/variable.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 6232ebbf..0378e228 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -14,10 +14,17 @@ def main(): # This leads to an important point: everything # is an object in Python - print(isinstance(a, object) and issubclass(a_type, object)) - print(isinstance(b, object) and issubclass(b_type, object)) - print(isinstance(c, object) and issubclass(c_type, object)) - print(isinstance(d, object) and issubclass(d_type, object)) + a_is_obj = isinstance(a, object) and issubclass(a_type, object) + b_is_obj = isinstance(b, object) and issubclass(b_type, object) + c_is_obj = isinstance(c, object) and issubclass(c_type, object) + d_is_obj = isinstance(d, object) and issubclass(d_type, object) + + # Here is a summary via the print function. Notice + # that we print more than one variable at a time + print(a, a_type, a_is_obj) + print(b, b_type, b_is_obj) + print(c, c_type, c_is_obj) + print(d, d_type, d_is_obj) if __name__ == "__main__": From 81838cde8ab789a1b37a79f4161bf19693aa0192 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:03:11 -0700 Subject: [PATCH 041/310] Add more comments to conditional --- ultimatepython/conditional.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 9d0c123e..f725f8a4 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -4,28 +4,30 @@ def main(): # This condition is obviously true if x_add_two == 3: - print("math wins") # ran + print("math wins") # run # A negated condition can also be true if not x_add_two == 1: - print("math wins here too") # ran + print("math wins here too") # run # There are else statements as well, which get run - # if the initial condition fails + # if the initial condition fails. Notice that the + # line in the first if block is skipped if x_add_two == 1: - print("math lost here...") + print("math lost here...") # skip else: - print("math wins otherwise") # ran + print("math wins otherwise") # run - # Or they get run once every other condition fails + # Or they get run once every other condition fails. + # Notice that multiple lines get skipped if x_add_two == 1: - print("nope not this one...") + print("nope not this one...") # skip elif x_add_two == 2: - print("nope not this one either...") + print("nope not this one either...") # skip elif x_add_two < 3 or x_add_two > 3: - print("nope not quite...") + print("nope not quite...") # skip else: - print("math wins finally") # ran + print("math wins finally") # run if __name__ == "__main__": From deeded809e9cbda2451d6f3ce100a54e773ccfac Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:06:29 -0700 Subject: [PATCH 042/310] Add content on variable again --- ultimatepython/variable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 0378e228..549cd474 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -5,8 +5,9 @@ def main(): c = True d = "hello" - # Notice that each type is a 'class'. Each variable - # refers to an 'instance' of the class it belongs to + # Notice that each type is a 'class'. Each of the + # variables above refers to an 'instance' of the + # class it belongs to a_type = type(a) # b_type = type(b) # c_type = type(c) # From 4ef813658bda33d1f152fcbc20d675d0c040a67a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:14:05 -0700 Subject: [PATCH 043/310] Add deeper content in expression --- ultimatepython/expression.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py index 872f064a..76a6c14b 100644 --- a/ultimatepython/expression.py +++ b/ultimatepython/expression.py @@ -5,7 +5,10 @@ def main(): # Its value can used as part of expressions print(x + 1) - # An expression can be chained indefinitely + # An expression can be chained indefinitely. This concept + # of chaining expressions is powerful because it allows + # you to compose simple pieces of code into larger pieces + # of code over time print(x * 2 * 2 * 2) # Division is a bit tricky in Python because it returns a result @@ -16,6 +19,12 @@ def main(): # must be added to the expression print(x // 2) + # Powers of an integer can be leveraged too. If you want + # more math functionality, then you may have to leverage + # the builtin 'math' library, a third-party library or + # your own library + print(x * 2 ** 3) + if __name__ == "__main__": main() From e80009da1db5817fa0713aaf658ee8f628bfa9fc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:25:43 -0700 Subject: [PATCH 044/310] Add concept of fitting in conditional --- ultimatepython/conditional.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index f725f8a4..7446bfd5 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -11,15 +11,20 @@ def main(): print("math wins here too") # run # There are else statements as well, which get run - # if the initial condition fails. Notice that the - # line in the first if block is skipped + # if the initial condition fails. Notice that one + # line gets skipped, and that this conditional + # does not help you make a conclusive assessment + # on the variable's true value if x_add_two == 1: print("math lost here...") # skip else: print("math wins otherwise") # run - # Or they get run once every other condition fails. - # Notice that multiple lines get skipped + # Else statements also get run once every other + # if and elif condition fails. Notice that multiple + # lines get skipped, and that all of the conditions + # could have been compressed to 'x_add_two != 3' + # for simplicity. In this case, less is more if x_add_two == 1: print("nope not this one...") # skip elif x_add_two == 2: From 0a3d2ca1b1ddabad058c18cfaf755f2d7203593b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:30:45 -0700 Subject: [PATCH 045/310] Add more in function --- ultimatepython/function.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ultimatepython/function.py b/ultimatepython/function.py index 0739e196..fc6a1a70 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -1,8 +1,7 @@ def add(x, y): """Add two objects together to produce a new object. - This add function is a function, just like main is. Two - differences between add and main are that: + Two differences between add and main are that: A) It accepts input parameters B) It returns a value @@ -14,11 +13,13 @@ def run_until(fn, n): """Run a function from 0 until n - 1. This expects a function to be provided as its first - input and an integer as its second input. + input and an integer as its second input. Unlike add, + run_until does NOT return a value. - This leads to an important point that was introduced before: + The fact that a function can be passed into run_until + highlights a core concept that was mentioned before: everything in Python is an object, and that includes - this docstring from run_until! + the docstring you are reading right now! """ for i in range(n): fn(i) From 47d681c243116fe38a4a28a08f0a692213645db8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:32:48 -0700 Subject: [PATCH 046/310] Add backtick around code snippets in comments --- ultimatepython/conditional.py | 4 ++-- ultimatepython/expression.py | 2 +- ultimatepython/function.py | 2 +- ultimatepython/variable.py | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index 7446bfd5..e5b252b6 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -21,9 +21,9 @@ def main(): print("math wins otherwise") # run # Else statements also get run once every other - # if and elif condition fails. Notice that multiple + # `if` and `elif` condition fails. Notice that multiple # lines get skipped, and that all of the conditions - # could have been compressed to 'x_add_two != 3' + # could have been compressed to `x_add_two != 3` # for simplicity. In this case, less is more if x_add_two == 1: print("nope not this one...") # skip diff --git a/ultimatepython/expression.py b/ultimatepython/expression.py index 76a6c14b..1d029daf 100644 --- a/ultimatepython/expression.py +++ b/ultimatepython/expression.py @@ -21,7 +21,7 @@ def main(): # Powers of an integer can be leveraged too. If you want # more math functionality, then you may have to leverage - # the builtin 'math' library, a third-party library or + # the builtin `math` library, a third-party library or # your own library print(x * 2 ** 3) diff --git a/ultimatepython/function.py b/ultimatepython/function.py index fc6a1a70..57230448 100644 --- a/ultimatepython/function.py +++ b/ultimatepython/function.py @@ -43,7 +43,7 @@ def main(): run_until(lambda i: print(f"hello at {i}"), 2) # Did you want to see the run_until docstring? Well - # you can with the __doc__ magic attribute! Remember + # you can with the `__doc__` magic attribute! Remember # this one point - everything in Python is # an object print(run_until.__doc__) diff --git a/ultimatepython/variable.py b/ultimatepython/variable.py index 549cd474..f38e8c6e 100644 --- a/ultimatepython/variable.py +++ b/ultimatepython/variable.py @@ -5,8 +5,8 @@ def main(): c = True d = "hello" - # Notice that each type is a 'class'. Each of the - # variables above refers to an 'instance' of the + # Notice that each type is a `class`. Each of the + # variables above refers to an `instance` of the # class it belongs to a_type = type(a) # b_type = type(b) # @@ -20,7 +20,7 @@ def main(): c_is_obj = isinstance(c, object) and issubclass(c_type, object) d_is_obj = isinstance(d, object) and issubclass(d_type, object) - # Here is a summary via the print function. Notice + # Here is a summary via the `print` function. Notice # that we print more than one variable at a time print(a, a_type, a_is_obj) print(b, b_type, b_is_obj) From a5bc88e0cc45f09099e914ed8f5912878666b58b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:41:52 -0700 Subject: [PATCH 047/310] Add to conditional --- ultimatepython/conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/conditional.py b/ultimatepython/conditional.py index e5b252b6..ef45843d 100644 --- a/ultimatepython/conditional.py +++ b/ultimatepython/conditional.py @@ -10,7 +10,7 @@ def main(): if not x_add_two == 1: print("math wins here too") # run - # There are else statements as well, which get run + # There are `else` statements as well, which run # if the initial condition fails. Notice that one # line gets skipped, and that this conditional # does not help you make a conclusive assessment @@ -20,7 +20,7 @@ def main(): else: print("math wins otherwise") # run - # Else statements also get run once every other + # The `else` statement also run once every other # `if` and `elif` condition fails. Notice that multiple # lines get skipped, and that all of the conditions # could have been compressed to `x_add_two != 3` From ef347012cfd89a3c2f8aba5e99053e1219a703d3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 08:56:44 -0700 Subject: [PATCH 048/310] Add new notes on data_structures --- README.md | 1 + ultimatepython/data_structures.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 ultimatepython/data_structures.py diff --git a/README.md b/README.md index 3fdf3e37..64ca8b47 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) - Loop: [for-loop | while-loop](ultimatepython/loop.py) - Function: [def | lambda](ultimatepython/function.py) + - Data Structures: [list](ultimatepython/data_structures.py) ## Additional resources diff --git a/ultimatepython/data_structures.py b/ultimatepython/data_structures.py new file mode 100644 index 00000000..7a138fa4 --- /dev/null +++ b/ultimatepython/data_structures.py @@ -0,0 +1,21 @@ +def main(): + # This is a list of one-letter strings where + # 'a' is a string at index 0 and + # 'e' is a string at index 4 + letters = ["a", "b", "c", "d", "e"] + + # This is a list of integers where + # 1 is an integer at index 0 + # 5 is an integer at index 4 + numbers = [1, 2, 3, 4, 5] + + # Print letters and numbers side-by-side. Notice + # that we pair the letter at index 0 with the + # number at index 0, and do the same for the rest + # of the indices + for letter, number in zip(letters, numbers): + print(letter, number) + + +if __name__ == "__main__": + main() From 60b82d5f8ab533cec3b0221136d3df5035239813 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:07:12 -0700 Subject: [PATCH 049/310] Refactor structure of ToC and files --- README.md | 14 +++++----- ultimatepython/data_structures.py | 21 --------------- ultimatepython/data_structures/list.py | 30 ++++++++++++++++++++++ ultimatepython/{ => syntax}/conditional.py | 0 ultimatepython/{ => syntax}/expression.py | 0 ultimatepython/{ => syntax}/function.py | 0 ultimatepython/{ => syntax}/loop.py | 0 ultimatepython/{ => syntax}/variable.py | 0 8 files changed, 38 insertions(+), 27 deletions(-) delete mode 100644 ultimatepython/data_structures.py create mode 100644 ultimatepython/data_structures/list.py rename ultimatepython/{ => syntax}/conditional.py (100%) rename ultimatepython/{ => syntax}/expression.py (100%) rename ultimatepython/{ => syntax}/function.py (100%) rename ultimatepython/{ => syntax}/loop.py (100%) rename ultimatepython/{ => syntax}/variable.py (100%) diff --git a/README.md b/README.md index 64ca8b47..6ebddaf5 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,14 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Table of contents - **Language Mechanics** - - Variable: [Built-in literals](ultimatepython/variable.py) - - Expression: [Numeric operations](ultimatepython/expression.py) - - Conditional: [if | if-else | if-elif-else](ultimatepython/conditional.py) - - Loop: [for-loop | while-loop](ultimatepython/loop.py) - - Function: [def | lambda](ultimatepython/function.py) - - Data Structures: [list](ultimatepython/data_structures.py) + - **Syntax** + - Variable: [Built-in literals](ultimatepython/syntax/variable.py) + - Expression: [Numeric operations](ultimatepython/syntax/expression.py) + - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) + - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) + - Function: [def | lambda](ultimatepython/syntax/function.py) + - **Data Structures** + - List: [list | zip | enumerate](ultimatepython/data_structures/list.py) ## Additional resources diff --git a/ultimatepython/data_structures.py b/ultimatepython/data_structures.py deleted file mode 100644 index 7a138fa4..00000000 --- a/ultimatepython/data_structures.py +++ /dev/null @@ -1,21 +0,0 @@ -def main(): - # This is a list of one-letter strings where - # 'a' is a string at index 0 and - # 'e' is a string at index 4 - letters = ["a", "b", "c", "d", "e"] - - # This is a list of integers where - # 1 is an integer at index 0 - # 5 is an integer at index 4 - numbers = [1, 2, 3, 4, 5] - - # Print letters and numbers side-by-side. Notice - # that we pair the letter at index 0 with the - # number at index 0, and do the same for the rest - # of the indices - for letter, number in zip(letters, numbers): - print(letter, number) - - -if __name__ == "__main__": - main() diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py new file mode 100644 index 00000000..9815c491 --- /dev/null +++ b/ultimatepython/data_structures/list.py @@ -0,0 +1,30 @@ +def main(): + # This is a list of one-letter strings where + # 'a' is a string at index 0 and + # 'e' is a string at index 4 + letters = ["a", "b", "c", "d", "e"] + + # This is a list of integers where + # 1 is an integer at index 0 + # 5 is an integer at index 4 + numbers = [1, 2, 3, 4, 5] + + # Print letters and numbers side-by-side using the `zip` + # function. Notice that we pair the letter at index 0 + # with the number at index 0, and do the same for the + # remaining indices + for letter, number in zip(letters, numbers): + print("letters_and_numbers", letter, number) + + # The for loop above worked well because the length + # of `letters` and `numbers` are equal + assert len(letters) == len(numbers) + + # If you want to the see the indices of a list + # you can run `enumerate` - as you may understand + for index, number in enumerate(numbers): + print("numbers", index, number) + + +if __name__ == "__main__": + main() diff --git a/ultimatepython/conditional.py b/ultimatepython/syntax/conditional.py similarity index 100% rename from ultimatepython/conditional.py rename to ultimatepython/syntax/conditional.py diff --git a/ultimatepython/expression.py b/ultimatepython/syntax/expression.py similarity index 100% rename from ultimatepython/expression.py rename to ultimatepython/syntax/expression.py diff --git a/ultimatepython/function.py b/ultimatepython/syntax/function.py similarity index 100% rename from ultimatepython/function.py rename to ultimatepython/syntax/function.py diff --git a/ultimatepython/loop.py b/ultimatepython/syntax/loop.py similarity index 100% rename from ultimatepython/loop.py rename to ultimatepython/syntax/loop.py diff --git a/ultimatepython/variable.py b/ultimatepython/syntax/variable.py similarity index 100% rename from ultimatepython/variable.py rename to ultimatepython/syntax/variable.py From e418b6bfe8ce420d1db07494077a35a6da53efdc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:16:48 -0700 Subject: [PATCH 050/310] Adjust grammar in list.py --- ultimatepython/data_structures/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 9815c491..1b120b78 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -16,7 +16,7 @@ def main(): for letter, number in zip(letters, numbers): print("letters_and_numbers", letter, number) - # The for loop above worked well because the length + # The for loop above worked well because the lengths # of `letters` and `numbers` are equal assert len(letters) == len(numbers) From 6baef109faf2e064828f881a62c1689f3462ceda Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:18:39 -0700 Subject: [PATCH 051/310] Refine list content for enumerate --- ultimatepython/data_structures/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 1b120b78..9131e071 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -20,8 +20,8 @@ def main(): # of `letters` and `numbers` are equal assert len(letters) == len(numbers) - # If you want to the see the indices of a list - # you can run `enumerate` - as you may understand + # If you want to the see the indices and values of a + # list side-by-side, you can use `enumerate` for index, number in enumerate(numbers): print("numbers", index, number) From c03137dfe0dcf5d59994783f7dc712e438f24332 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:30:15 -0700 Subject: [PATCH 052/310] Update list content with mutations --- README.md | 2 +- ultimatepython/data_structures/list.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ebddaf5..659a54c1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) - Function: [def | lambda](ultimatepython/syntax/function.py) - **Data Structures** - - List: [list | zip | enumerate](ultimatepython/data_structures/list.py) + - List: [list | zip | len | enumerate](ultimatepython/data_structures/list.py) ## Additional resources diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 9131e071..356a1bd5 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -25,6 +25,21 @@ def main(): for index, number in enumerate(numbers): print("numbers", index, number) + # Lists can be nested at arbitrary levels + matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + print("matrix", matrix) + + # Another to know about lists is that they are mutable + mutable = [] + for _ in range(5): # [0, ..., 0] + mutable.append(0) + mutable.pop() # pop out the fifth zero + mutable[0] = 100 # first item + mutable[1] = 4 # second item + mutable[-1] = 30 # last item + mutable[-2] = 50 # second to last item + print("mutable", mutable) + if __name__ == "__main__": main() From a9308950ccac4b6a7d1440bee545292cc711d9db Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:38:29 -0700 Subject: [PATCH 053/310] Fixup print statements in list --- ultimatepython/data_structures/list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 356a1bd5..d5ff95e6 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -14,7 +14,7 @@ def main(): # with the number at index 0, and do the same for the # remaining indices for letter, number in zip(letters, numbers): - print("letters_and_numbers", letter, number) + print("letter_and_number", letter, number) # The for loop above worked well because the lengths # of `letters` and `numbers` are equal @@ -23,13 +23,13 @@ def main(): # If you want to the see the indices and values of a # list side-by-side, you can use `enumerate` for index, number in enumerate(numbers): - print("numbers", index, number) + print("number", index, number) # Lists can be nested at arbitrary levels matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print("matrix", matrix) - # Another to know about lists is that they are mutable + # Something to know about lists is that they are mutable mutable = [] for _ in range(5): # [0, ..., 0] mutable.append(0) From 7c4a5faf7664115ca4b492b07c6a3c394dc6b8c9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:50:55 -0700 Subject: [PATCH 054/310] Expand content for left-to-right reading --- ultimatepython/data_structures/list.py | 12 ++++++------ ultimatepython/syntax/conditional.py | 15 ++++++--------- ultimatepython/syntax/expression.py | 14 ++++++-------- ultimatepython/syntax/function.py | 18 ++++++++---------- ultimatepython/syntax/loop.py | 19 ++++++++----------- ultimatepython/syntax/variable.py | 12 +++++------- 6 files changed, 39 insertions(+), 51 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index d5ff95e6..d09f6105 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -9,15 +9,15 @@ def main(): # 5 is an integer at index 4 numbers = [1, 2, 3, 4, 5] - # Print letters and numbers side-by-side using the `zip` - # function. Notice that we pair the letter at index 0 - # with the number at index 0, and do the same for the - # remaining indices + # Print letters and numbers side-by-side using the `zip` function. Notice + # that we pair the letter at index 0 with the number at index 0, and + # do the same for the remaining indices for letter, number in zip(letters, numbers): print("letter_and_number", letter, number) - # The for loop above worked well because the lengths - # of `letters` and `numbers` are equal + # The for loop worked because the lengths of both lists are equal. Notice + # that we use the `assert` keyword to enforce that the statement + # must be true assert len(letters) == len(numbers) # If you want to the see the indices and values of a diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index ef45843d..139ed127 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -10,20 +10,17 @@ def main(): if not x_add_two == 1: print("math wins here too") # run - # There are `else` statements as well, which run - # if the initial condition fails. Notice that one - # line gets skipped, and that this conditional - # does not help you make a conclusive assessment - # on the variable's true value + # There are `else` statements as well, which run if the initial condition + # fails. Notice that one line gets skipped, and that this conditional + # does not help one make a conclusion on the variable's true value if x_add_two == 1: print("math lost here...") # skip else: print("math wins otherwise") # run - # The `else` statement also run once every other - # `if` and `elif` condition fails. Notice that multiple - # lines get skipped, and that all of the conditions - # could have been compressed to `x_add_two != 3` + # The `else` statement also run once every other `if` and `elif` condition + # fails. Notice that multiple lines get skipped, and that all of the + # conditions could have been compressed to `x_add_two != 3` # for simplicity. In this case, less is more if x_add_two == 1: print("nope not this one...") # skip diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 1d029daf..c94d9e22 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -5,10 +5,9 @@ def main(): # Its value can used as part of expressions print(x + 1) - # An expression can be chained indefinitely. This concept - # of chaining expressions is powerful because it allows - # you to compose simple pieces of code into larger pieces - # of code over time + # An expression can be chained indefinitely. This concept of chaining + # expressions is powerful because it allows you to compose simple pieces + # of code into larger pieces of code over time print(x * 2 * 2 * 2) # Division is a bit tricky in Python because it returns a result @@ -19,10 +18,9 @@ def main(): # must be added to the expression print(x // 2) - # Powers of an integer can be leveraged too. If you want - # more math functionality, then you may have to leverage - # the builtin `math` library, a third-party library or - # your own library + # Powers of an integer can be leveraged too. If you want more math + # features, then you will have to leverage the builtin `math` library, + # a third-party library or your own library print(x * 2 ** 3) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index 57230448..43ce072d 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -34,18 +34,16 @@ def main(): add_result_string = add("hello", " world") print(f"Add('hello', ' world') = '{add_result_string}'") - # Run the input function twice. Notice that we make - # use of lambda to create an anonymous function (i.e. - # a function without a name) that accepts one input - # and does something with it. Anonymous functions - # are powerful because they allow one to write - # functions inline, unlike add and run_until + # Run the input function twice. Notice that we make use of lambda to + # create an anonymous function (i.e. a function without a name) that + # accepts one input and does something with it. Anonymous functions + # are powerful because they allow one to write functions inline, unlike + # add and run_until run_until(lambda i: print(f"hello at {i}"), 2) - # Did you want to see the run_until docstring? Well - # you can with the `__doc__` magic attribute! Remember - # this one point - everything in Python is - # an object + # Did you want to see the run_until docstring? Well you can with the + # `__doc__` magic attribute! Remember this one point - everything in + # Python is an object print(run_until.__doc__) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index f4673b77..12cb7390 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -17,29 +17,26 @@ def main(): # The answer is...120! print(f"Fibonacci(5..1) = {fib}") - # This is a simple while loop, similar to a for loop - # except that the counter is declared outside of the - # loop and its state is explicitly managed inside of - # the loop. The loop will continue until the counter + # This is a simple while loop, similar to a for loop except that the + # counter is declared outside of the loop and its state is explicitly + # managed inside of the loop. The loop will continue until the counter # exceeds 5 i = 0 while i < 5: print(f"While {i} < 5") i += 2 - # This is a while loop that is stopped with break - # and its counter is multiplied in the loop, showing - # that you can do anything to the counter. Like the - # previous while loop, this one continues until + # This is a while loop that is stopped with break and its counter is + # multiplied in the loop, showing that you can do anything to the + # counter. Like the previous while loop, this one continues until # the counter exceeds 5 i = 1 while True: print(f"Do While {i} < 5") i *= 2 - # Putting this conditional after the print statement - # makes the loop look like the do-while loop from other - # programming languages + # Putting this conditional after the print statement makes the loop + # look like the do-while loop from other programming languages if i >= 5: print(f"Break out! {i} is no longer < 5") diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index f38e8c6e..cfda2281 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -5,23 +5,21 @@ def main(): c = True d = "hello" - # Notice that each type is a `class`. Each of the - # variables above refers to an `instance` of the - # class it belongs to + # Notice that each type is a `class`. Each of the variables above refers + # to an `instance` of the class it belongs to a_type = type(a) # b_type = type(b) # c_type = type(c) # d_type = type(d) # - # This leads to an important point: everything - # is an object in Python + # This leads to an important point: everything is an object in Python a_is_obj = isinstance(a, object) and issubclass(a_type, object) b_is_obj = isinstance(b, object) and issubclass(b_type, object) c_is_obj = isinstance(c, object) and issubclass(c_type, object) d_is_obj = isinstance(d, object) and issubclass(d_type, object) - # Here is a summary via the `print` function. Notice - # that we print more than one variable at a time + # Here is a summary via the `print` function. Notice that we print more + # than one variable at a time print(a, a_type, a_is_obj) print(b, b_type, b_is_obj) print(c, c_type, c_is_obj) From 31c852673574eeaa22d1a56e670811a0c0709777 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:52:38 -0700 Subject: [PATCH 055/310] Expand function docstring --- ultimatepython/syntax/function.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index 43ce072d..dab98723 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -12,14 +12,12 @@ def add(x, y): def run_until(fn, n): """Run a function from 0 until n - 1. - This expects a function to be provided as its first - input and an integer as its second input. Unlike add, - run_until does NOT return a value. - - The fact that a function can be passed into run_until - highlights a core concept that was mentioned before: - everything in Python is an object, and that includes - the docstring you are reading right now! + This expects a function to be provided as its first input and an integer + as its second input. Unlike add, run_until does NOT return a value. + + The fact that a function can be passed into run_until highlights a core + concept that was mentioned before: everything in Python is an object, and + that includes the docstring you are reading right now! """ for i in range(n): fn(i) From 6f662b9cb3b68dcb6ad35e2785a64c48780ab21b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 09:54:19 -0700 Subject: [PATCH 056/310] Use backtick in docstrings --- ultimatepython/syntax/function.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index dab98723..69a4854c 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -1,7 +1,7 @@ def add(x, y): """Add two objects together to produce a new object. - Two differences between add and main are that: + Two differences between `add` and `main` are that: A) It accepts input parameters B) It returns a value @@ -13,9 +13,9 @@ def run_until(fn, n): """Run a function from 0 until n - 1. This expects a function to be provided as its first input and an integer - as its second input. Unlike add, run_until does NOT return a value. + as its second input. Unlike `add`, `run_until` does NOT return a value. - The fact that a function can be passed into run_until highlights a core + The fact that a function can be passed into `run_until` highlights a core concept that was mentioned before: everything in Python is an object, and that includes the docstring you are reading right now! """ From 96be72e4b6d76b6b85df68c9cc8eef1901356f08 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 15:19:14 -0700 Subject: [PATCH 057/310] Update function.py --- ultimatepython/syntax/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index 69a4854c..9a87563b 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -39,7 +39,7 @@ def main(): # add and run_until run_until(lambda i: print(f"hello at {i}"), 2) - # Did you want to see the run_until docstring? Well you can with the + # Did you want to see the `run_until` docstring? Well you can with the # `__doc__` magic attribute! Remember this one point - everything in # Python is an object print(run_until.__doc__) From aa992a8c118fcc4faa1a4fcfd46c96f07bebe27a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 15:21:11 -0700 Subject: [PATCH 058/310] Update loop.py --- ultimatepython/syntax/loop.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 12cb7390..2f33730e 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -1,6 +1,6 @@ def main(): - # This is a for loop that iterates on values 0..4 - # and adds each value to total + # This is a `for` loop that iterates on values 0..4 + # and adds each value to `total` total = 0 for i in range(5): total += i @@ -8,8 +8,8 @@ def main(): # The answer is...10! print(f"Sum(0..4) = {total}") - # This is a for loop that iterates on values 5..1 - # and multiplies each value to fib + # This is a `for` loop that iterates on values 5..1 + # and multiplies each value to `fib` fib = 1 for i in range(5, 0, -1): fib *= i @@ -17,7 +17,7 @@ def main(): # The answer is...120! print(f"Fibonacci(5..1) = {fib}") - # This is a simple while loop, similar to a for loop except that the + # This is a simple `while` loop, similar to a `for` loop except that the # counter is declared outside of the loop and its state is explicitly # managed inside of the loop. The loop will continue until the counter # exceeds 5 @@ -26,9 +26,9 @@ def main(): print(f"While {i} < 5") i += 2 - # This is a while loop that is stopped with break and its counter is + # This is a `while` loop that is stopped with `break` and its counter is # multiplied in the loop, showing that you can do anything to the - # counter. Like the previous while loop, this one continues until + # counter. Like the previous `while` loop, this one continues until # the counter exceeds 5 i = 1 while True: @@ -40,16 +40,16 @@ def main(): if i >= 5: print(f"Break out! {i} is no longer < 5") - # The break statement stops the current while loop. - # If this while loop was nested in another loop, + # The `break` statement stops the current while loop. + # If this `while` loop was nested in another loop, # this statement would not stop the parent loop break else: - # The continue statement returns to the + # The `continue` statement returns to the # start of the current while loop continue - # Skipped because of continue and break + # Skipped because of `continue` and `break` # print("I will never get called") From 03ecdc1f9b8c1161a28453a16dea444d623b47b8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 18 Aug 2020 15:24:02 -0700 Subject: [PATCH 059/310] Update list.py --- ultimatepython/data_structures/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index d09f6105..ca9207ea 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -1,7 +1,7 @@ def main(): # This is a list of one-letter strings where - # 'a' is a string at index 0 and - # 'e' is a string at index 4 + # "a" is a string at index 0 and + # "e" is a string at index 4 letters = ["a", "b", "c", "d", "e"] # This is a list of integers where From eb87725d6e36389732e743e33e565371ef535ce7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 19 Aug 2020 18:16:56 -0700 Subject: [PATCH 060/310] Update list.py --- ultimatepython/data_structures/list.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index ca9207ea..09d5ee67 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -29,6 +29,12 @@ def main(): matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print("matrix", matrix) + # This matrix just so happens to be a square so the + # the length of each rows is the same as the number of + # rows in the matrix + for row in matrix: + assert len(matrix) == len(row) + # Something to know about lists is that they are mutable mutable = [] for _ in range(5): # [0, ..., 0] From ad76786ffdc8a23c742acb6e99022fbaeb7f2103 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 19 Aug 2020 23:31:04 -0700 Subject: [PATCH 061/310] Revise representation of list --- ultimatepython/data_structures/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 09d5ee67..d2dfb94d 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -37,7 +37,7 @@ def main(): # Something to know about lists is that they are mutable mutable = [] - for _ in range(5): # [0, ..., 0] + for _ in range(5): # [0, 0, 0, 0, 0] mutable.append(0) mutable.pop() # pop out the fifth zero mutable[0] = 100 # first item From 3afac72a168945e148df89d663e4cc6cf1155976 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 19 Aug 2020 23:46:29 -0700 Subject: [PATCH 062/310] Show multiple types of values in list --- ultimatepython/data_structures/list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index d2dfb94d..6b44f83f 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -41,9 +41,9 @@ def main(): mutable.append(0) mutable.pop() # pop out the fifth zero mutable[0] = 100 # first item - mutable[1] = 4 # second item - mutable[-1] = 30 # last item - mutable[-2] = 50 # second to last item + mutable[1] = 'hello' # second item + mutable[-1] = True # last item + mutable[-2] = 5.0 # second to last item print("mutable", mutable) From 8ab36bfc202f73b0affe029cf0871bf9a3170bd2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 19 Aug 2020 23:47:23 -0700 Subject: [PATCH 063/310] Create new lesson on tuple --- README.md | 1 + ultimatepython/data_structures/tuple.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 ultimatepython/data_structures/tuple.py diff --git a/README.md b/README.md index 659a54c1..9ca00b3a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Function: [def | lambda](ultimatepython/syntax/function.py) - **Data Structures** - List: [list | zip | len | enumerate](ultimatepython/data_structures/list.py) + - Tuple: [tuple](ultimatepython/data_structures/tuple.py) ## Additional resources diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py new file mode 100644 index 00000000..f114e840 --- /dev/null +++ b/ultimatepython/data_structures/tuple.py @@ -0,0 +1,22 @@ +def main(): + # This is a tuple of integers + immutable = (1, 2, 3, 4) + print(immutable) + + # It can be indexed like a list + assert immutable[0] == 1 + + # It can be iterated over like a list + for number in immutable: + print("immutable", number) + + # But its contents cannot be changed. As an alternative, you can + # create new tuples from existing tuples + bigger_immutable = immutable + (5, 6) + print(bigger_immutable) + smaller_immutable = immutable[0:2] + print(smaller_immutable) + + +if __name__ == '__main__': + main() From 8f15440846b9cb5e736d8e1390f7c181206d88f2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 20 Aug 2020 20:36:21 -0700 Subject: [PATCH 064/310] Create new lesson on set --- README.md | 1 + ultimatepython/data_structures/set.py | 31 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 ultimatepython/data_structures/set.py diff --git a/README.md b/README.md index 9ca00b3a..09c61d8e 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - **Data Structures** - List: [list | zip | len | enumerate](ultimatepython/data_structures/list.py) - Tuple: [tuple](ultimatepython/data_structures/tuple.py) + - Set: [set](ultimatepython/data_structures/set.py) ## Additional resources diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py new file mode 100644 index 00000000..cd6f062c --- /dev/null +++ b/ultimatepython/data_structures/set.py @@ -0,0 +1,31 @@ +def main(): + # Define two `set` collections + multiples_two = set() + multiples_three = set() + + # Fill sensible values into the set using `add` + for i in range(10): + multiples_two.add(i * 2) + multiples_three.add(i * 3) + + print("twos", multiples_two) + print("threes", multiples_three) + + # Numbers in common are multiples of 6 + multiples_common = multiples_two.intersection(multiples_three) + for number in multiples_common: + assert number % 6 == 0 + + print("common", multiples_common) + + # You can compute exclusive multiples + multiples_two_exclusive = multiples_two.difference(multiples_three) + multiples_three_exclusive = multiples_three.difference(multiples_two) + assert len(multiples_two_exclusive) and len(multiples_three_exclusive) + + print("twos_exclusive", multiples_two_exclusive) + print("threes_exclusive", multiples_three_exclusive) + + +if __name__ == "__main__": + main() From 688fff437e867763292a2b4c8197dcc0f6a952f7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 20 Aug 2020 21:59:52 -0700 Subject: [PATCH 065/310] Add more content for set lesson --- ultimatepython/data_structures/set.py | 35 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index cd6f062c..1adce6f4 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -1,30 +1,45 @@ def main(): # Define two `set` collections multiples_two = set() - multiples_three = set() + multiples_four = set() # Fill sensible values into the set using `add` for i in range(10): multiples_two.add(i * 2) - multiples_three.add(i * 3) + multiples_four.add(i * 4) print("twos", multiples_two) - print("threes", multiples_three) + print("threes", multiples_four) - # Numbers in common are multiples of 6 - multiples_common = multiples_two.intersection(multiples_three) + # Common numbers are even and multiples of four + multiples_common = multiples_two.intersection(multiples_four) for number in multiples_common: - assert number % 6 == 0 + assert number % 2 == 0 and number % 4 == 0 print("common", multiples_common) # You can compute exclusive multiples - multiples_two_exclusive = multiples_two.difference(multiples_three) - multiples_three_exclusive = multiples_three.difference(multiples_two) - assert len(multiples_two_exclusive) and len(multiples_three_exclusive) + multiples_two_exclusive = multiples_two.difference(multiples_four) + multiples_four_exclusive = multiples_four.difference(multiples_two) + assert len(multiples_two_exclusive) and len(multiples_four_exclusive) + + # Numbers in this bracket are >2*9 and <4*10 + for number in multiples_four_exclusive: + assert 18 < number < 40 print("twos_exclusive", multiples_two_exclusive) - print("threes_exclusive", multiples_three_exclusive) + print("fours_exclusive", multiples_four_exclusive) + + multiples_all = multiples_two.union(multiples_four) + print("all", multiples_all) + + # Check if set A is subset of set B + assert multiples_four_exclusive.issubset(multiples_four) + assert multiples_four.issubset(multiples_all) + + # Check if set A is superset of set B + assert multiples_all.issuperset(multiples_two) + assert multiples_two.issuperset(multiples_two_exclusive) if __name__ == "__main__": From 0485e154ac82dde3269e6acdd942a3666e6af09a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 20 Aug 2020 22:06:21 -0700 Subject: [PATCH 066/310] Try superset and subset against the set itself --- ultimatepython/data_structures/set.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 1adce6f4..2ad7797f 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -11,7 +11,9 @@ def main(): print("twos", multiples_two) print("threes", multiples_four) - # Common numbers are even and multiples of four + # One cannot decide in what order the numbers come out - so what + # we're really looking for is fundamental truths like this one + # which can be quite fascinating multiples_common = multiples_two.intersection(multiples_four) for number in multiples_common: assert number % 2 == 0 and number % 4 == 0 @@ -37,6 +39,10 @@ def main(): assert multiples_four_exclusive.issubset(multiples_four) assert multiples_four.issubset(multiples_all) + # Check that set A is subset and superset of itself + assert multiples_all.issubset(multiples_all) + assert multiples_all.issuperset(multiples_all) + # Check if set A is superset of set B assert multiples_all.issuperset(multiples_two) assert multiples_two.issuperset(multiples_two_exclusive) From 41538cf8c57477d200f2f774dd01030f164cc5a1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 20 Aug 2020 22:55:47 -0700 Subject: [PATCH 067/310] Improve formatting of output --- ultimatepython/data_structures/list.py | 8 ++++---- ultimatepython/data_structures/set.py | 12 ++++++------ ultimatepython/syntax/loop.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 6b44f83f..bca05366 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -13,7 +13,7 @@ def main(): # that we pair the letter at index 0 with the number at index 0, and # do the same for the remaining indices for letter, number in zip(letters, numbers): - print("letter_and_number", letter, number) + print("Letter and number", letter, number) # The for loop worked because the lengths of both lists are equal. Notice # that we use the `assert` keyword to enforce that the statement @@ -23,11 +23,11 @@ def main(): # If you want to the see the indices and values of a # list side-by-side, you can use `enumerate` for index, number in enumerate(numbers): - print("number", index, number) + print("Number", index, number) # Lists can be nested at arbitrary levels matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - print("matrix", matrix) + print("Matrix of lists", matrix) # This matrix just so happens to be a square so the # the length of each rows is the same as the number of @@ -44,7 +44,7 @@ def main(): mutable[1] = 'hello' # second item mutable[-1] = True # last item mutable[-2] = 5.0 # second to last item - print("mutable", mutable) + print("Mutable list", mutable) if __name__ == "__main__": diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 2ad7797f..3d41f003 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -8,8 +8,8 @@ def main(): multiples_two.add(i * 2) multiples_four.add(i * 4) - print("twos", multiples_two) - print("threes", multiples_four) + print("Multiples of two", multiples_two) + print("Multiples of three", multiples_four) # One cannot decide in what order the numbers come out - so what # we're really looking for is fundamental truths like this one @@ -18,7 +18,7 @@ def main(): for number in multiples_common: assert number % 2 == 0 and number % 4 == 0 - print("common", multiples_common) + print("Multiples in common", multiples_common) # You can compute exclusive multiples multiples_two_exclusive = multiples_two.difference(multiples_four) @@ -29,11 +29,11 @@ def main(): for number in multiples_four_exclusive: assert 18 < number < 40 - print("twos_exclusive", multiples_two_exclusive) - print("fours_exclusive", multiples_four_exclusive) + print("Exclusive multiples of two", multiples_two_exclusive) + print("Exclusive multiples of four", multiples_four_exclusive) multiples_all = multiples_two.union(multiples_four) - print("all", multiples_all) + print("All multiples", multiples_all) # Check if set A is subset of set B assert multiples_four_exclusive.issubset(multiples_four) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 2f33730e..b1c20e0c 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -32,7 +32,7 @@ def main(): # the counter exceeds 5 i = 1 while True: - print(f"Do While {i} < 5") + print(f"Do while {i} < 5") i *= 2 # Putting this conditional after the print statement makes the loop From c876304e0519d948ee739dbb675f1a4567a2b547 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 20 Aug 2020 22:56:06 -0700 Subject: [PATCH 068/310] Create new lesson on dict --- README.md | 1 + ultimatepython/data_structures/dict.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 ultimatepython/data_structures/dict.py diff --git a/README.md b/README.md index 09c61d8e..4af37456 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - List: [list | zip | len | enumerate](ultimatepython/data_structures/list.py) - Tuple: [tuple](ultimatepython/data_structures/tuple.py) - Set: [set](ultimatepython/data_structures/set.py) + - Dict: [dict](ultimatepython/data_structures/dict.py) ## Additional resources diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py new file mode 100644 index 00000000..160c798a --- /dev/null +++ b/ultimatepython/data_structures/dict.py @@ -0,0 +1,24 @@ +def main(): + student_gpa = {"john": 3.5, + "jane": 4.0, + "bob": 2.8, + "mary": 3.2} + + # You can access the value of a particular key + assert student_gpa["john"] == 3.5 + + # You can access the dictionary keys in isolation + for student in student_gpa.keys(): + assert len(student) > 2 + + # You can access the dictionary values in isolation + for gpa in student_gpa.values(): + assert gpa > 2.0 + + # You can access the dictionary keys and values simultaneously + for student, gpa in student_gpa.items(): + print(f"Student {student} has a {gpa} GPA") + + +if __name__ == '__main__': + main() From 73190586c2977bf4769bac8b769e6495ac1ff9a4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 09:58:10 -0700 Subject: [PATCH 069/310] Enhance list content --- ultimatepython/data_structures/list.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index bca05366..6d3e748e 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -20,18 +20,18 @@ def main(): # must be true assert len(letters) == len(numbers) - # If you want to the see the indices and values of a - # list side-by-side, you can use `enumerate` + # To see the indices and values of a list at the same time, you can use + # `enumerate` to transform the list of values into an iterator of + # index-number pairs for index, number in enumerate(numbers): - print("Number", index, number) + print(f"At numbers[{index}]", number) # Lists can be nested at arbitrary levels matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print("Matrix of lists", matrix) - # This matrix just so happens to be a square so the - # the length of each rows is the same as the number of - # rows in the matrix + # This matrix just so happens to be a square so the the length of each + # row is the same as the number of rows in the matrix for row in matrix: assert len(matrix) == len(row) @@ -41,9 +41,9 @@ def main(): mutable.append(0) mutable.pop() # pop out the fifth zero mutable[0] = 100 # first item - mutable[1] = 'hello' # second item - mutable[-1] = True # last item - mutable[-2] = 5.0 # second to last item + mutable[1] = 30 # second item + mutable[-1] = 50 # last item + mutable[-2] = 20 # second to last item print("Mutable list", mutable) From aa353d935ccd95bcc6b79e415b516155df3af91e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 09:58:27 -0700 Subject: [PATCH 070/310] Create new lesson on comprehension --- README.md | 9 +++++---- ultimatepython/data_structures/comprehension.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 ultimatepython/data_structures/comprehension.py diff --git a/README.md b/README.md index 4af37456..3f495405 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,11 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) - Function: [def | lambda](ultimatepython/syntax/function.py) - **Data Structures** - - List: [list | zip | len | enumerate](ultimatepython/data_structures/list.py) - - Tuple: [tuple](ultimatepython/data_structures/tuple.py) - - Set: [set](ultimatepython/data_structures/set.py) - - Dict: [dict](ultimatepython/data_structures/dict.py) + - List: [List operations](ultimatepython/data_structures/list.py) + - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) + - Set: [Set operations](ultimatepython/data_structures/set.py) + - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) + - Comprehension: [list](ultimatepython/data_structures/comprehension.py) ## Additional resources diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py new file mode 100644 index 00000000..4f2833c3 --- /dev/null +++ b/ultimatepython/data_structures/comprehension.py @@ -0,0 +1,17 @@ +def main(): + # One interesting fact about lists is that you can build them with + # list comprehensions. Let's break this down in simple terms: + # we just want to create zeros so our expression is set to `0` + # since no computing is required; because `0` is a constant value, + # we can set the item that we compute with to `_`; and we want to + # create five zeros so we set the iterator as `range(5)`. This + # looks like an embedded for-loop and you are right! So you can + # build a list out of anything. You can imagine using this same + # pattern to convert a collection of words into a list of word + # lengths. You can also use it to convert a series of numbers into + # a list of number strings + print("List of zeros", [0 for _ in range(5)]) + + +if __name__ == '__main__': + main() From 2c9f1ed91bcce9add7d5da1baed969cd94cd49c5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:21:56 -0700 Subject: [PATCH 071/310] Add content to comprehension --- README.md | 2 +- .../data_structures/comprehension.py | 31 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3f495405..846e6862 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) - Set: [Set operations](ultimatepython/data_structures/set.py) - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) - - Comprehension: [list](ultimatepython/data_structures/comprehension.py) + - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) ## Additional resources diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index 4f2833c3..650aae03 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -1,16 +1,29 @@ def main(): - # One interesting fact about lists is that you can build them with - # list comprehensions. Let's break this down in simple terms: + # One interesting fact about data structures is that you can build + # them with comprehensions. Let's explain how the first one works: # we just want to create zeros so our expression is set to `0` # since no computing is required; because `0` is a constant value, # we can set the item that we compute with to `_`; and we want to - # create five zeros so we set the iterator as `range(5)`. This - # looks like an embedded for-loop and you are right! So you can - # build a list out of anything. You can imagine using this same - # pattern to convert a collection of words into a list of word - # lengths. You can also use it to convert a series of numbers into - # a list of number strings - print("List of zeros", [0 for _ in range(5)]) + # create five zeros so we set the iterator as `range(5)` + list_comp = [0 for _ in range(5)] + print("List of zeros", list_comp) + + words = ["cat", "mice", "horse", "bat"] + + # You can use comprehension to find the length for each word + tuple_comp = tuple(len(word) for word in words) + assert len(tuple_comp) == len(words) + print("Tuple of word lengths", tuple_comp) + + # You can use comprehension to find the unique word lengths + set_comp = {len(word) for word in words} + assert len(set_comp) < len(words) + print("Set of word lengths", set_comp) + + # You can use comprehension to map each word to its length + dict_comp = {word: len(word) for word in words} + assert len(dict_comp) == len(words) + print("Mapping of word to length", dict_comp) if __name__ == '__main__': From f952233e01b3cc2dfff2c123bd78243d0d0c6ef2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:26:02 -0700 Subject: [PATCH 072/310] Use proper terms in comprehension --- ultimatepython/data_structures/comprehension.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index 650aae03..71d2e0c0 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -10,17 +10,17 @@ def main(): words = ["cat", "mice", "horse", "bat"] - # You can use comprehension to find the length for each word + # Tuple comprehension can find the length for each word tuple_comp = tuple(len(word) for word in words) assert len(tuple_comp) == len(words) print("Tuple of word lengths", tuple_comp) - # You can use comprehension to find the unique word lengths + # Set comprehension can find the unique word lengths set_comp = {len(word) for word in words} assert len(set_comp) < len(words) print("Set of word lengths", set_comp) - # You can use comprehension to map each word to its length + # Dictionary comprehension can map each word to its length dict_comp = {word: len(word) for word in words} assert len(dict_comp) == len(words) print("Mapping of word to length", dict_comp) From 76ac8466ea5cd362aaa605c8b3b4b617d329a39f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:28:30 -0700 Subject: [PATCH 073/310] Fix up __main__ in comprehension --- ultimatepython/data_structures/comprehension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index 71d2e0c0..188a6397 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -26,5 +26,5 @@ def main(): print("Mapping of word to length", dict_comp) -if __name__ == '__main__': +if __name__ == "__main__": main() From e54d837f248a0f1c9de3991f39b776d43f4b9c3f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:29:24 -0700 Subject: [PATCH 074/310] Fix __main__ for other modules --- ultimatepython/data_structures/dict.py | 2 +- ultimatepython/data_structures/tuple.py | 2 +- ultimatepython/syntax/function.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 160c798a..0ba90474 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -20,5 +20,5 @@ def main(): print(f"Student {student} has a {gpa} GPA") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index f114e840..6dd63290 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -18,5 +18,5 @@ def main(): print(smaller_immutable) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index 9a87563b..a8d709bb 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -45,5 +45,5 @@ def main(): print(run_until.__doc__) -if __name__ == '__main__': +if __name__ == "__main__": main() From 940485f874ea58de78eddcfda329af48ca21a2dc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:34:38 -0700 Subject: [PATCH 075/310] Add header snippet to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 846e6862..da8ccd5f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go). +```python +print("Ultimate Python study guide") +``` + ## Goals Here are the primary goals of creating this guide: From e32d0d459ed801073b31ee80f238330b568e2c59 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 10:44:47 -0700 Subject: [PATCH 076/310] Add design philosophy to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index da8ccd5f..fde6456a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Table of contents +- **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) - **Language Mechanics** - **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) From cbd8ade11c570aed44d415524c4283ece4f8d125 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 11:06:46 -0700 Subject: [PATCH 077/310] Fixup list and set content --- ultimatepython/data_structures/list.py | 2 ++ ultimatepython/data_structures/set.py | 17 ++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 6d3e748e..ad638fb5 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -3,6 +3,8 @@ def main(): # "a" is a string at index 0 and # "e" is a string at index 4 letters = ["a", "b", "c", "d", "e"] + for letter in letters: + assert len(letter) == 1 # This is a list of integers where # 1 is an integer at index 0 diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 3d41f003..b4a29365 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -11,9 +11,10 @@ def main(): print("Multiples of two", multiples_two) print("Multiples of three", multiples_four) - # One cannot decide in what order the numbers come out - so what - # we're really looking for is fundamental truths like this one - # which can be quite fascinating + # One cannot decide in which order the numbers come out - so what + # we're looking for is fundamental truths like divisibility against + # 2 and 4. We do this by checking whether the modulus of 2 and 4 + # yields 0 (i.e. no remainder from division) multiples_common = multiples_two.intersection(multiples_four) for number in multiples_common: assert number % 2 == 0 and number % 4 == 0 @@ -25,25 +26,27 @@ def main(): multiples_four_exclusive = multiples_four.difference(multiples_two) assert len(multiples_two_exclusive) and len(multiples_four_exclusive) - # Numbers in this bracket are >2*9 and <4*10 + # Numbers in this bracket are greater than 2 * 9 and less than 4 * 10 for number in multiples_four_exclusive: assert 18 < number < 40 print("Exclusive multiples of two", multiples_two_exclusive) print("Exclusive multiples of four", multiples_four_exclusive) + # By computing a set union against the two sets, we have all integers + # in this program multiples_all = multiples_two.union(multiples_four) print("All multiples", multiples_all) - # Check if set A is subset of set B + # Check if set A is a subset of set B assert multiples_four_exclusive.issubset(multiples_four) assert multiples_four.issubset(multiples_all) - # Check that set A is subset and superset of itself + # Check that set A is a subset and superset of itself assert multiples_all.issubset(multiples_all) assert multiples_all.issuperset(multiples_all) - # Check if set A is superset of set B + # Check if set A is a superset of set B assert multiples_all.issuperset(multiples_two) assert multiples_two.issuperset(multiples_two_exclusive) From 0429c3ce82588e435c1fc7f0c6312d088718bc29 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 11:11:54 -0700 Subject: [PATCH 078/310] Add more content per line in README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fde6456a..ed525621 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,8 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Additional resources -As you start applying Python fundamentals to the real world, -read over best practices and examples from other well-regarded -resources. +As you start applying Python fundamentals to the real world, read over +best practices and examples from other well-regarded resources. Here are some repositories to get started with: From 3c4a515000cdb086a8c1bc7467819b14cf4d490c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 15:28:59 -0700 Subject: [PATCH 079/310] Establish ultimatepython as package --- ultimatepython/__init__.py | 0 ultimatepython/data_structures/__init__.py | 0 ultimatepython/syntax/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 ultimatepython/__init__.py create mode 100644 ultimatepython/data_structures/__init__.py create mode 100644 ultimatepython/syntax/__init__.py diff --git a/ultimatepython/__init__.py b/ultimatepython/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ultimatepython/data_structures/__init__.py b/ultimatepython/data_structures/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ultimatepython/syntax/__init__.py b/ultimatepython/syntax/__init__.py new file mode 100644 index 00000000..e69de29b From 1ceb1b7fb321b9d1ce8025dfc1a3688fe5ec417f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 15:52:24 -0700 Subject: [PATCH 080/310] Create simple runner for main modules --- runner.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 runner.py diff --git a/runner.py b/runner.py new file mode 100644 index 00000000..2cf21525 --- /dev/null +++ b/runner.py @@ -0,0 +1,47 @@ +import contextlib +import os +import sys +from importlib import import_module +from inspect import isfunction, signature +from pkgutil import walk_packages + +import ultimatepython as root + + +@contextlib.contextmanager +def no_stdout(): + save_stdout = sys.stdout + with open(os.devnull, "w") as dev_null: + sys.stdout = dev_null + yield + sys.stdout = save_stdout + + +def main(): + print(f"Start {root.__name__} runner \U0001F447") + + for item in walk_packages(root.__path__, root.__name__ + "."): + mod = import_module(item.name) + + if not hasattr(mod, "main"): + continue + + # By this point, there is a main object in the module + main_func = getattr(mod, "main") + + # The main object is a function + assert isfunction(main_func) + + # The main function has zero parameters + assert len(signature(main_func).parameters) == 0 + + # The main function should not throw any errors + print(f"Run {mod.__name__}:{main_func.__name__}") + with no_stdout(): + main_func() + + print(f"End {root.__name__} runner \U0001F44C") + + +if __name__ == "__main__": + main() From 5f978a0eb3446c790b7e4bf64232a4fc97099f96 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 16:43:51 -0700 Subject: [PATCH 081/310] Ignore __pycache__ in repo --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cbf0b2ee..86758348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ venv/ +__pycache__/ *.pyc .DS_Store From bd0d9bd62baf9f3f5f30547675e2fba4c771cc2f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 16:50:00 -0700 Subject: [PATCH 082/310] Be more specific in imports --- runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runner.py b/runner.py index 2cf21525..01477609 100644 --- a/runner.py +++ b/runner.py @@ -1,17 +1,17 @@ -import contextlib -import os import sys +from contextlib import contextmanager from importlib import import_module from inspect import isfunction, signature +from os import devnull from pkgutil import walk_packages import ultimatepython as root -@contextlib.contextmanager +@contextmanager def no_stdout(): save_stdout = sys.stdout - with open(os.devnull, "w") as dev_null: + with open(devnull, "w") as dev_null: sys.stdout = dev_null yield sys.stdout = save_stdout From 05f7cf346bd13412a582c6957828a95dc02d7107 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 17:13:24 -0700 Subject: [PATCH 083/310] Add more content on iterators in loop --- ultimatepython/syntax/loop.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index b1c20e0c..be08149b 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -1,6 +1,8 @@ def main(): - # This is a `for` loop that iterates on values 0..4 - # and adds each value to `total` + # This is a `for` loop that iterates on values 0..4 and adds each + # value to `total`. It leverages the `range` iterator. Providing + # a single integer implies that the start point is 0, the end point + # is 5 and the increment step is 1 (going forward one step) total = 0 for i in range(5): total += i @@ -8,8 +10,10 @@ def main(): # The answer is...10! print(f"Sum(0..4) = {total}") - # This is a `for` loop that iterates on values 5..1 - # and multiplies each value to `fib` + # This is a `for` loop that iterates on values 5..1 and multiplies each + # value to `fib`. The `range` iterator is used here more explicitly by + # setting the start point at 5, the end point at 0 and the increment + # step at -1 (going backward one step) fib = 1 for i in range(5, 0, -1): fib *= i From f64ed93c673ee044634ef2b0d8e87f049020eb0f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 17:16:46 -0700 Subject: [PATCH 084/310] Pad runner print statements --- runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runner.py b/runner.py index 01477609..6b76699a 100644 --- a/runner.py +++ b/runner.py @@ -18,7 +18,7 @@ def no_stdout(): def main(): - print(f"Start {root.__name__} runner \U0001F447") + print(f">>> Start {root.__name__} runner \U0001F447") for item in walk_packages(root.__path__, root.__name__ + "."): mod = import_module(item.name) @@ -40,7 +40,7 @@ def main(): with no_stdout(): main_func() - print(f"End {root.__name__} runner \U0001F44C") + print(f">>> End {root.__name__} runner \U0001F44C") if __name__ == "__main__": From 42a085fe830babd1406e6db8c546789263d0736a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 17:31:43 -0700 Subject: [PATCH 085/310] Create simple lesson on classes --- README.md | 2 ++ ultimatepython/classes/__init__.py | 0 ultimatepython/classes/simple.py | 31 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 ultimatepython/classes/__init__.py create mode 100644 ultimatepython/classes/simple.py diff --git a/README.md b/README.md index ed525621..dcb37a55 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Set: [Set operations](ultimatepython/data_structures/set.py) - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) + - **Classes** + - Simple: [class](ultimatepython/classes/simple.py) ## Additional resources diff --git a/ultimatepython/classes/__init__.py b/ultimatepython/classes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ultimatepython/classes/simple.py b/ultimatepython/classes/simple.py new file mode 100644 index 00000000..aeba4113 --- /dev/null +++ b/ultimatepython/classes/simple.py @@ -0,0 +1,31 @@ +class Car: + def __init__(self, make, model, year, miles): + """Constructor logic.""" + self.make = make + self.model = model + self.year = year + self.miles = miles + + def __repr__(self): + """Official representation for developers.""" + return f"" + + def __str__(self): + """Informal representation for users.""" + return f"{self.make} {self.model} ({self.year})" + + def drive(self, rate_in_mph): + """Drive car at a certain rate.""" + print(f"{self} is driving at {rate_in_mph} MPH") + + +def main(): + # Create a car with the provided class constructor + car = Car("Bumble", "Bee", 2000, 200000.0) + + # Call a method on the class constructor + car.drive(75) + + +if __name__ == "__main__": + main() From c262f83485117317d3c41e10365e161bf5c6a9ce Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 17:39:16 -0700 Subject: [PATCH 086/310] Revise basic_class a little bit --- README.md | 2 +- ultimatepython/classes/{simple.py => basic_class.py} | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) rename ultimatepython/classes/{simple.py => basic_class.py} (72%) diff --git a/README.md b/README.md index dcb37a55..2c49d4b9 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) - **Classes** - - Simple: [class](ultimatepython/classes/simple.py) + - Basic class: [Single class definition](ultimatepython/classes/basic_class.py) ## Additional resources diff --git a/ultimatepython/classes/simple.py b/ultimatepython/classes/basic_class.py similarity index 72% rename from ultimatepython/classes/simple.py rename to ultimatepython/classes/basic_class.py index aeba4113..6ce603b5 100644 --- a/ultimatepython/classes/simple.py +++ b/ultimatepython/classes/basic_class.py @@ -1,4 +1,12 @@ class Car: + """Simple representation of a car. + + A car is a simple example to get started with defining a class because + it has state and capabilities associated with it. We start with a + simple mental model of what a car is, so that we can start with basic + concepts associated with a class definition. + """ + def __init__(self, make, model, year, miles): """Constructor logic.""" self.make = make From 2b47b77c7b926638bbec241b26f7b6c3cd98f45a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 17:41:38 -0700 Subject: [PATCH 087/310] Add assertion in basic_class --- ultimatepython/classes/basic_class.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 6ce603b5..5ca31c06 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -15,7 +15,7 @@ def __init__(self, make, model, year, miles): self.miles = miles def __repr__(self): - """Official representation for developers.""" + """Formal representation for developers.""" return f"" def __str__(self): @@ -31,6 +31,9 @@ def main(): # Create a car with the provided class constructor car = Car("Bumble", "Bee", 2000, 200000.0) + # Formal and informal representations are not the same + assert repr(car) != str(car) + # Call a method on the class constructor car.drive(75) From 3f31d63230a025cdc8cfcf89754b754030ba2d15 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 18:02:51 -0700 Subject: [PATCH 088/310] Revise basic_class docstring --- ultimatepython/classes/basic_class.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 5ca31c06..55ae24db 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,10 +1,10 @@ class Car: - """Simple representation of a car. + """Basic representation of a car. - A car is a simple example to get started with defining a class because - it has state and capabilities associated with it. We start with a - simple mental model of what a car is, so that we can start with basic - concepts associated with a class definition. + A car is a good entity for defining with a class because it has state + and capabilities associated with it. We start with a simple mental model + of what a car is, so that we can start with core concepts associated + with a class definition. """ def __init__(self, make, model, year, miles): From d8eb6e184189e7245a517110ba817be23f6ab9a2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 18:26:45 -0700 Subject: [PATCH 089/310] Minor change to basic_class --- ultimatepython/classes/basic_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 55ae24db..48bacb1a 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -2,7 +2,7 @@ class Car: """Basic representation of a car. A car is a good entity for defining with a class because it has state - and capabilities associated with it. We start with a simple mental model + and methods associated with it. We start with a simple mental model of what a car is, so that we can start with core concepts associated with a class definition. """ From 9ca9f95cb6c5f834d4fe22cbedb8944baa393243 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 18:26:55 -0700 Subject: [PATCH 090/310] Create new lesson for abstract class --- README.md | 1 + ultimatepython/classes/abstract_class.py | 82 ++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 ultimatepython/classes/abstract_class.py diff --git a/README.md b/README.md index 2c49d4b9..9100162b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) - **Classes** - Basic class: [Single class definition](ultimatepython/classes/basic_class.py) + - Abstract class: [Abstract class definition](ultimatepython/classes/abstract_class.py) ## Additional resources diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py new file mode 100644 index 00000000..1d5cf025 --- /dev/null +++ b/ultimatepython/classes/abstract_class.py @@ -0,0 +1,82 @@ +from abc import ABC, abstractmethod + + +class Employee(ABC): + """Abstract representation of an employee.""" + + def __init__(self, name, title): + self.name = name + self.title = title + + def __str__(self): + return f"{self.name} ({self.title})" + + @abstractmethod + def do_work(self): + raise NotImplementedError + + @abstractmethod + def join_meeting(self): + raise NotImplementedError + + @abstractmethod + def relax(self): + raise NotImplementedError + + +class Engineer(Employee): + """Concrete representation of an engineer.""" + + def __init__(self, name, title, skill): + super().__init__(name, title) + self.skill = skill + + def __repr__(self): + return f"" + + def do_work(self): + print(f"{self} is doing work with {self.skill}") + + def join_meeting(self): + print(f"{self} is joining a meeting on {self.skill}") + + def relax(self): + print(f"{self} is relaxing by watching YouTube") + + +class Manager(Employee): + """Concrete representation of a manager.""" + + def __init__(self, name, title, direct_reports): + super().__init__(name, title) + self.direct_reports = direct_reports + + def __repr__(self): + return f"" + + def do_work(self): + print(f"{self} is meeting up with {self.direct_reports}") + + def join_meeting(self): + print(f"{self} is starting a meeting with {self.direct_reports}") + + def relax(self): + print(f"{self} is taking a trip to the Bahamas") + + +def main(): + engineer_john = Engineer("John Doe", "Software Engineer", "Android") + + engineer_john.do_work() + engineer_john.join_meeting() + engineer_john.relax() + + engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS") + manager_max = Manager("Max Doe", "Engineering Manager", [engineer_john, engineer_jane]) + manager_max.do_work() + manager_max.join_meeting() + manager_max.relax() + + +if __name__ == '__main__': + main() From 9862eca7642c6199fbf6fc4b03b1631a53328d2c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 18:33:16 -0700 Subject: [PATCH 091/310] Change abstract class a little bit --- ultimatepython/classes/abstract_class.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 1d5cf025..3af159ed 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -35,7 +35,7 @@ def __repr__(self): return f"" def do_work(self): - print(f"{self} is doing work with {self.skill}") + print(f"{self} is doing {self.skill} work") def join_meeting(self): print(f"{self} is joining a meeting on {self.skill}") @@ -66,13 +66,15 @@ def relax(self): def main(): engineer_john = Engineer("John Doe", "Software Engineer", "Android") + engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS") - engineer_john.do_work() - engineer_john.join_meeting() - engineer_john.relax() + engineers = [engineer_john, engineer_jane] + for engineer in engineers: + engineer.do_work() + engineer.join_meeting() + engineer.relax() - engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS") - manager_max = Manager("Max Doe", "Engineering Manager", [engineer_john, engineer_jane]) + manager_max = Manager("Max Doe", "Engineering Manager", engineers) manager_max.do_work() manager_max.join_meeting() manager_max.relax() From a3d48b7d90546db9fdd57088cc6181ca5c608cd9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 18:42:21 -0700 Subject: [PATCH 092/310] Change representation to definition --- ultimatepython/classes/abstract_class.py | 6 +++--- ultimatepython/classes/basic_class.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 3af159ed..40b7065f 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -2,7 +2,7 @@ class Employee(ABC): - """Abstract representation of an employee.""" + """Abstract definition of an employee.""" def __init__(self, name, title): self.name = name @@ -25,7 +25,7 @@ def relax(self): class Engineer(Employee): - """Concrete representation of an engineer.""" + """Concrete definition of an engineer.""" def __init__(self, name, title, skill): super().__init__(name, title) @@ -45,7 +45,7 @@ def relax(self): class Manager(Employee): - """Concrete representation of a manager.""" + """Concrete definition of a manager.""" def __init__(self, name, title, direct_reports): super().__init__(name, title) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 48bacb1a..472adac0 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,5 +1,5 @@ class Car: - """Basic representation of a car. + """Basic definition of a car. A car is a good entity for defining with a class because it has state and methods associated with it. We start with a simple mental model From 66898d6a954b8342d5cd207ffed4bafd40644517 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 21:35:32 -0700 Subject: [PATCH 093/310] Add more comments to abstract_class --- ultimatepython/classes/abstract_class.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 40b7065f..c2313538 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -35,7 +35,7 @@ def __repr__(self): return f"" def do_work(self): - print(f"{self} is doing {self.skill} work") + print(f"{self} is coding in {self.skill}") def join_meeting(self): print(f"{self} is joining a meeting on {self.skill}") @@ -43,6 +43,10 @@ def join_meeting(self): def relax(self): print(f"{self} is relaxing by watching YouTube") + def do_refactor(self): + """Do the hard work of refactoring code, unlike managers.""" + print(f"{self} is refactoring work in {self.skill}") + class Manager(Employee): """Concrete definition of a manager.""" @@ -63,21 +67,36 @@ def join_meeting(self): def relax(self): print(f"{self} is taking a trip to the Bahamas") + def do_hire(self): + """Do the hard work of hiring employees, unlike engineers.""" + print(f"{self} is hiring employees") + def main(): + # Declare two engineers engineer_john = Engineer("John Doe", "Software Engineer", "Android") engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS") engineers = [engineer_john, engineer_jane] for engineer in engineers: + assert isinstance(engineer, Engineer) + assert isinstance(engineer, Employee) + assert not isinstance(engineer, Manager) engineer.do_work() engineer.join_meeting() engineer.relax() + engineer.do_refactor() + # Declare manager with engineers as direct reports manager_max = Manager("Max Doe", "Engineering Manager", engineers) + + assert isinstance(manager_max, Manager) + assert isinstance(manager_max, Employee) + assert not isinstance(manager_max, Engineer) manager_max.do_work() manager_max.join_meeting() manager_max.relax() + manager_max.do_hire() if __name__ == '__main__': From c4a0b3f88606890e4b1a01a1391a1a2b00dbe2d7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:03:33 -0700 Subject: [PATCH 094/310] Optimize isinstance calls in abstract_class --- ultimatepython/classes/abstract_class.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index c2313538..d3c57c01 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -79,8 +79,7 @@ def main(): engineers = [engineer_john, engineer_jane] for engineer in engineers: - assert isinstance(engineer, Engineer) - assert isinstance(engineer, Employee) + assert isinstance(engineer, (Engineer, Employee)) assert not isinstance(engineer, Manager) engineer.do_work() engineer.join_meeting() @@ -90,8 +89,7 @@ def main(): # Declare manager with engineers as direct reports manager_max = Manager("Max Doe", "Engineering Manager", engineers) - assert isinstance(manager_max, Manager) - assert isinstance(manager_max, Employee) + assert isinstance(manager_max, (Manager, Employee)) assert not isinstance(manager_max, Engineer) manager_max.do_work() manager_max.join_meeting() From 8923ba2dc11546f54ee6380b12cc9b565ec31ec0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:03:44 -0700 Subject: [PATCH 095/310] Create new exception_class lesson --- README.md | 1 + ultimatepython/classes/exception_class.py | 34 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 ultimatepython/classes/exception_class.py diff --git a/README.md b/README.md index 9100162b..ce1c957f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - **Classes** - Basic class: [Single class definition](ultimatepython/classes/basic_class.py) - Abstract class: [Abstract class definition](ultimatepython/classes/abstract_class.py) + - Exception class: [Exception class definition](ultimatepython/classes/exception_class.py) ## Additional resources diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py new file mode 100644 index 00000000..cf30fe05 --- /dev/null +++ b/ultimatepython/classes/exception_class.py @@ -0,0 +1,34 @@ +class UltimatePythonError(Exception): + """Base class of errors for ultimate-python package.""" + + +class BadInputError(UltimatePythonError, ValueError): + """Bad input provided by developer.""" + + def __init__(self, bad_input, bad_reason): + self.bad_input = bad_input + self.bad_reason = bad_reason + + def __repr__(self): + return f"" + + def __str__(self): + return f"Input {self.bad_input} is bad. {self.bad_reason}" + + +def divide(num_x, num_y): + if num_y == 0: + raise BadInputError(num_y, "Cannot have a zero for the divisor") + return num_x // num_y + + +def main(): + try: + divide(1, 0) + except BadInputError as e: + assert isinstance(e, UltimatePythonError) + print(e) + + +if __name__ == '__main__': + main() From 46399333737fd6422d0584a773443c09a73b346b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:33:14 -0700 Subject: [PATCH 096/310] Create new iterator_class lesson --- README.md | 1 + ultimatepython/classes/iterator_class.py | 39 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 ultimatepython/classes/iterator_class.py diff --git a/README.md b/README.md index ce1c957f..4636108f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Basic class: [Single class definition](ultimatepython/classes/basic_class.py) - Abstract class: [Abstract class definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception class definition](ultimatepython/classes/exception_class.py) + - Iterator class: [Iterator class definition](ultimatepython/classes/iterator_class.py) ## Additional resources diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py new file mode 100644 index 00000000..6099036b --- /dev/null +++ b/ultimatepython/classes/iterator_class.py @@ -0,0 +1,39 @@ +from ultimatepython.classes.abstract_class import Employee, Manager, Engineer + + +class EmployeeIterator: + """Employee iterator.""" + def __init__(self, employee): + self.employees_to_visit = [employee] + + def __iter__(self): + return self + + def __next__(self): + if not self.employees_to_visit: + raise StopIteration + employee = self.employees_to_visit.pop() + if isinstance(employee, Engineer): + return employee + if isinstance(employee, Manager): + for report in employee.direct_reports: + self.employees_to_visit.append(report) + return employee + raise StopIteration + + +def main(): + manager = Manager("Max Doe", "Engineering Manager", [ + Engineer("John Doe", "Software Engineer", "Android"), + Engineer("Jane Doe", "Software Engineer", "iOS") + ]) + employees = [emp for emp in EmployeeIterator(manager)] + assert len(employees) == 3 + assert all(isinstance(emp, Employee) for emp in employees) + assert any(isinstance(emp, Engineer) for emp in employees) + assert any(isinstance(emp, Manager) for emp in employees) + print(employees) + + +if __name__ == '__main__': + main() From c39d60129effcda97d85881b2797d7729a7fb700 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:34:52 -0700 Subject: [PATCH 097/310] Sort imports in iterator_class --- ultimatepython/classes/iterator_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 6099036b..b1c34a40 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,4 +1,4 @@ -from ultimatepython.classes.abstract_class import Employee, Manager, Engineer +from ultimatepython.classes.abstract_class import Employee, Engineer, Manager class EmployeeIterator: From bf0068cf6cf0d0832c19b7ed3d64725b23990185 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:38:27 -0700 Subject: [PATCH 098/310] Put proper spacing in iterator_class --- ultimatepython/classes/iterator_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index b1c34a40..6f62b96d 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -3,6 +3,7 @@ class EmployeeIterator: """Employee iterator.""" + def __init__(self, employee): self.employees_to_visit = [employee] From 0d3661f5a51da18d5b573936a0df7ef646486ec9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 22:57:54 -0700 Subject: [PATCH 099/310] Update README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4636108f..f0e4f6a9 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,14 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Additional resources As you start applying Python fundamentals to the real world, read over -best practices and examples from other well-regarded resources. +best practices, code examples and project ideas from other well-regarded +resources. Here are some repositories to get started with: -- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) -- [faif/python-patterns](https://github.com/faif/python-patterns) -- [geekcomputers/Python](https://github.com/geekcomputers/Python) +- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (Interview) +- [faif/python-patterns](https://github.com/faif/python-patterns) (Design) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) (Examples) +- [geekcomputers/Python](https://github.com/geekcomputers/Python) (Examples) +- [karan/Projects](https://github.com/karan/Projects) (Ideas) +- [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (Ideas) From b803e945ba98fc257f8df86896be043efa04490f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 23:38:31 -0700 Subject: [PATCH 100/310] Update runner display content --- runner.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/runner.py b/runner.py index 6b76699a..e83f0334 100644 --- a/runner.py +++ b/runner.py @@ -7,9 +7,16 @@ import ultimatepython as root +# Module-level constants +_SUCCESS = "\033[92m" +_BOLD = "\033[1m" +_END = "\033[0m" +_RUNNER_PROGRESS = "->" + @contextmanager def no_stdout(): + """Silence standard output with /dev/null.""" save_stdout = sys.stdout with open(devnull, "w") as dev_null: sys.stdout = dev_null @@ -17,8 +24,18 @@ def no_stdout(): sys.stdout = save_stdout +def success_text(text): + """Get success text.""" + return f"{_SUCCESS}{bold_text(text)}{_END}" + + +def bold_text(text): + """Get bold text.""" + return f"{_BOLD}{text}{_END}" + + def main(): - print(f">>> Start {root.__name__} runner \U0001F447") + print(bold_text(f"Start {root.__name__} runner")) for item in walk_packages(root.__path__, root.__name__ + "."): mod = import_module(item.name) @@ -36,11 +53,12 @@ def main(): assert len(signature(main_func).parameters) == 0 # The main function should not throw any errors - print(f"Run {mod.__name__}:{main_func.__name__}") + print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{main_func.__name__}", end="") with no_stdout(): main_func() + print(" [PASS]") - print(f">>> End {root.__name__} runner \U0001F44C") + print(success_text(f"Finish {root.__name__} runner")) if __name__ == "__main__": From 806437d4f8ebc801170eeaecf92648197d963bbd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 21 Aug 2020 23:59:00 -0700 Subject: [PATCH 101/310] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f0e4f6a9..90d77d61 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,9 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Additional resources As you start applying Python fundamentals to the real world, read over -best practices, code examples and project ideas from other well-regarded -resources. +code examples and project ideas from other well-regarded resources. -Here are some repositories to get started with: +Here are some GitHub repositories to get started with: - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (Interview) - [faif/python-patterns](https://github.com/faif/python-patterns) (Design) From da822d96acac6fc1ba58602cfd5eedd2886de1b6 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:06:13 -0700 Subject: [PATCH 102/310] Add generator in iterator_class lesson --- ultimatepython/classes/iterator_class.py | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 6f62b96d..ee8551d0 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,4 +1,5 @@ from ultimatepython.classes.abstract_class import Employee, Engineer, Manager +from ultimatepython.classes.exception_class import UltimatePythonError class EmployeeIterator: @@ -6,6 +7,7 @@ class EmployeeIterator: def __init__(self, employee): self.employees_to_visit = [employee] + self.employees_visited = set() def __iter__(self): return self @@ -14,6 +16,9 @@ def __next__(self): if not self.employees_to_visit: raise StopIteration employee = self.employees_to_visit.pop() + if employee.name in self.employees_visited: + raise UltimatePythonError("Cyclic loop detected") + self.employees_visited.add(employee.name) if isinstance(employee, Engineer): return employee if isinstance(employee, Manager): @@ -23,12 +28,32 @@ def __next__(self): raise StopIteration +def employee_generator(top_employee): + """Employee generator.""" + to_visit = [top_employee] + visited = set() + while len(to_visit) > 0: + employee = to_visit.pop() + if employee.name in visited: + raise UltimatePythonError("Cyclic loop detected") + visited.add(employee.name) + if isinstance(employee, Engineer): + yield employee + elif isinstance(employee, Manager): + for report in employee.direct_reports: + to_visit.append(report) + yield employee + else: + raise StopIteration + + def main(): manager = Manager("Max Doe", "Engineering Manager", [ Engineer("John Doe", "Software Engineer", "Android"), Engineer("Jane Doe", "Software Engineer", "iOS") ]) employees = [emp for emp in EmployeeIterator(manager)] + assert employees == [emp for emp in employee_generator(manager)] assert len(employees) == 3 assert all(isinstance(emp, Employee) for emp in employees) assert any(isinstance(emp, Engineer) for emp in employees) From 061d472eee6e16ff80df8b4abc6e2aaf2bef9ca8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:11:09 -0700 Subject: [PATCH 103/310] Add more descriptive comments for iterator_class --- ultimatepython/classes/iterator_class.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index ee8551d0..bb452ff0 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -48,16 +48,22 @@ def employee_generator(top_employee): def main(): + # Manager with two direct reports manager = Manager("Max Doe", "Engineering Manager", [ Engineer("John Doe", "Software Engineer", "Android"), Engineer("Jane Doe", "Software Engineer", "iOS") ]) + + # We should return the same three employees in the same order regardless + # of whether we use the iterator class or the generator function employees = [emp for emp in EmployeeIterator(manager)] assert employees == [emp for emp in employee_generator(manager)] assert len(employees) == 3 + + # Make sure that the employees are who we expect them to be assert all(isinstance(emp, Employee) for emp in employees) - assert any(isinstance(emp, Engineer) for emp in employees) - assert any(isinstance(emp, Manager) for emp in employees) + assert isinstance(employees[0], Manager) + assert all(isinstance(emp, Engineer) for emp in employees[1:]) print(employees) From 882207675fb22203ff50afa77143cc07861eac8a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:17:59 -0700 Subject: [PATCH 104/310] Add IterationError --- ultimatepython/classes/exception_class.py | 4 ++++ ultimatepython/classes/iterator_class.py | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index cf30fe05..80b62fb2 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -2,6 +2,10 @@ class UltimatePythonError(Exception): """Base class of errors for ultimate-python package.""" +class IterationError(UltimatePythonError, RuntimeError): + """Any error that comes while iterating through objects.""" + + class BadInputError(UltimatePythonError, ValueError): """Bad input provided by developer.""" diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index bb452ff0..e0f20768 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,5 +1,5 @@ from ultimatepython.classes.abstract_class import Employee, Engineer, Manager -from ultimatepython.classes.exception_class import UltimatePythonError +from ultimatepython.classes.exception_class import IterationError class EmployeeIterator: @@ -17,7 +17,7 @@ def __next__(self): raise StopIteration employee = self.employees_to_visit.pop() if employee.name in self.employees_visited: - raise UltimatePythonError("Cyclic loop detected") + raise IterationError("Cyclic loop detected") self.employees_visited.add(employee.name) if isinstance(employee, Engineer): return employee @@ -35,7 +35,7 @@ def employee_generator(top_employee): while len(to_visit) > 0: employee = to_visit.pop() if employee.name in visited: - raise UltimatePythonError("Cyclic loop detected") + raise IterationError("Cyclic loop detected") visited.add(employee.name) if isinstance(employee, Engineer): yield employee @@ -54,7 +54,7 @@ def main(): Engineer("Jane Doe", "Software Engineer", "iOS") ]) - # We should return the same three employees in the same order regardless + # We should provide the same three employees in the same order regardless # of whether we use the iterator class or the generator function employees = [emp for emp in EmployeeIterator(manager)] assert employees == [emp for emp in employee_generator(manager)] @@ -67,5 +67,5 @@ def main(): print(employees) -if __name__ == '__main__': +if __name__ == "__main__": main() From 4a5d5c03c3eada3377c23725bdc83ff88f58f8bd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:41:00 -0700 Subject: [PATCH 105/310] Add even more comments in iterator_class --- ultimatepython/classes/iterator_class.py | 27 ++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index e0f20768..b4502f8c 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -3,16 +3,26 @@ class EmployeeIterator: - """Employee iterator.""" + """Employee iterator. + + An iterator class is composed of three methods: + + - A constructor which defines data structures + - An iterator returns the instance itself + - A retriever which gets the next element + """ def __init__(self, employee): + """Constructor logic.""" self.employees_to_visit = [employee] self.employees_visited = set() def __iter__(self): + """Iterator is self by convention.""" return self def __next__(self): + """Return the next employee available.""" if not self.employees_to_visit: raise StopIteration employee = self.employees_to_visit.pop() @@ -29,7 +39,20 @@ def __next__(self): def employee_generator(top_employee): - """Employee generator.""" + """Employee generator. + + It is essentially the same logic as above except constructed as a + generator function. Notice that the generator code is in a single + place, whereas the iterator code is in multiple places. Also notice + that we are using the `yield` keyword in the generator code. + + It is a matter of preference and context that we choose one approach + over the other. If we want something simple, go with the generator. + Otherwise, go with the iterator to fulfill more demanding requirements. + In this case, examples of such requirements are tasks like encrypting + the employee's username, running statistics on iterated employees or + excluding the reports under a particular set of managers. + """ to_visit = [top_employee] visited = set() while len(to_visit) > 0: From f2436e4e3b5f74ac7846199c34d19c4cde9554c1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:47:57 -0700 Subject: [PATCH 106/310] Add more content to abstract_class --- ultimatepython/classes/abstract_class.py | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index d3c57c01..ddd45998 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -2,7 +2,12 @@ class Employee(ABC): - """Abstract definition of an employee.""" + """Abstract definition of an employee. + + The Employee class is abstract because it inherits the `ABC` class + and it has at least one `abstractmethod`. That means you cannot create + an instance directly from its constructor. + """ def __init__(self, name, title): self.name = name @@ -25,7 +30,15 @@ def relax(self): class Engineer(Employee): - """Concrete definition of an engineer.""" + """Concrete definition of an engineer. + + The Engineer class is concrete because it implements every + `abstractmethod` that was not implemented above. + + Notice that we leverage the parent's constructor when creating + this object. We also define `do_refactor` for an engineer, which + is something that a manager prefers not to do. + """ def __init__(self, name, title, skill): super().__init__(name, title) @@ -49,7 +62,13 @@ def do_refactor(self): class Manager(Employee): - """Concrete definition of a manager.""" + """Concrete definition of a manager. + + The Manager class is concrete for the same reasons as the Engineer + class is concrete. Notice that a manager has direct reports and + has the responsibility of hiring people on the team, unlike an + engineer. + """ def __init__(self, name, title, direct_reports): super().__init__(name, title) From 3026d0628a34b61a69a56f740e830c87860c1ab3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:50:09 -0700 Subject: [PATCH 107/310] Change output slightly for abstract_class --- ultimatepython/classes/abstract_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index ddd45998..7e97d8d1 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -81,7 +81,7 @@ def do_work(self): print(f"{self} is meeting up with {self.direct_reports}") def join_meeting(self): - print(f"{self} is starting a meeting with {self.direct_reports}") + print(f"{self} is joining a meeting with {self.direct_reports}") def relax(self): print(f"{self} is taking a trip to the Bahamas") From c962c88ebcb1bfe193879b401e52e1f55814eaad Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:53:01 -0700 Subject: [PATCH 108/310] Add extra comment to dict --- ultimatepython/data_structures/dict.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 0ba90474..4db66145 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,4 +1,5 @@ def main(): + # Each student has a name key and a GPA value student_gpa = {"john": 3.5, "jane": 4.0, "bob": 2.8, From b7beb022db2e5553f32c2715f9d33a860ccb95b0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 08:54:38 -0700 Subject: [PATCH 109/310] Revise README ToC content --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90d77d61..8c51c956 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) - **Classes** - - Basic class: [Single class definition](ultimatepython/classes/basic_class.py) - - Abstract class: [Abstract class definition](ultimatepython/classes/abstract_class.py) - - Exception class: [Exception class definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator class definition](ultimatepython/classes/iterator_class.py) + - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) + - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) + - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) ## Additional resources From abcdc103279f38be6ece66d590424fdb81ad8481 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:05:36 -0700 Subject: [PATCH 110/310] Add more comments to iterator_class --- ultimatepython/classes/iterator_class.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index b4502f8c..7e87bb3f 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,5 +1,13 @@ -from ultimatepython.classes.abstract_class import Employee, Engineer, Manager -from ultimatepython.classes.exception_class import IterationError +# The benefit of writing classes (and functions for that matter) is that +# we can reuse them in other places. Specifically, we can import modules +# from the same package that this module resides in. Because, we used this +# feature, this module is now dependant on other modules. Running +# this as a plain old script will not work because that results in Python +# ignoring the other modules that exist here. In order to run this module, +# You will need it with the `-m` flag which executes a module as a script. +# See https://www.python.org/dev/peps/pep-0338/ for more details +from .abstract_class import Employee, Engineer, Manager +from .exception_class import IterationError class EmployeeIterator: @@ -10,6 +18,9 @@ class EmployeeIterator: - A constructor which defines data structures - An iterator returns the instance itself - A retriever which gets the next element + + We do this by providing what are called magic methods. Other people + call them d-under methods because they have double-underscores. """ def __init__(self, employee): From dcce2bc9a2a0cff476a59da944dbcd720c9cb98f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:10:25 -0700 Subject: [PATCH 111/310] Revise exception_class with assertions --- ultimatepython/classes/exception_class.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 80b62fb2..95cd7866 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -27,10 +27,13 @@ def divide(num_x, num_y): def main(): + # Exception classes are no different from concrete classes in that + # they all have inheritance baked in + assert issubclass(IterationError, UltimatePythonError) + assert issubclass(BadInputError, UltimatePythonError) try: divide(1, 0) except BadInputError as e: - assert isinstance(e, UltimatePythonError) print(e) From e12b6ffaf379cf6e289dbaf40c506807a19f97f3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:13:31 -0700 Subject: [PATCH 112/310] Hammer the concept of object in basic_class --- ultimatepython/classes/basic_class.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 472adac0..e40015e2 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -37,6 +37,14 @@ def main(): # Call a method on the class constructor car.drive(75) + # As a reminder: everything in Python is an object! And that applies + # to classes in the most interesting way - because they're not only + # a subclass of object - they are also an instance of object. This + # means that you can modify the Car class at runtime just like any + # other piece of data we define in Python + assert issubclass(Car, object) + assert isinstance(Car, object) + if __name__ == "__main__": main() From 7b5a08537590cbd0a33f37bfb0835369612c412b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:18:54 -0700 Subject: [PATCH 113/310] Fixup word-case in iterator_class --- ultimatepython/classes/iterator_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 7e87bb3f..d4b53e1e 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,10 +1,10 @@ # The benefit of writing classes (and functions for that matter) is that # we can reuse them in other places. Specifically, we can import modules -# from the same package that this module resides in. Because, we used this +# from the same package that this module resides in. Because we used this # feature, this module is now dependant on other modules. Running # this as a plain old script will not work because that results in Python # ignoring the other modules that exist here. In order to run this module, -# You will need it with the `-m` flag which executes a module as a script. +# we need to run it with the `-m` flag which executes a module as a script. # See https://www.python.org/dev/peps/pep-0338/ for more details from .abstract_class import Employee, Engineer, Manager from .exception_class import IterationError From 8634cf529d38b2f4559605fbfad29159ddb3d2be Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:22:57 -0700 Subject: [PATCH 114/310] Add content on algorithm in iterator_class --- ultimatepython/classes/iterator_class.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index d4b53e1e..d2181a83 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -33,7 +33,14 @@ def __iter__(self): return self def __next__(self): - """Return the next employee available.""" + """Return the next employee available. + + The logic may seem complex, but it's actually a common algorithm + used in traversing a graph of relationships. It is called depth-first + search and you can find it on Wikipedia: + + https://en.wikipedia.org/wiki/Depth-first_search + """ if not self.employees_to_visit: raise StopIteration employee = self.employees_to_visit.pop() From b5d485c9a96f071c81dc1971afac90bd765ae3da Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:23:57 -0700 Subject: [PATCH 115/310] Fixup wording in iterator_class again --- ultimatepython/classes/iterator_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index d2181a83..a8d8e112 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -36,7 +36,7 @@ def __next__(self): """Return the next employee available. The logic may seem complex, but it's actually a common algorithm - used in traversing a graph of relationships. It is called depth-first + used in traversing a relationship graph. It is called depth-first search and you can find it on Wikipedia: https://en.wikipedia.org/wiki/Depth-first_search From 345593a9269dd6abde5d0b3b9dda97cdbfa049b9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:28:50 -0700 Subject: [PATCH 116/310] Go back to absolute imports in iterator_class --- ultimatepython/classes/iterator_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index a8d8e112..ab9f6e00 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -6,8 +6,8 @@ # ignoring the other modules that exist here. In order to run this module, # we need to run it with the `-m` flag which executes a module as a script. # See https://www.python.org/dev/peps/pep-0338/ for more details -from .abstract_class import Employee, Engineer, Manager -from .exception_class import IterationError +from ultimatepython.classes.abstract_class import Employee, Engineer, Manager +from ultimatepython.classes.exception_class import IterationError class EmployeeIterator: From 701a0169a1d74df0e55d83c682df846b5a62dc1b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:39:52 -0700 Subject: [PATCH 117/310] Make abstract_class consistent --- ultimatepython/classes/abstract_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 7e97d8d1..79c06426 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -58,7 +58,7 @@ def relax(self): def do_refactor(self): """Do the hard work of refactoring code, unlike managers.""" - print(f"{self} is refactoring work in {self.skill}") + print(f"{self} is refactoring code") class Manager(Employee): From ffd555bc84ddba627ae0e96fd1027eedaec9b6e9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 10:57:31 -0700 Subject: [PATCH 118/310] Update README.md Add motivation for added flair --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 8c51c956..4e9aa494 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,16 @@ Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoan print("Ultimate Python study guide") ``` +## Motivation + +I created a GitHub repo to share what I've learned about core Python over the +past 5+ years of using it as a college graduate, an employee at large-scale +companies and as an open-source contributor of repositories like +[Celery](https://github.com/celery/celery) and +[Full Stack Python](https://github.com/mattmakai/fullstackpython.com). +I look forward to seeing more people learn Python and pursue their passions +through it. + ## Goals Here are the primary goals of creating this guide: From f158d3d1945bba1832ab75ec128fd526d0daaca2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 11:21:08 -0700 Subject: [PATCH 119/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e9aa494..c2b5a271 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ultimate Python study guide -Ultimate Python study guide, influenced by [ultimate-go](https://github.com/hoanhan101/ultimate-go). +Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: ```python print("Ultimate Python study guide") From abc0dc1580feab339d63046a89df0926d56fcf42 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 20:06:45 -0700 Subject: [PATCH 120/310] Fix grammar in basic_class --- ultimatepython/classes/basic_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index e40015e2..30688b96 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -39,7 +39,7 @@ def main(): # As a reminder: everything in Python is an object! And that applies # to classes in the most interesting way - because they're not only - # a subclass of object - they are also an instance of object. This + # subclasses of object - they are also instances of object. This # means that you can modify the Car class at runtime just like any # other piece of data we define in Python assert issubclass(Car, object) From 17daf063b4b1ba1ce754c5c2021233663bb146ac Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 20:09:41 -0700 Subject: [PATCH 121/310] Add design pattern links to iterator_class --- ultimatepython/classes/iterator_class.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index ab9f6e00..ca1dbaec 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -21,6 +21,17 @@ class EmployeeIterator: We do this by providing what are called magic methods. Other people call them d-under methods because they have double-underscores. + + An iterator class resembles the Iterator design pattern, and you + can find it on Wikipedia: + + https://en.wikipedia.org/wiki/Iterator_pattern + + Design patterns are battle-tested ways of structuring code to handle + common problems encountered while designing software as a team. + Here's a Wikipedia link for more design patterns: + + https://en.wikipedia.org/wiki/Design_Patterns """ def __init__(self, employee): From 1a13d3689da956bc8465b6d3690c07b67c1ddc8c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 20:14:11 -0700 Subject: [PATCH 122/310] Fix wording ever so slightly --- ultimatepython/classes/iterator_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index ca1dbaec..48fb8a7d 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -28,7 +28,7 @@ class EmployeeIterator: https://en.wikipedia.org/wiki/Iterator_pattern Design patterns are battle-tested ways of structuring code to handle - common problems encountered while designing software as a team. + common problems encountered while writing software as a team. Here's a Wikipedia link for more design patterns: https://en.wikipedia.org/wiki/Design_Patterns From 299e14fc821b6174cba6dc5e55b3d0a7dedc4d8a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 20:16:22 -0700 Subject: [PATCH 123/310] Fix iterator_class once more --- ultimatepython/classes/iterator_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 48fb8a7d..0c94f8e9 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -28,7 +28,7 @@ class EmployeeIterator: https://en.wikipedia.org/wiki/Iterator_pattern Design patterns are battle-tested ways of structuring code to handle - common problems encountered while writing software as a team. + common problems encountered while writing software in a team setting. Here's a Wikipedia link for more design patterns: https://en.wikipedia.org/wiki/Design_Patterns From 58f96cf5b9c264cd491022695d4a0b8dcd72d941 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 20:49:21 -0700 Subject: [PATCH 124/310] Revise exception_class structure --- ultimatepython/classes/exception_class.py | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 95cd7866..ec1fe215 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -1,39 +1,39 @@ class UltimatePythonError(Exception): - """Base class of errors for ultimate-python package.""" + """Base class of errors for ultimate-python package. + + This is the base exception for the Ultimate Python study guide. One of + the reasons why developers design a class like this is for consumption + by downstream services and command-line tools. + """ class IterationError(UltimatePythonError, RuntimeError): """Any error that comes while iterating through objects.""" -class BadInputError(UltimatePythonError, ValueError): - """Bad input provided by developer.""" - - def __init__(self, bad_input, bad_reason): - self.bad_input = bad_input - self.bad_reason = bad_reason - - def __repr__(self): - return f"" - - def __str__(self): - return f"Input {self.bad_input} is bad. {self.bad_reason}" +class DivisionError(UltimatePythonError, ValueError): + """Any division error that results from invalid input.""" -def divide(num_x, num_y): - if num_y == 0: - raise BadInputError(num_y, "Cannot have a zero for the divisor") - return num_x // num_y +def divide_positive_numbers(dividend, divisor): + """Divide a positive number by another positive number""" + if divisor == 0: + raise DivisionError("Cannot have a zero divisor") + elif dividend < 0: + raise DivisionError("Cannot have a negative dividend") + elif divisor < 0: + raise DivisionError("Cannot have a negative divisor") + return dividend // divisor def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in assert issubclass(IterationError, UltimatePythonError) - assert issubclass(BadInputError, UltimatePythonError) + assert issubclass(DivisionError, UltimatePythonError) try: - divide(1, 0) - except BadInputError as e: + divide_positive_numbers(1, 0) + except DivisionError as e: print(e) From 161d5566a062a90a8ba7ec4a340ad5ac4575225c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 21:00:41 -0700 Subject: [PATCH 125/310] Add more explanations on exception_class --- ultimatepython/classes/exception_class.py | 32 ++++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index ec1fe215..5cc488f0 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -8,18 +8,42 @@ class UltimatePythonError(Exception): class IterationError(UltimatePythonError, RuntimeError): - """Any error that comes while iterating through objects.""" + """Any error that comes while iterating through objects. + + This is used in `ultimatepython.classes.iterator_class` so please do + NOT delete this class. + """ class DivisionError(UltimatePythonError, ValueError): - """Any division error that results from invalid input.""" + """Any division error that results from invalid input. + + This can be complemented with the following exceptions if they + happen enough across the codebase: + + - ZeroDivisorError + - NegativeDividendError + - NegativeDivisorError + + That being said, there's a point of diminishing returns when + we design too many exceptions. It's better to design a few + that most developers handle than design many that few + developers handle. + """ def divide_positive_numbers(dividend, divisor): - """Divide a positive number by another positive number""" + """Divide a positive number by another positive number. + + Writing a program in this style is what is considered defensive + programming. For more on this programming style, check out the + Wikipedia link below: + + https://en.wikipedia.org/wiki/Defensive_programming + """ if divisor == 0: raise DivisionError("Cannot have a zero divisor") - elif dividend < 0: + elif dividend <= 0: raise DivisionError("Cannot have a negative dividend") elif divisor < 0: raise DivisionError("Cannot have a negative divisor") From 69d34a5e86aed3e259a266408d63ba63528a7ad2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 21:15:51 -0700 Subject: [PATCH 126/310] Add package setup configuration --- .gitignore | 1 + setup.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 86758348..16692b16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea/ venv/ __pycache__/ +*.egg-info/ *.pyc .DS_Store diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..66f6e712 --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from setuptools import find_packages, setup + +setup( + name="ultimatepython", + packages=find_packages(), + description="Ultimate Python study guide.", + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], +) From b0b49bec6be4e28ab3f78952472f6c74ec6ee91b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 21:20:31 -0700 Subject: [PATCH 127/310] Revise description of package --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 66f6e712..babdd5cd 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="ultimatepython", packages=find_packages(), - description="Ultimate Python study guide.", + description="Ultimate Python study guide for newcomers and professionals alike.", classifiers=[ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", From 07f8d991eb138157eee7a488ed69adf2e6a0d51d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 22 Aug 2020 21:22:20 -0700 Subject: [PATCH 128/310] Add Python 3.8 to the mix --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index babdd5cd..773ea266 100644 --- a/setup.py +++ b/setup.py @@ -8,5 +8,6 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], ) From 427471bc1c27befe1cf3fa39f089a3c8f4b1a86f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 09:08:54 -0700 Subject: [PATCH 129/310] Create new meta_class lesson --- README.md | 1 + ultimatepython/classes/meta_class.py | 129 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 ultimatepython/classes/meta_class.py diff --git a/README.md b/README.md index c2b5a271..3edfa630 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) + - Meta class: [Metaclass definition](ultimatepython/classes/meta_class.py) ## Additional resources diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py new file mode 100644 index 00000000..26340a5c --- /dev/null +++ b/ultimatepython/classes/meta_class.py @@ -0,0 +1,129 @@ +from abc import ABC + + +class ModelMeta(type): + """Model metaclass. + + By studying how SQLAlchemy and Django ORM work under the hood, we can see + how one may create a rudimentary meta-class for adding useful abstractions + to class definitions at runtime. + + Two of the main use cases for a metaclass are to (A) modify a class before + it is visible to a developer and (B) add a class to a dynamic registry for + further automation. + + For more on meta-classes, feel free to visit the link below: + + https://realpython.com/python-metaclasses/ + https://docs.python.org/3/reference/datamodel.html + """ + + # Model table registry + tables = {} + + def __new__(mcs, name, bases, attrs): + """Factory for modifying the defined class at runtime.""" + kls = super().__new__(mcs, name, bases, attrs) + + # Abstract model does not have a `model_name` but a real model does. + # We will leverage this fact later on this routine. + if attrs.get("__abstract__") is True: + kls.model_name = None + else: + custom_name = attrs.get("__table_name__") + default_name = kls.__name__.replace("Model", "").lower() + kls.model_name = custom_name if custom_name else default_name + + # Ensure abstract and real models have fields so that + # they can be inherited + kls.model_fields = {} + + # Fill model fields from the parent classes (left-to-right) + for base in bases: + kls.model_fields.update(base.model_fields) + + # Add model fields of its own last + kls.model_fields.update({ + field_name: field_obj + for field_name, field_obj in attrs.items() + if isinstance(field_obj, BaseField) + }) + + # Register a real table (a table with valid `model_name`) to + # the metaclass `table` registry so that it can be sent later to a + # database adapter which uses each table entry to create a + # corresponding physical table in a database. + if kls.model_name: + ModelMeta.tables[kls.model_name] = ModelTable(kls.model_name, + kls.model_fields) + + # Return newly modified class + return kls + + +class ModelTable: + """Model table.""" + + def __init__(self, table_name, table_fields): + self.table_name = table_name + self.table_fields = table_fields + + def __repr__(self): + return f"" + + +class BaseField(ABC): + """Base field.""" + + def __repr__(self): + """Brief representation of any field.""" + return f"<{type(self).__name__}>" + + +class CharField(BaseField): + """Character field.""" + + +class IntegerField(BaseField): + """Integer field.""" + + +class BaseModel(metaclass=ModelMeta): + """Base model.""" + __abstract__ = True # This is NOT a real table + row_id = IntegerField() + + +class UserModel(BaseModel): + """User model.""" + __table_name__ = "user_rocks" # This is a custom table name + username = CharField() + password = CharField() + age = CharField() + sex = CharField() + + +class AddressModel(BaseModel): + """Address model.""" + user_id = IntegerField() + address = CharField() + state = CharField() + zip_code = CharField() + + +def main(): + # Each model was modified at runtime with ModelMeta + for real_model in BaseModel.__subclasses__(): + print("Real table", real_model.model_name) + print("Real fields", real_model.model_fields) + + # Each models was registered at runtime with ModelMeta + for meta_table in ModelMeta.tables.values(): + print("Meta table", meta_table) + + # Base model was given special treatment, as expected + assert BaseModel.model_name is None + + +if __name__ == '__main__': + main() From fba3146cd2662713e335e5471c008b4213459385 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 09:45:28 -0700 Subject: [PATCH 130/310] Add disclaimer on meta_class lesson --- ultimatepython/classes/meta_class.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 26340a5c..14c22d91 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -8,11 +8,15 @@ class ModelMeta(type): how one may create a rudimentary meta-class for adding useful abstractions to class definitions at runtime. - Two of the main use cases for a metaclass are to (A) modify a class before - it is visible to a developer and (B) add a class to a dynamic registry for - further automation. + The main use cases for a metaclass are to (A) modify a class before + it is visible to a developer and (B) add a class to a dynamic registry + for further automation. - For more on meta-classes, feel free to visit the link below: + Do NOT use a meta-class if it can be done more simply with class + composition, class inheritance or functions. Simple code is the reason + why Python is attractive for 99% of users. + + For more on meta-classes, visit the links below: https://realpython.com/python-metaclasses/ https://docs.python.org/3/reference/datamodel.html From 93b7946b2b8b0fd6c4d108312e64b99e187edcf8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 09:46:53 -0700 Subject: [PATCH 131/310] Fix spelling/grammar on meta_class --- ultimatepython/classes/meta_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 14c22d91..1412e278 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -121,7 +121,7 @@ def main(): print("Real table", real_model.model_name) print("Real fields", real_model.model_fields) - # Each models was registered at runtime with ModelMeta + # Each model was registered at runtime with ModelMeta for meta_table in ModelMeta.tables.values(): print("Meta table", meta_table) From ce4923956602d3ce5609e222812ff0f273f4b2a1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 11:25:31 -0700 Subject: [PATCH 132/310] Fixup comment in meta_class --- ultimatepython/classes/meta_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 1412e278..bba105c1 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -46,7 +46,7 @@ def __new__(mcs, name, bases, attrs): for base in bases: kls.model_fields.update(base.model_fields) - # Add model fields of its own last + # Fill model fields from itself kls.model_fields.update({ field_name: field_obj for field_name, field_obj in attrs.items() From 508285993dcc7a43d3b1b98b2b5869eb87d3b790 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 13:26:17 -0700 Subject: [PATCH 133/310] Add PEP8 as style guide --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3edfa630..f0d6fe41 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. ## Table of contents - **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) +- **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) - **Language Mechanics** - **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) From 49ed982329fa7aa4aa7a91f9b2f70d78c2b1a504 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:25:06 -0700 Subject: [PATCH 134/310] Add range queries in list lesson --- ultimatepython/data_structures/list.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index ad638fb5..35eb4f30 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -6,6 +6,13 @@ def main(): for letter in letters: assert len(letter) == 1 + # You can get a subset of letters with the range selector + assert letters[1:] == ["b", "c", "d", "e"] + assert letters[:-1] == ["a", "b", "c", "d"] + assert letters[1:-2] == ["b", "c"] + assert letters[::2] == ["a", "c", "e"] + assert letters[::-1] == ["e", "d", "c", "b", "a"] + # This is a list of integers where # 1 is an integer at index 0 # 5 is an integer at index 4 From 7435d8a94f9a220dbbfddd8bceae87da3ada0771 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:40:22 -0700 Subject: [PATCH 135/310] Add more print and assert in ultimatepython --- ultimatepython/syntax/expression.py | 10 +++++----- ultimatepython/syntax/variable.py | 26 ++++++++++++++++---------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index c94d9e22..6a3c8144 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -3,25 +3,25 @@ def main(): x = 1 # Its value can used as part of expressions - print(x + 1) + print("Add integer", x + 1) # An expression can be chained indefinitely. This concept of chaining # expressions is powerful because it allows you to compose simple pieces # of code into larger pieces of code over time - print(x * 2 * 2 * 2) + print("Multiply integers", x * 2 * 2 * 2) # Division is a bit tricky in Python because it returns a result # of type 'float' by default - print(x / 2) + print("Divide as float", x / 2) # If an integer division is desired, then an extra slash # must be added to the expression - print(x // 2) + print("Divide by integer", x // 2) # Powers of an integer can be leveraged too. If you want more math # features, then you will have to leverage the builtin `math` library, # a third-party library or your own library - print(x * 2 ** 3) + print("Power of integer", x * 2 ** 3) if __name__ == "__main__": diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index cfda2281..68e926a4 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -6,24 +6,30 @@ def main(): d = "hello" # Notice that each type is a `class`. Each of the variables above refers - # to an `instance` of the class it belongs to + # to an `instance` of the class it belongs to. For instance, `a` is of type + # `int` which is a `class` a_type = type(a) # b_type = type(b) # c_type = type(c) # d_type = type(d) # - # This leads to an important point: everything is an object in Python - a_is_obj = isinstance(a, object) and issubclass(a_type, object) - b_is_obj = isinstance(b, object) and issubclass(b_type, object) - c_is_obj = isinstance(c, object) and issubclass(c_type, object) - d_is_obj = isinstance(d, object) and issubclass(d_type, object) + # This leads to an important point: everything is an object in Python. + # Notice that all instances and classes are objects. Also, say hello + # to the `assert` keyword! This is a debugging aid that we use to validate + # the code as we progress through the `main` functions. These statements + # are used to validate the correctness of the data and to reduce + # the amount of standard output that is sent when running `main` + assert isinstance(a, object) and isinstance(a_type, object) + assert isinstance(b, object) and isinstance(b_type, object) + assert isinstance(c, object) and isinstance(c_type, object) + assert isinstance(d, object) and isinstance(d_type, object) # Here is a summary via the `print` function. Notice that we print more # than one variable at a time - print(a, a_type, a_is_obj) - print(b, b_type, b_is_obj) - print(c, c_type, c_is_obj) - print(d, d_type, d_is_obj) + print(a, a_type) + print(b, b_type) + print(c, c_type) + print(d, d_type) if __name__ == "__main__": From b3e7e100a2318a414944bf8654cf9182c9e7a1ac Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:44:00 -0700 Subject: [PATCH 136/310] Fixup variable lesson --- ultimatepython/syntax/variable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 68e926a4..9417cacf 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -26,10 +26,10 @@ def main(): # Here is a summary via the `print` function. Notice that we print more # than one variable at a time - print(a, a_type) - print(b, b_type) - print(c, c_type) - print(d, d_type) + print("a", a, a_type) + print("b", b, b_type) + print("c", c, c_type) + print("d", d, d_type) if __name__ == "__main__": From 6da06303652da34a3338d87287ab8c3aaf0d8e3a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:45:11 -0700 Subject: [PATCH 137/310] Fixup conditional lesson --- ultimatepython/syntax/conditional.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index 139ed127..1b0cf51e 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -4,32 +4,32 @@ def main(): # This condition is obviously true if x_add_two == 3: - print("math wins") # run + print("Math wins") # run # A negated condition can also be true if not x_add_two == 1: - print("math wins here too") # run + print("Math wins here too") # run # There are `else` statements as well, which run if the initial condition # fails. Notice that one line gets skipped, and that this conditional # does not help one make a conclusion on the variable's true value if x_add_two == 1: - print("math lost here...") # skip + print("Math lost here...") # skip else: - print("math wins otherwise") # run + print("Math wins otherwise") # run # The `else` statement also run once every other `if` and `elif` condition # fails. Notice that multiple lines get skipped, and that all of the # conditions could have been compressed to `x_add_two != 3` # for simplicity. In this case, less is more if x_add_two == 1: - print("nope not this one...") # skip + print("Nope not this one...") # skip elif x_add_two == 2: - print("nope not this one either...") # skip + print("Nope not this one either...") # skip elif x_add_two < 3 or x_add_two > 3: - print("nope not quite...") # skip + print("Nope not quite...") # skip else: - print("math wins finally") # run + print("Math wins finally") # run if __name__ == "__main__": From 3731ae385626fad48bcd359ff436371c5492cb59 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:46:18 -0700 Subject: [PATCH 138/310] Fixup tuple lesson --- ultimatepython/data_structures/tuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 6dd63290..a6bf4e95 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -8,7 +8,7 @@ def main(): # It can be iterated over like a list for number in immutable: - print("immutable", number) + print("Immutable", number) # But its contents cannot be changed. As an alternative, you can # create new tuples from existing tuples From 3aaeb85d96b5c065ab169431342376b71f668d5e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 18:51:54 -0700 Subject: [PATCH 139/310] Enhance docstring in meta_class --- ultimatepython/classes/meta_class.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index bba105c1..8691af23 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -5,14 +5,16 @@ class ModelMeta(type): """Model metaclass. By studying how SQLAlchemy and Django ORM work under the hood, we can see - how one may create a rudimentary meta-class for adding useful abstractions - to class definitions at runtime. + how one may create a rudimentary metaclass for adding useful abstractions + to class definitions at runtime. That being said, this metaclass is a toy + example and does not reflect everything that happens in the metaclass + definitions define in either framework. The main use cases for a metaclass are to (A) modify a class before it is visible to a developer and (B) add a class to a dynamic registry for further automation. - Do NOT use a meta-class if it can be done more simply with class + Do NOT use a metaclass if it can be done more simply with class composition, class inheritance or functions. Simple code is the reason why Python is attractive for 99% of users. From 8d3e39497caab842799df3e93cc86cdd3d4699d0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 19:18:46 -0700 Subject: [PATCH 140/310] Improve meta_class lesson more --- ultimatepython/classes/meta_class.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 8691af23..2f4d5bd4 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -56,16 +56,22 @@ def __new__(mcs, name, bases, attrs): }) # Register a real table (a table with valid `model_name`) to - # the metaclass `table` registry so that it can be sent later to a - # database adapter which uses each table entry to create a - # corresponding physical table in a database. + # the metaclass `table` registry. After all the tables are + # registered, the registry can be sent to a database adapter + # which uses each table to create a properly defined schema + # for the database of choice (i.e. PostgreSQL, MySQL). if kls.model_name: - ModelMeta.tables[kls.model_name] = ModelTable(kls.model_name, - kls.model_fields) + kls.model_table = ModelTable(kls.model_name, kls.model_fields) + ModelMeta.tables[kls.model_name] = kls.model_table # Return newly modified class return kls + @property + def is_registered(cls): + """Check if the model's name is valid and exists in the registry.""" + return cls.model_name and cls.model_name in cls.tables + class ModelTable: """Model table.""" @@ -120,14 +126,17 @@ class AddressModel(BaseModel): def main(): # Each model was modified at runtime with ModelMeta for real_model in BaseModel.__subclasses__(): - print("Real table", real_model.model_name) - print("Real fields", real_model.model_fields) + assert real_model.is_registered + print("Real model name", real_model.model_name) + print("Real model fields", real_model.model_fields) + print("Real model table", real_model.model_table) # Each model was registered at runtime with ModelMeta for meta_table in ModelMeta.tables.values(): - print("Meta table", meta_table) + print("ModelMeta table", meta_table) # Base model was given special treatment, as expected + assert not BaseModel.is_registered assert BaseModel.model_name is None From 1028277b0908e27b7df7efc1f863fe3284cebbf9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 19:37:14 -0700 Subject: [PATCH 141/310] Fix grammar a bit in meta_class --- ultimatepython/classes/meta_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 2f4d5bd4..8dd71a8d 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -8,7 +8,7 @@ class ModelMeta(type): how one may create a rudimentary metaclass for adding useful abstractions to class definitions at runtime. That being said, this metaclass is a toy example and does not reflect everything that happens in the metaclass - definitions define in either framework. + definitions defined in either framework. The main use cases for a metaclass are to (A) modify a class before it is visible to a developer and (B) add a class to a dynamic registry From 1fdf33869ff5858b177dffd7b4a6b5707e7add5e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 19:39:00 -0700 Subject: [PATCH 142/310] Add extra modification to meta_class --- ultimatepython/classes/meta_class.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/classes/meta_class.py index 8dd71a8d..9b902e06 100644 --- a/ultimatepython/classes/meta_class.py +++ b/ultimatepython/classes/meta_class.py @@ -63,6 +63,8 @@ def __new__(mcs, name, bases, attrs): if kls.model_name: kls.model_table = ModelTable(kls.model_name, kls.model_fields) ModelMeta.tables[kls.model_name] = kls.model_table + else: + kls.model_table = None # Return newly modified class return kls @@ -138,6 +140,7 @@ def main(): # Base model was given special treatment, as expected assert not BaseModel.is_registered assert BaseModel.model_name is None + assert BaseModel.model_table is None if __name__ == '__main__': From fcceed8043d6d14288675d1b29967db7f9a0e8da Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 23 Aug 2020 19:41:11 -0700 Subject: [PATCH 143/310] Remove comment about assert keyword in list --- ultimatepython/data_structures/list.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 35eb4f30..24b0cea7 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -24,9 +24,7 @@ def main(): for letter, number in zip(letters, numbers): print("Letter and number", letter, number) - # The for loop worked because the lengths of both lists are equal. Notice - # that we use the `assert` keyword to enforce that the statement - # must be true + # The for loop worked because the lengths of both lists are equal assert len(letters) == len(numbers) # To see the indices and values of a list at the same time, you can use From 4d459026c68c766e58472e6bee827416b88339fb Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 08:28:07 -0700 Subject: [PATCH 144/310] Create advanced section and decorator lesson --- README.md | 4 +- ultimatepython/advanced/__init__.py | 0 ultimatepython/advanced/decorator.py | 105 ++++++++++++++++++ .../{classes => advanced}/meta_class.py | 0 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 ultimatepython/advanced/__init__.py create mode 100644 ultimatepython/advanced/decorator.py rename ultimatepython/{classes => advanced}/meta_class.py (100%) diff --git a/README.md b/README.md index f0d6fe41..70715288 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,9 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) - - Meta class: [Metaclass definition](ultimatepython/classes/meta_class.py) + - **Advanced** + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) ## Additional resources diff --git a/ultimatepython/advanced/__init__.py b/ultimatepython/advanced/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py new file mode 100644 index 00000000..667e955b --- /dev/null +++ b/ultimatepython/advanced/decorator.py @@ -0,0 +1,105 @@ +from contextlib import contextmanager +from functools import wraps + +_HEADER = "---" + + +@contextmanager +def header_section(): + """Print header line first before running anything. + + Notice a context manager is used so that we enter a block where a header + is printed out before proceeding with the function call at the point + of yielding. + + Also notice that `header_section` is a generator that is wrapped by + `contextmanager`. The `contextmanager` handles entering and exiting a section + of code without defining a full-blown class to handle `__enter__` and + `__exit__` use cases. + + There are many more use cases for context managers, like writing / reading + data from a file. Another one is protecting database integrity while sending + CREATE / UPDATE / DELETE statements over the network. For more on how context + managers work, please consult the Python docs for more information. + + https://docs.python.org/3/library/contextlib.html + """ + print(_HEADER) + yield + + +def run_with_any(fn): + """Run with a string or a collection with strings exclusively. + + We define a custom decorator that allows one to convert a function whose + input is a single string into a function whose input can be many things. + + A function decorator consists of the following: + + - An input function to run with + - A wrapper function that uses the input function + + The `wrapper` does not need to accept the input function as a parameter + because it can get that from its parent `run_with_any`. Furthermore, the + data that wrapper receives does NOT have to be the same as the data that + the input function needs to accept. + + The formal specification for function decorators is here: + + https://www.python.org/dev/peps/pep-0318/ + + The formal specification for class decorators is here: + + https://www.python.org/dev/peps/pep-3129/ + """ + + @wraps(fn) + def wrapper(stringy): + """Apply wrapped function to a string or a collection.""" + if isinstance(stringy, str): + return fn(stringy) + elif isinstance(stringy, dict): + if all(isinstance(item, str) for item in stringy.values()): + return {key: fn(value) for key, value in stringy.items()} + # Nested call on unknown data entries + return {key: wrapper(value) for key, value in stringy.items()} + elif isinstance(stringy, (list, set, tuple)): + sequence_kls = type(stringy) + if all(isinstance(item, str) for item in stringy): + return sequence_kls(fn(value) for value in stringy) + # Nested call on unknown data entries + return sequence_kls(wrapper(value) for value in stringy) + raise ValueError("Found item that is not a collection or a string.") + + return wrapper + + +@run_with_any +def hide_content(content): + """Hide half of the stringy content.""" + start_point = len(content) // 2 + num_of_asterisks = len(content) // 2 + len(content) % 2 + return content[:start_point] + "*" * num_of_asterisks + + +def main(): + # There are so many plain-text secrets out in the open + insecure_stringy = [ + {"username": "johndoe", "password": "s3cret123"}, # User credentials + ["123-456=7890", "123-456-7891"], # Social security numbers + [("johndoe", "janedoe"), ("bobdoe", "marydoe")], # Couple names + "secretLaunchCode123", # Secret launch code + ] + + # Time to encrypt them all so that they can't be snatched away + secure_stringy = hide_content(insecure_stringy) + + # See what changed between the old stringy and the new stringy + for insecure_item, secure_item in zip(insecure_stringy, secure_stringy): + with header_section(): + print("Insecure item", insecure_item) + print("Secure item", secure_item) + + +if __name__ == '__main__': + main() diff --git a/ultimatepython/classes/meta_class.py b/ultimatepython/advanced/meta_class.py similarity index 100% rename from ultimatepython/classes/meta_class.py rename to ultimatepython/advanced/meta_class.py From 7ae0c40056f242fe56b73eab6e0424f712293a80 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 09:04:40 -0700 Subject: [PATCH 145/310] Add GDPR comment in decorator lesson --- ultimatepython/advanced/decorator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 667e955b..4200b279 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -91,10 +91,13 @@ def main(): "secretLaunchCode123", # Secret launch code ] - # Time to encrypt them all so that they can't be snatched away + # Time to encrypt them all so that they can't be snatched away. This kind + # of work is the stuff that might be done by a company for GDPR. For more + # on that policy, check out the following Wikipedia page: + # https://en.wikipedia.org/wiki/General_Data_Protection_Regulation secure_stringy = hide_content(insecure_stringy) - # See what changed between the old stringy and the new stringy + # See what changed between the insecure stringy and the secure stringy for insecure_item, secure_item in zip(insecure_stringy, secure_stringy): with header_section(): print("Insecure item", insecure_item) From 4e7e95b667aa16f668ef5696a2dabebc08d74f89 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 09:07:29 -0700 Subject: [PATCH 146/310] Fix ValueError comment --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 4200b279..aefdcae7 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -69,7 +69,7 @@ def wrapper(stringy): return sequence_kls(fn(value) for value in stringy) # Nested call on unknown data entries return sequence_kls(wrapper(value) for value in stringy) - raise ValueError("Found item that is not a collection or a string.") + raise ValueError("Found item that is not a string or a collection.") return wrapper From 962dc713bbab03dc0571df230234de129b2eaab7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 09:08:35 -0700 Subject: [PATCH 147/310] Fixup __main__ quotes in decorator lesson --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index aefdcae7..b4d62329 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -104,5 +104,5 @@ def main(): print("Secure item", secure_item) -if __name__ == '__main__': +if __name__ == "__main__": main() From eb9774cfed663f7cc4bc3b011ff5ea72a75abfa7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 13:04:59 -0700 Subject: [PATCH 148/310] Update runner.py --- runner.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/runner.py b/runner.py index e83f0334..7156df6f 100644 --- a/runner.py +++ b/runner.py @@ -12,6 +12,7 @@ _BOLD = "\033[1m" _END = "\033[0m" _RUNNER_PROGRESS = "->" +_MODULE_MAIN = "main" @contextmanager @@ -40,11 +41,12 @@ def main(): for item in walk_packages(root.__path__, root.__name__ + "."): mod = import_module(item.name) - if not hasattr(mod, "main"): + # Skip modules without a main object + if not hasattr(mod, _MODULE_MAIN): continue # By this point, there is a main object in the module - main_func = getattr(mod, "main") + main_func = getattr(mod, _MODULE_MAIN) # The main object is a function assert isfunction(main_func) @@ -53,7 +55,7 @@ def main(): assert len(signature(main_func).parameters) == 0 # The main function should not throw any errors - print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{main_func.__name__}", end="") + print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_MODULE_MAIN}", end="") with no_stdout(): main_func() print(" [PASS]") From 7b88b835311d016a67377d9e707676f80e67320c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 20:44:11 -0700 Subject: [PATCH 149/310] Update module constants for runner --- runner.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runner.py b/runner.py index 7156df6f..daf938f5 100644 --- a/runner.py +++ b/runner.py @@ -8,9 +8,9 @@ import ultimatepython as root # Module-level constants -_SUCCESS = "\033[92m" -_BOLD = "\033[1m" -_END = "\033[0m" +_STYLE_SUCCESS = "\033[92m" +_STYLE_BOLD = "\033[1m" +_STYLE_END = "\033[0m" _RUNNER_PROGRESS = "->" _MODULE_MAIN = "main" @@ -27,12 +27,12 @@ def no_stdout(): def success_text(text): """Get success text.""" - return f"{_SUCCESS}{bold_text(text)}{_END}" + return f"{_STYLE_SUCCESS}{bold_text(text)}{_STYLE_END}" def bold_text(text): """Get bold text.""" - return f"{_BOLD}{text}{_END}" + return f"{_STYLE_BOLD}{text}{_STYLE_END}" def main(): From 8397a10eabdb2bba46904135cab0a3841f2547e8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 20:56:28 -0700 Subject: [PATCH 150/310] Revise DivisionError docstring --- ultimatepython/classes/exception_class.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 5cc488f0..d14cc3b5 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -18,7 +18,7 @@ class IterationError(UltimatePythonError, RuntimeError): class DivisionError(UltimatePythonError, ValueError): """Any division error that results from invalid input. - This can be complemented with the following exceptions if they + This exception can be subclassed with the following exceptions if they happen enough across the codebase: - ZeroDivisorError @@ -26,9 +26,9 @@ class DivisionError(UltimatePythonError, ValueError): - NegativeDivisorError That being said, there's a point of diminishing returns when - we design too many exceptions. It's better to design a few - that most developers handle than design many that few - developers handle. + we design too many exceptions. It's better to design a few exceptions + that most developers handle than design many exceptions that + few developers handle. """ From 45f21c825553db605c78dc73588b7cc0ec132123 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 21:18:05 -0700 Subject: [PATCH 151/310] Add commnts to exception_class --- ultimatepython/classes/exception_class.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index d14cc3b5..878f40b3 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -4,14 +4,25 @@ class UltimatePythonError(Exception): This is the base exception for the Ultimate Python study guide. One of the reasons why developers design a class like this is for consumption by downstream services and command-line tools. + + If you are designing a standalone application with few downstream + consumers, then it makes little sense to make a base class of exceptions. + Try using the existing hierarchy of builtin exception classes, which are + listed in the Python docs: + + https://docs.python.org/3/library/exceptions.html """ class IterationError(UltimatePythonError, RuntimeError): """Any error that comes while iterating through objects. - This is used in `ultimatepython.classes.iterator_class` so please do - NOT delete this class. + This class is used by the `iterator_class` module for raising exceptions + so please do NOT delete this definition. + + Notice that this class subclasses both `UltimatePythonError` and + `RuntimeError` classes. That way dependent functions can handle this + exception using either the package hierarchy or the native hierarchy. """ From 3af93ad84739888a1dfdb5004ea56b429ce5e40b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 21:36:11 -0700 Subject: [PATCH 152/310] Shorten the code in decorator lesson --- ultimatepython/advanced/decorator.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index b4d62329..db773c41 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -55,20 +55,28 @@ def run_with_any(fn): @wraps(fn) def wrapper(stringy): - """Apply wrapped function to a string or a collection.""" + """Apply wrapped function to a string or a collection. + + This looks like a policy-based engine which runs a `return` statement + if a particular set of rules is true. Otherwise it aborts. This is + an example of the Strategy design pattern. + + https://en.wikipedia.org/wiki/Strategy_pattern + + But instead of writing the logic using classes, we write the logic + using a single function that encapsulates all possible rules. + """ if isinstance(stringy, str): return fn(stringy) elif isinstance(stringy, dict): - if all(isinstance(item, str) for item in stringy.values()): - return {key: fn(value) for key, value in stringy.items()} - # Nested call on unknown data entries - return {key: wrapper(value) for key, value in stringy.items()} + all_string = all(isinstance(item, str) for item in stringy.values()) + transformer = fn if all_string else wrapper + return {key: transformer(value) for key, value in stringy.items()} elif isinstance(stringy, (list, set, tuple)): sequence_kls = type(stringy) - if all(isinstance(item, str) for item in stringy): - return sequence_kls(fn(value) for value in stringy) - # Nested call on unknown data entries - return sequence_kls(wrapper(value) for value in stringy) + all_string = all(isinstance(item, str) for item in stringy) + transformer = fn if all_string else wrapper + return sequence_kls(transformer(value) for value in stringy) raise ValueError("Found item that is not a string or a collection.") return wrapper From 8a97ef658d9ec17f022f535d89cb253cc264effd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 21:37:00 -0700 Subject: [PATCH 153/310] Change hide_content docstring --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index db773c41..bfb2960c 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -84,7 +84,7 @@ def wrapper(stringy): @run_with_any def hide_content(content): - """Hide half of the stringy content.""" + """Hide half of the string content.""" start_point = len(content) // 2 num_of_asterisks = len(content) // 2 + len(content) % 2 return content[:start_point] + "*" * num_of_asterisks From 9ef8777a881b7360e137481b92226d17c35ae82f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 21:46:21 -0700 Subject: [PATCH 154/310] Revise single word in meta_class lesson --- ultimatepython/advanced/meta_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 9b902e06..c80e55e2 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -14,7 +14,7 @@ class ModelMeta(type): it is visible to a developer and (B) add a class to a dynamic registry for further automation. - Do NOT use a metaclass if it can be done more simply with class + Do NOT use a metaclass if a task can be done more simply with class composition, class inheritance or functions. Simple code is the reason why Python is attractive for 99% of users. From 9fbdeedd954f13c60e15d4adae24c18e6d103ac1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 22:15:38 -0700 Subject: [PATCH 155/310] Rename decorator vars a bit --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index bfb2960c..f72d9e61 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -71,7 +71,7 @@ def wrapper(stringy): elif isinstance(stringy, dict): all_string = all(isinstance(item, str) for item in stringy.values()) transformer = fn if all_string else wrapper - return {key: transformer(value) for key, value in stringy.items()} + return {name: transformer(item) for name, item in stringy.items()} elif isinstance(stringy, (list, set, tuple)): sequence_kls = type(stringy) all_string = all(isinstance(item, str) for item in stringy) From adf901408ecc73113856c436597589963b0e9d8e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 23:47:33 -0700 Subject: [PATCH 156/310] Create new mro lesson --- README.md | 1 + ultimatepython/advanced/mro.py | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 ultimatepython/advanced/mro.py diff --git a/README.md b/README.md index 70715288..6d47bd1f 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. - **Advanced** - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) ## Additional resources diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py new file mode 100644 index 00000000..37c66a53 --- /dev/null +++ b/ultimatepython/advanced/mro.py @@ -0,0 +1,77 @@ +class A: + """Base class.""" + + def ping(self): + print("ping", self) + + +class B(A): + """B inherits from A.""" + + def pong(self): + print("pong", self) + + +class C(A): + """C inherits from A.""" + + def pong(self): + print("PONG", self) + + +class D(B, C): + """D inherits from B and C. + + This is what we call the diamond problem, where A has multiple child + classes that are the same as D's parent classes. Python has the MRO to + determine which method is applied when calling `super()`. + """ + + def ping(self): + print("PING", self) + + def ping_pong(self): + self.ping() + super().ping() + self.pong() + super().pong() + + +class E(C, B): + """E inherits from C and B. + + This exhibits the Python MRO as well. Notice that this was class was + created successfully without any conflicts to D. + """ + + def pong(self): + print("pong", self) + + def ping_pong(self): + self.ping() + super().ping() + self.pong() + super().pong() + + +def main(): + assert D.mro() == [D, B, C, A, object] + assert E.mro() == [E, C, B, A, object] + + d_obj = D() + d_obj.ping_pong() + + e_obj = E() + e_obj.ping_pong() + + try: + # Creating a new class F from D and E result in a type error + # because D and E have MRO outputs that cannot be merged + # together as one + type("F", (D, E), {}) + except TypeError as e: + print(e) + + +if __name__ == '__main__': + main() From 9406903820f391e9c606786efa1f21ef9b5dcc55 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 24 Aug 2020 23:55:24 -0700 Subject: [PATCH 157/310] Add comments in mro main --- ultimatepython/advanced/mro.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 37c66a53..9e521fdf 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -55,12 +55,17 @@ def ping_pong(self): def main(): + # Show how methods in class D are resolved from child to parent assert D.mro() == [D, B, C, A, object] + + # Show how methods in class E are resolved from child to parent assert E.mro() == [E, C, B, A, object] + # Show D method resolution in action d_obj = D() d_obj.ping_pong() + # Show E method resolution in action e_obj = E() e_obj.ping_pong() From ce637a7894e9172b7e686b09caa567fda2da4350 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 00:00:20 -0700 Subject: [PATCH 158/310] Add more comments to mro --- ultimatepython/advanced/mro.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 9e521fdf..0f9ae355 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -24,7 +24,11 @@ class D(B, C): This is what we call the diamond problem, where A has multiple child classes that are the same as D's parent classes. Python has the MRO to - determine which method is applied when calling `super()`. + determine which `pong` method is applied when calling `super()`. + + For more on the subject, please consult this link: + + https://www.python.org/download/releases/2.3/mro/ """ def ping(self): @@ -40,8 +44,9 @@ def ping_pong(self): class E(C, B): """E inherits from C and B. - This exhibits the Python MRO as well. Notice that this was class was - created successfully without any conflicts to D. + This exhibits the Python MRO as well. Notice that this class was + created successfully without any conflicts because of D's existence. + All is good in the world. """ def pong(self): From 0b3d8ec70b1d886b5ae0cd14f1b926ea6e850012 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 07:27:10 -0700 Subject: [PATCH 159/310] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d47bd1f..d99ead57 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ Here are the primary goals of creating this guide: **Serve as a resource** for Python newcomers who prefer to learn hands-on. This repository has a collection of standalone modules which can be run -in an IDE or a terminal. Most lines have carefully crafted comments which -guide a reader through what the programs are doing step-by-step. +in software like [PyCharm IDE](https://www.jetbrains.com/pycharm/) and +[Repl.it](https://repl.it/languages/python3). Most lines have carefully +crafted comments which guide a reader through what the programs are doing +step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without From 550e5b46b9d93d1e4219eca86ee57542ab685560 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 07:32:13 -0700 Subject: [PATCH 160/310] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d99ead57..018cb8bb 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Here are the primary goals of creating this guide: **Serve as a resource** for Python newcomers who prefer to learn hands-on. This repository has a collection of standalone modules which can be run in software like [PyCharm IDE](https://www.jetbrains.com/pycharm/) and -[Repl.it](https://repl.it/languages/python3). Most lines have carefully -crafted comments which guide a reader through what the programs are doing -step-by-step. +[Repl.it](https://repl.it/languages/python3). Even a plain old terminal +will do! Most lines have carefully crafted comments which guide a reader +through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without From 7a5fc60b745b7c8317a7906edc324fc2edd5b6d1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 07:33:44 -0700 Subject: [PATCH 161/310] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 018cb8bb..ec8ff18e 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ through it. Here are the primary goals of creating this guide: **Serve as a resource** for Python newcomers who prefer to learn hands-on. -This repository has a collection of standalone modules which can be run -in software like [PyCharm IDE](https://www.jetbrains.com/pycharm/) and -[Repl.it](https://repl.it/languages/python3). Even a plain old terminal -will do! Most lines have carefully crafted comments which guide a reader -through what the programs are doing step-by-step. +This repository has a collection of standalone modules which can be run in an IDE +like [PyCharm](https://www.jetbrains.com/pycharm/) and in the browser like +[Repl.it](https://repl.it/languages/python3). Even a plain old terminal will do! +Most lines have carefully crafted comments which guide a reader through what the +programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without From 6af9cee1763bfd75eb9e785ca4fc1cf9f1e7bfda Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 07:39:17 -0700 Subject: [PATCH 162/310] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ec8ff18e..f71bac27 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ programs are doing step-by-step. Only builtin libraries are leveraged so that these concepts can be conveyed without the overhead of domain-specific concepts. As such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. +However, reading the source code in these frameworks is inspiring and highly +encouraged if your goal is to become a true +[Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). ## Table of contents From 777ae2cf0617166c73437ef0d57ff9be30850bf3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 07:58:44 -0700 Subject: [PATCH 163/310] Refactor iterator_class to become standalone --- ultimatepython/classes/exception_class.py | 13 ----- ultimatepython/classes/iterator_class.py | 70 ++++++++++++----------- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 878f40b3..5debad9d 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -14,18 +14,6 @@ class UltimatePythonError(Exception): """ -class IterationError(UltimatePythonError, RuntimeError): - """Any error that comes while iterating through objects. - - This class is used by the `iterator_class` module for raising exceptions - so please do NOT delete this definition. - - Notice that this class subclasses both `UltimatePythonError` and - `RuntimeError` classes. That way dependent functions can handle this - exception using either the package hierarchy or the native hierarchy. - """ - - class DivisionError(UltimatePythonError, ValueError): """Any division error that results from invalid input. @@ -64,7 +52,6 @@ def divide_positive_numbers(dividend, divisor): def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in - assert issubclass(IterationError, UltimatePythonError) assert issubclass(DivisionError, UltimatePythonError) try: divide_positive_numbers(1, 0) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 0c94f8e9..6a849348 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,13 +1,30 @@ -# The benefit of writing classes (and functions for that matter) is that -# we can reuse them in other places. Specifically, we can import modules -# from the same package that this module resides in. Because we used this -# feature, this module is now dependant on other modules. Running -# this as a plain old script will not work because that results in Python -# ignoring the other modules that exist here. In order to run this module, -# we need to run it with the `-m` flag which executes a module as a script. -# See https://www.python.org/dev/peps/pep-0338/ for more details -from ultimatepython.classes.abstract_class import Employee, Engineer, Manager -from ultimatepython.classes.exception_class import IterationError +from abc import ABC + + +class Employee(ABC): + """Generic employee class. + + For this module, we're going to remove the inheritance hierarchy + in `abstract_class` and make all employees have a `direct_reports` + attribute. + """ + + def __init__(self, name, title, direct_reports): + self.name = name + self.title = title + self.direct_reports = direct_reports + + def __repr__(self): + return f"" + + +class IterationError(RuntimeError): + """Any error that comes while iterating through objects. + + Notice that this class inherits from `RuntimeError`. That way dependent + functions can handle this exception using either the package hierarchy + or the native hierarchy. + """ class EmployeeIterator: @@ -56,15 +73,11 @@ def __next__(self): raise StopIteration employee = self.employees_to_visit.pop() if employee.name in self.employees_visited: - raise IterationError("Cyclic loop detected") + raise RuntimeError("Cyclic loop detected") self.employees_visited.add(employee.name) - if isinstance(employee, Engineer): - return employee - if isinstance(employee, Manager): - for report in employee.direct_reports: - self.employees_to_visit.append(report) - return employee - raise StopIteration + for report in employee.direct_reports: + self.employees_to_visit.append(report) + return employee def employee_generator(top_employee): @@ -87,23 +100,18 @@ def employee_generator(top_employee): while len(to_visit) > 0: employee = to_visit.pop() if employee.name in visited: - raise IterationError("Cyclic loop detected") + raise RuntimeError("Cyclic loop detected") visited.add(employee.name) - if isinstance(employee, Engineer): - yield employee - elif isinstance(employee, Manager): - for report in employee.direct_reports: - to_visit.append(report) - yield employee - else: - raise StopIteration + for report in employee.direct_reports: + to_visit.append(report) + yield employee def main(): # Manager with two direct reports - manager = Manager("Max Doe", "Engineering Manager", [ - Engineer("John Doe", "Software Engineer", "Android"), - Engineer("Jane Doe", "Software Engineer", "iOS") + manager = Employee("Max Doe", "Engineering Manager", [ + Employee("John Doe", "Software Engineer", []), + Employee("Jane Doe", "Software Engineer", []) ]) # We should provide the same three employees in the same order regardless @@ -114,8 +122,6 @@ def main(): # Make sure that the employees are who we expect them to be assert all(isinstance(emp, Employee) for emp in employees) - assert isinstance(employees[0], Manager) - assert all(isinstance(emp, Engineer) for emp in employees[1:]) print(employees) From 99306d6e0bf9790d0bee8ea4b6d2d4368b528251 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:11:16 -0700 Subject: [PATCH 164/310] Add additional issubclass in exception_class --- ultimatepython/classes/exception_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 5debad9d..d3569134 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -53,6 +53,7 @@ def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in assert issubclass(DivisionError, UltimatePythonError) + assert issubclass(DivisionError, ValueError) try: divide_positive_numbers(1, 0) except DivisionError as e: From 95ddf9bba4bd044b422f463978a315de1dcf650f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:16:02 -0700 Subject: [PATCH 165/310] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f71bac27..9e7a1746 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Here are the primary goals of creating this guide: **Serve as a resource** for Python newcomers who prefer to learn hands-on. This repository has a collection of standalone modules which can be run in an IDE like [PyCharm](https://www.jetbrains.com/pycharm/) and in the browser like -[Repl.it](https://repl.it/languages/python3). Even a plain old terminal will do! -Most lines have carefully crafted comments which guide a reader through what the -programs are doing step-by-step. +[Repl.it](https://repl.it/languages/python3). Even a plain old terminal will work +with the examples. Most lines have carefully crafted comments which guide a reader +through what the programs are doing step-by-step. **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without From 2210c2b4f6789e5d890a02e649400f03608e1b72 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:19:18 -0700 Subject: [PATCH 166/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e7a1746..2baf78d9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ companies and as an open-source contributor of repositories like [Celery](https://github.com/celery/celery) and [Full Stack Python](https://github.com/mattmakai/fullstackpython.com). I look forward to seeing more people learn Python and pursue their passions -through it. +through it. :mortar_board: ## Goals From 9531ccad30c7d78e40cdf5d6ec4f4abdf415ae45 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:22:34 -0700 Subject: [PATCH 167/310] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2baf78d9..961538be 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Ultimate Python study guide for newcomers and professionals alike. :snake: :snak print("Ultimate Python study guide") ``` -## Motivation +## Motivation :mortar_board: I created a GitHub repo to share what I've learned about core Python over the past 5+ years of using it as a college graduate, an employee at large-scale @@ -14,9 +14,9 @@ companies and as an open-source contributor of repositories like [Celery](https://github.com/celery/celery) and [Full Stack Python](https://github.com/mattmakai/fullstackpython.com). I look forward to seeing more people learn Python and pursue their passions -through it. :mortar_board: +through it. -## Goals +## Goals :star: Here are the primary goals of creating this guide: @@ -35,7 +35,7 @@ However, reading the source code in these frameworks is inspiring and highly encouraged if your goal is to become a true [Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). -## Table of contents +## Table of contents :blue_book: - **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) - **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) @@ -62,7 +62,7 @@ encouraged if your goal is to become a true - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) -## Additional resources +## Additional resources :bookmark: As you start applying Python fundamentals to the real world, read over code examples and project ideas from other well-regarded resources. From 6f1b0a3be39de377006daa8bf82fe4ae1e9e534c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:24:12 -0700 Subject: [PATCH 168/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 961538be..8b549c2c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ encouraged if your goal is to become a true - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) -## Additional resources :bookmark: +## Additional resources :earth_americas: As you start applying Python fundamentals to the real world, read over code examples and project ideas from other well-regarded resources. From 40f5bae18658d48ee51b4ffbb65207ba4567a018 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:26:29 -0700 Subject: [PATCH 169/310] Remove ABC from iterator_class --- ultimatepython/classes/iterator_class.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 6a849348..549d8a10 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,7 +1,4 @@ -from abc import ABC - - -class Employee(ABC): +class Employee: """Generic employee class. For this module, we're going to remove the inheritance hierarchy From 080f8c429d0b33ca0e2be9d952575c4d9cd5925b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:33:51 -0700 Subject: [PATCH 170/310] Add more context in iterator_class --- ultimatepython/classes/iterator_class.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 549d8a10..39c7c635 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -91,6 +91,11 @@ def employee_generator(top_employee): In this case, examples of such requirements are tasks like encrypting the employee's username, running statistics on iterated employees or excluding the reports under a particular set of managers. + + For more on the subject of using a function versus a class, check + out this post from Microsoft Developer Blogs: + + https://devblogs.microsoft.com/python/idiomatic-python-functions-versus-classes/ """ to_visit = [top_employee] visited = set() From abb2360d6c77ab05056af475ae9034fa9e899aad Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:36:44 -0700 Subject: [PATCH 171/310] Fix ValueError message in decorator --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index f72d9e61..9493152d 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -77,7 +77,7 @@ def wrapper(stringy): all_string = all(isinstance(item, str) for item in stringy) transformer = fn if all_string else wrapper return sequence_kls(transformer(value) for value in stringy) - raise ValueError("Found item that is not a string or a collection.") + raise ValueError("Found item that is neither a string nor a collection.") return wrapper From 913b56483ee252e102c61bdbd915082de94fdf99 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:39:13 -0700 Subject: [PATCH 172/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b549c2c..c54d78d2 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ However, reading the source code in these frameworks is inspiring and highly encouraged if your goal is to become a true [Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). -## Table of contents :blue_book: +## Table of contents :books: - **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) - **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) From ee9a1aa2a4b9fc8f4a2cf4523a5504805cf4cad7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:41:55 -0700 Subject: [PATCH 173/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c54d78d2..821c567a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ companies and as an open-source contributor of repositories like I look forward to seeing more people learn Python and pursue their passions through it. -## Goals :star: +## Goals :trophy: Here are the primary goals of creating this guide: From 1d0ce63b46f0a7f807607dfe8a62991ffa1b71a2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 08:46:02 -0700 Subject: [PATCH 174/310] Add motivation content to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 821c567a..f3cec373 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ print("Ultimate Python study guide") ## Motivation :mortar_board: -I created a GitHub repo to share what I've learned about core Python over the -past 5+ years of using it as a college graduate, an employee at large-scale -companies and as an open-source contributor of repositories like +I created a GitHub repo to share what I've learned about [core Python](https://www.python.org/) +over the past 5+ years of using it as a college graduate, an employee at +large-scale companies and as an open-source contributor of repositories like [Celery](https://github.com/celery/celery) and [Full Stack Python](https://github.com/mattmakai/fullstackpython.com). I look forward to seeing more people learn Python and pursue their passions From 2b96e0a317f1e0d03565038725bed570ac6192e1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:03:00 -0700 Subject: [PATCH 175/310] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f3cec373..a4b1a73f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Ultimate Python study guide +![](https://img.shields.io/github/followers/huangsam) +![](https://img.shields.io/github/stars/huangsam/ultimate-python) +![](https://img.shields.io/github/license/huangsam/ultimate-python) + Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: ```python From 3a6eba9d94266c65848d4267516df265ff4afb38 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:18:24 -0700 Subject: [PATCH 176/310] Add .circleci/config.yml --- .circleci/config.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..36e0b066 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,21 @@ +version: 2.1 + +orbs: + python: circleci/python@0.3.2 + +jobs: + build-and-test: + executor: python/default + steps: + - checkout + - run: + command: pip install . + name: Build + - run: + command: python runner.py + name: Test + +workflows: + main: + jobs: + - build-and-test From 7eec6821a33443fb6862d706330a833ae869450c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:34:13 -0700 Subject: [PATCH 177/310] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a4b1a73f..bf369b8c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Ultimate Python study guide +![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) ![](https://img.shields.io/github/followers/huangsam) ![](https://img.shields.io/github/stars/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) From f64e400118d7e3b6f595b2832f7b89d85a9a0d1b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:35:25 -0700 Subject: [PATCH 178/310] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index bf369b8c..1b7a432c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Ultimate Python study guide -![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) -![](https://img.shields.io/github/followers/huangsam) ![](https://img.shields.io/github/stars/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) +![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From 6579734672e3a4963cff113bd5351140380edb3c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:36:47 -0700 Subject: [PATCH 179/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b7a432c..bf0e3dde 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Ultimate Python study guide +![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) ![](https://img.shields.io/github/stars/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) -![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From 12b1329e65cfdd9cfa4ba912b14d8951b91872bc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 09:38:08 -0700 Subject: [PATCH 180/310] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index bf0e3dde..7954f521 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Ultimate Python study guide ![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) -![](https://img.shields.io/github/stars/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From ab08be4604e6d77152c21b379e430f47d86aef7e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 13:21:10 -0700 Subject: [PATCH 181/310] Add assertions and print statements --- ultimatepython/data_structures/list.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 24b0cea7..f3ffe398 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -1,11 +1,17 @@ def main(): - # This is a list of one-letter strings where + # This is a list of strings where # "a" is a string at index 0 and # "e" is a string at index 4 letters = ["a", "b", "c", "d", "e"] + print("Letters", letters) + for letter in letters: + # Each of the strings is one character assert len(letter) == 1 + # Each of the strings is a letter + assert letter.isalpha() + # You can get a subset of letters with the range selector assert letters[1:] == ["b", "c", "d", "e"] assert letters[:-1] == ["a", "b", "c", "d"] @@ -17,6 +23,7 @@ def main(): # 1 is an integer at index 0 # 5 is an integer at index 4 numbers = [1, 2, 3, 4, 5] + print("Numbers", numbers) # Print letters and numbers side-by-side using the `zip` function. Notice # that we pair the letter at index 0 with the number at index 0, and From dcc0d96c66160b7d2000eee4fcd6859f9065cd54 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 13:27:18 -0700 Subject: [PATCH 182/310] Use io.open instead of default open --- runner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runner.py b/runner.py index daf938f5..986ec878 100644 --- a/runner.py +++ b/runner.py @@ -1,3 +1,4 @@ +import io import sys from contextlib import contextmanager from importlib import import_module @@ -19,7 +20,7 @@ def no_stdout(): """Silence standard output with /dev/null.""" save_stdout = sys.stdout - with open(devnull, "w") as dev_null: + with io.open(devnull, "w") as dev_null: sys.stdout = dev_null yield sys.stdout = save_stdout From 6bac1b26e6c66a47bf9e7297d1b6127e4bd18264 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 13:39:39 -0700 Subject: [PATCH 183/310] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7954f521..06c00c7a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Ultimate Python study guide for newcomers and professionals alike. :snake: :snak print("Ultimate Python study guide") ``` -## Motivation :mortar_board: +## Motivation I created a GitHub repo to share what I've learned about [core Python](https://www.python.org/) over the past 5+ years of using it as a college graduate, an employee at @@ -17,20 +17,20 @@ large-scale companies and as an open-source contributor of repositories like [Celery](https://github.com/celery/celery) and [Full Stack Python](https://github.com/mattmakai/fullstackpython.com). I look forward to seeing more people learn Python and pursue their passions -through it. +through it. :mortar_board: -## Goals :trophy: +## Goals Here are the primary goals of creating this guide: -**Serve as a resource** for Python newcomers who prefer to learn hands-on. +:trophy: **Serve as a resource** for Python newcomers who prefer to learn hands-on. This repository has a collection of standalone modules which can be run in an IDE like [PyCharm](https://www.jetbrains.com/pycharm/) and in the browser like [Repl.it](https://repl.it/languages/python3). Even a plain old terminal will work with the examples. Most lines have carefully crafted comments which guide a reader through what the programs are doing step-by-step. -**Serve as a pure guide** for those who want to revisit core Python concepts. +:trophy: **Serve as a pure guide** for those who want to revisit core Python concepts. Only builtin libraries are leveraged so that these concepts can be conveyed without the overhead of domain-specific concepts. As such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. @@ -38,10 +38,10 @@ However, reading the source code in these frameworks is inspiring and highly encouraged if your goal is to become a true [Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). -## Table of contents :books: +## Table of contents -- **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) -- **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) +- **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) +- **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) - **Language Mechanics** - **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) @@ -65,10 +65,10 @@ encouraged if your goal is to become a true - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) -## Additional resources :earth_americas: +## Additional resources As you start applying Python fundamentals to the real world, read over -code examples and project ideas from other well-regarded resources. +code examples and project ideas from other well-regarded resources. :earth_americas: Here are some GitHub repositories to get started with: From b56aa55cd03f96180f30eee211d34b7be2baaa6e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 14:02:27 -0700 Subject: [PATCH 184/310] Add extra range slices in list --- ultimatepython/data_structures/list.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index f3ffe398..d021a3fb 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -16,7 +16,9 @@ def main(): assert letters[1:] == ["b", "c", "d", "e"] assert letters[:-1] == ["a", "b", "c", "d"] assert letters[1:-2] == ["b", "c"] + assert letters[0:3:2] == ["a", "c"] assert letters[::2] == ["a", "c", "e"] + assert letters[::-2] == ["e", "c", "a"] assert letters[::-1] == ["e", "d", "c", "b", "a"] # This is a list of integers where From 5a7e57c56777588509202cd74b1b9c2c6327b33c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 14:13:52 -0700 Subject: [PATCH 185/310] Make ToC less nested --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 06c00c7a..ed79210b 100644 --- a/README.md +++ b/README.md @@ -40,30 +40,30 @@ encouraged if your goal is to become a true ## Table of contents -- **Design Philosophy**: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) -- **Style Guide**: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) -- **Language Mechanics** - - **Syntax** - - Variable: [Built-in literals](ultimatepython/syntax/variable.py) - - Expression: [Numeric operations](ultimatepython/syntax/expression.py) - - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) - - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) - - Function: [def | lambda](ultimatepython/syntax/function.py) - - **Data Structures** - - List: [List operations](ultimatepython/data_structures/list.py) - - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) - - Set: [Set operations](ultimatepython/data_structures/set.py) - - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) - - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) - - **Classes** - - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) - - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) - - **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) +1. **About Python** + - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) + - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) +2. **Syntax** + - Variable: [Built-in literals](ultimatepython/syntax/variable.py) + - Expression: [Numeric operations](ultimatepython/syntax/expression.py) + - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) + - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) + - Function: [def | lambda](ultimatepython/syntax/function.py) +3. **Data Structures** + - List: [List operations](ultimatepython/data_structures/list.py) + - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) + - Set: [Set operations](ultimatepython/data_structures/set.py) + - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) + - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) +4. **Classes** + - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) + - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) + - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) +5. **Advanced** + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) ## Additional resources From 06c21565f59cd0e5a063cd0db79d75c9eaaf5aa9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 14:21:57 -0700 Subject: [PATCH 186/310] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed79210b..894a4d49 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ encouraged if your goal is to become a true ## Table of contents 1. **About Python** + - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) 2. **Syntax** From d3857dab13de2a9f6bfe1a4f6a9bbee808187a00 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 15:08:48 -0700 Subject: [PATCH 187/310] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 894a4d49..14caed1f 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ with the examples. Most lines have carefully crafted comments which guide a read through what the programs are doing step-by-step. :trophy: **Serve as a pure guide** for those who want to revisit core Python concepts. -Only builtin libraries are leveraged so that these concepts can be conveyed without -the overhead of domain-specific concepts. As such, popular open-source libraries -and frameworks (i.e. `sqlalchemy`, `requests`, `pandas`) are not installed. -However, reading the source code in these frameworks is inspiring and highly -encouraged if your goal is to become a true +Only [builtin libraries](https://docs.python.org/3/library/) are leveraged so that +these concepts can be conveyed without the overhead of domain-specific concepts. As +such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests`, +`pandas`) are not installed. However, reading the source code in these frameworks is +inspiring and highly encouraged if your goal is to become a true [Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). ## Table of contents From 4dec0c84f41cb43e4a7235935b82690f21a624a8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 19:08:31 -0700 Subject: [PATCH 188/310] Improve sentences in variable lesson --- ultimatepython/syntax/variable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 9417cacf..00b0485c 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -5,9 +5,9 @@ def main(): c = True d = "hello" - # Notice that each type is a `class`. Each of the variables above refers - # to an `instance` of the class it belongs to. For instance, `a` is of type - # `int` which is a `class` + # Notice that each type is a class. Each of the variables above refers + # to an instance of the class it belongs to. For example, `a` refers to + # the value `1` which belongs to the integer type `int` a_type = type(a) # b_type = type(b) # c_type = type(c) # From 62053200bf56f1300eff016fad38a663a4456c01 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 19:15:40 -0700 Subject: [PATCH 189/310] Fixup the README content --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 14caed1f..7976b0ba 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ This repository has a collection of standalone modules which can be run in an ID like [PyCharm](https://www.jetbrains.com/pycharm/) and in the browser like [Repl.it](https://repl.it/languages/python3). Even a plain old terminal will work with the examples. Most lines have carefully crafted comments which guide a reader -through what the programs are doing step-by-step. +through what the programs are doing step-by-step. Users are encouraged to modify +source code anywhere, as long as the `main` routine in each program +[does not break](runner.py). :trophy: **Serve as a pure guide** for those who want to revisit core Python concepts. Only [builtin libraries](https://docs.python.org/3/library/) are leveraged so that @@ -68,8 +70,8 @@ inspiring and highly encouraged if your goal is to become a true ## Additional resources -As you start applying Python fundamentals to the real world, read over -code examples and project ideas from other well-regarded resources. :earth_americas: +As you start applying Python fundamentals to the real world, read code examples +and project ideas from other well-regarded resources. :earth_americas: Here are some GitHub repositories to get started with: From 69dd29dc617c518d65ccb09647b403d758142143 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 19:24:21 -0700 Subject: [PATCH 190/310] Modify exception_class --- ultimatepython/classes/exception_class.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index d3569134..ec1ed1cd 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -1,9 +1,9 @@ -class UltimatePythonError(Exception): - """Base class of errors for ultimate-python package. +class CustomError(Exception): + """Custom class of errors. - This is the base exception for the Ultimate Python study guide. One of - the reasons why developers design a class like this is for consumption - by downstream services and command-line tools. + This is a custom base exception for any issues that arise in this module. + One of the reasons why developers design a class like this is for + consumption by downstream services and command-line tools. If you are designing a standalone application with few downstream consumers, then it makes little sense to make a base class of exceptions. @@ -14,7 +14,7 @@ class UltimatePythonError(Exception): """ -class DivisionError(UltimatePythonError, ValueError): +class DivisionError(CustomError): """Any division error that results from invalid input. This exception can be subclassed with the following exceptions if they @@ -52,8 +52,7 @@ def divide_positive_numbers(dividend, divisor): def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in - assert issubclass(DivisionError, UltimatePythonError) - assert issubclass(DivisionError, ValueError) + assert issubclass(DivisionError, CustomError) try: divide_positive_numbers(1, 0) except DivisionError as e: From 5c8408f5a0f2d920bd0c7ee6bf84af908583bddd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 19:53:34 -0700 Subject: [PATCH 191/310] Improve conditional content --- ultimatepython/syntax/conditional.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index 1b0cf51e..ead355ae 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -18,10 +18,10 @@ def main(): else: print("Math wins otherwise") # run - # The `else` statement also run once every other `if` and `elif` condition - # fails. Notice that multiple lines get skipped, and that all of the - # conditions could have been compressed to `x_add_two != 3` - # for simplicity. In this case, less is more + # The `else` statement also run once all other `if` and `elif` conditions + # fail. Notice that multiple lines get skipped, and that all of the + # conditions could have been compressed to `x_add_two != 3` for + # simplicity. In this case, less logic results in more clarity if x_add_two == 1: print("Nope not this one...") # skip elif x_add_two == 2: From febe5b191adb045831e579092acb77da60dfe77e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:21:17 -0700 Subject: [PATCH 192/310] Create new async lesson --- README.md | 1 + ultimatepython/advanced/async.py | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 ultimatepython/advanced/async.py diff --git a/README.md b/README.md index 7976b0ba..f7784143 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ inspiring and highly encouraged if your goal is to become a true - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) + - Asyncio: [async | await](ultimatepython/advanced/async.py) ## Additional resources diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py new file mode 100644 index 00000000..d3f5591e --- /dev/null +++ b/ultimatepython/advanced/async.py @@ -0,0 +1,50 @@ +import asyncio +from dataclasses import dataclass +from datetime import datetime +from uuid import uuid4 + + +# Define a tuple with named records +@dataclass(order=True) +class TaskInfo: + tid: str + queued_at: datetime + started_at: datetime + + +def current_time(): + """Return current time that is timezone-naive.""" + return datetime.now() + + +async def start_task(delay, task_id): + """Start task_id after a certain delay in seconds.""" + queue_time = current_time() + print(f"{queue_time} -> Queued task {task_id[:16]}...") + await asyncio.sleep(delay) + start_time = current_time() + print(f"{start_time} -> Started task {task_id[:16]}...") + return TaskInfo(tid=task_id, queued_at=queue_time, started_at=start_time) + + +async def start_batch(): + """Create a batch of tasks them concurrently.""" + print(f"{current_time()} -> Send initiation email") + + tasks = [asyncio.create_task(start_task(i * .01, f"{uuid4()}")) + for i in range(1, 5)] + + # Gather all tasks for batch completion + task_info_records = await asyncio.gather(*tasks) + for result in task_info_records: + assert result.queued_at < result.started_at + + print(f"{current_time()} -> Send completion email") + + +def main(): + asyncio.run(start_batch()) + + +if __name__ == "__main__": + main() From 87949634dc4cbe0d732cc7a2caac9511a510b5a0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:25:03 -0700 Subject: [PATCH 193/310] Update async lesson --- ultimatepython/advanced/async.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index d3f5591e..3c645c7b 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -6,7 +6,7 @@ # Define a tuple with named records @dataclass(order=True) -class TaskInfo: +class TaskRecord: tid: str queued_at: datetime started_at: datetime @@ -24,20 +24,20 @@ async def start_task(delay, task_id): await asyncio.sleep(delay) start_time = current_time() print(f"{start_time} -> Started task {task_id[:16]}...") - return TaskInfo(tid=task_id, queued_at=queue_time, started_at=start_time) + return TaskRecord(task_id, queue_time, start_time) async def start_batch(): """Create a batch of tasks them concurrently.""" print(f"{current_time()} -> Send initiation email") - tasks = [asyncio.create_task(start_task(i * .01, f"{uuid4()}")) + tasks = [asyncio.create_task(start_task(i * .01, uuid4().hex)) for i in range(1, 5)] # Gather all tasks for batch completion - task_info_records = await asyncio.gather(*tasks) - for result in task_info_records: - assert result.queued_at < result.started_at + task_records = await asyncio.gather(*tasks) + for record in task_records: + assert record.queued_at < record.started_at print(f"{current_time()} -> Send completion email") From 07df82e985073a75fdf2127c0e428d6eb7b021ca Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:29:28 -0700 Subject: [PATCH 194/310] Refine async lesson again --- ultimatepython/advanced/async.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 3c645c7b..2a4ab831 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -5,7 +5,7 @@ # Define a tuple with named records -@dataclass(order=True) +@dataclass class TaskRecord: tid: str queued_at: datetime @@ -20,26 +20,26 @@ def current_time(): async def start_task(delay, task_id): """Start task_id after a certain delay in seconds.""" queue_time = current_time() - print(f"{queue_time} -> Queued task {task_id[:16]}...") + print(f"{queue_time} -> Queue task {task_id[:16]}...") await asyncio.sleep(delay) start_time = current_time() - print(f"{start_time} -> Started task {task_id[:16]}...") + print(f"{start_time} -> Start task {task_id[:16]}...") return TaskRecord(task_id, queue_time, start_time) async def start_batch(): - """Create a batch of tasks them concurrently.""" - print(f"{current_time()} -> Send initiation email") + """Start a batch of tasks concurrently.""" + print(f"{current_time()} -> Send kickoff email") tasks = [asyncio.create_task(start_task(i * .01, uuid4().hex)) for i in range(1, 5)] - # Gather all tasks for batch completion + # Gather all tasks for batch start task_records = await asyncio.gather(*tasks) for record in task_records: assert record.queued_at < record.started_at - print(f"{current_time()} -> Send completion email") + print(f"{current_time()} -> Send confirmation email") def main(): From ecf6fb4565ae1d1d69077f08adf987e451a9318e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:35:35 -0700 Subject: [PATCH 195/310] Use namedtuple instead --- ultimatepython/advanced/async.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 2a4ab831..981029cf 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -1,15 +1,11 @@ import asyncio -from dataclasses import dataclass from datetime import datetime +from collections import namedtuple from uuid import uuid4 # Define a tuple with named records -@dataclass -class TaskRecord: - tid: str - queued_at: datetime - started_at: datetime +TaskRecord = namedtuple("TaskRecord", ["tid", "queued_at", "started_at"]) def current_time(): From 6394d43debea324be8a291361ee984df32fe6d18 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:37:21 -0700 Subject: [PATCH 196/310] Fix spacing in async lesson --- ultimatepython/advanced/async.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 981029cf..b0e3551c 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -3,7 +3,6 @@ from collections import namedtuple from uuid import uuid4 - # Define a tuple with named records TaskRecord = namedtuple("TaskRecord", ["tid", "queued_at", "started_at"]) From 2a1d332891abe3aea759f3de483124d601f6f916 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:45:13 -0700 Subject: [PATCH 197/310] Sort imports in async lesson --- ultimatepython/advanced/async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index b0e3551c..f071357c 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -1,6 +1,6 @@ import asyncio -from datetime import datetime from collections import namedtuple +from datetime import datetime from uuid import uuid4 # Define a tuple with named records From 74782993848e0aa2d6935a6b36d2e52b59b3d5cf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 22:57:15 -0700 Subject: [PATCH 198/310] Confirm usage of Python 3.7 features --- setup.py | 1 - ultimatepython/advanced/async.py | 22 ++++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 773ea266..7d5f56f3 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ description="Ultimate Python study guide for newcomers and professionals alike.", classifiers=[ "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ], diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index f071357c..e43eb34d 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -1,10 +1,16 @@ import asyncio -from collections import namedtuple +from dataclasses import dataclass from datetime import datetime from uuid import uuid4 -# Define a tuple with named records -TaskRecord = namedtuple("TaskRecord", ["tid", "queued_at", "started_at"]) + +@dataclass +class TaskRecord: + """Task record with useful metadata.""" + + tid: int + queued_at: datetime + started_at: datetime def current_time(): @@ -23,7 +29,15 @@ async def start_task(delay, task_id): async def start_batch(): - """Start a batch of tasks concurrently.""" + """Start a batch of tasks concurrently. + + Each item in the `tasks` list is a `Task` which is an instance of + a `Future`. The `Task` instance was created by providing a coroutine + instance from `start_task` into the `create_task` function. + + After awaiting the list of tasks, we get a list of `TaskRecord` items + with characteristics that we expect. + """ print(f"{current_time()} -> Send kickoff email") tasks = [asyncio.create_task(start_task(i * .01, uuid4().hex)) From 9dfe965b0ce6cb23fa83fe47bd179a2b031eacef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 23:05:04 -0700 Subject: [PATCH 199/310] Rename task to job for clarity --- ultimatepython/advanced/async.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index e43eb34d..5f142247 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -5,8 +5,8 @@ @dataclass -class TaskRecord: - """Task record with useful metadata.""" +class JobRecord: + """Job record with useful metadata.""" tid: int queued_at: datetime @@ -18,34 +18,35 @@ def current_time(): return datetime.now() -async def start_task(delay, task_id): +async def start_job(delay, job_id): """Start task_id after a certain delay in seconds.""" queue_time = current_time() - print(f"{queue_time} -> Queue task {task_id[:16]}...") + print(f"{queue_time} -> Queue job {job_id[:16]}...") await asyncio.sleep(delay) start_time = current_time() - print(f"{start_time} -> Start task {task_id[:16]}...") - return TaskRecord(task_id, queue_time, start_time) + print(f"{start_time} -> Start job {job_id[:16]}...") + return JobRecord(job_id, queue_time, start_time) async def start_batch(): - """Start a batch of tasks concurrently. + """Start a batch of jobs concurrently. Each item in the `tasks` list is a `Task` which is an instance of a `Future`. The `Task` instance was created by providing a coroutine - instance from `start_task` into the `create_task` function. + instance from `start_job` into the `create_task` function. - After awaiting the list of tasks, we get a list of `TaskRecord` items + After awaiting the list of tasks, we get a list of `JobRecord` items with characteristics that we expect. """ print(f"{current_time()} -> Send kickoff email") - tasks = [asyncio.create_task(start_task(i * .01, uuid4().hex)) + tasks = [asyncio.create_task(start_job(i * .01, uuid4().hex)) for i in range(1, 5)] # Gather all tasks for batch start - task_records = await asyncio.gather(*tasks) - for record in task_records: + job_records = await asyncio.gather(*tasks) + for record in job_records: + assert isinstance(record, JobRecord) assert record.queued_at < record.started_at print(f"{current_time()} -> Send confirmation email") From 849d54a3d1011d84392edaeba3da03cb28c5a0d9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 23:12:49 -0700 Subject: [PATCH 200/310] Add more comments into start_batch --- ultimatepython/advanced/async.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 5f142247..649db138 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -31,19 +31,26 @@ async def start_job(delay, job_id): async def start_batch(): """Start a batch of jobs concurrently. - Each item in the `tasks` list is a `Task` which is an instance of - a `Future`. The `Task` instance was created by providing a coroutine - instance from `start_job` into the `create_task` function. + Each item in the `tasks` list is a `asyncio.Task` instance. Each task + was created by passing a coroutine instance (created by `start_job`) + into the `asyncio.create_task` function. After awaiting the list of tasks, we get a list of `JobRecord` items - with characteristics that we expect. + with reasonable timestamps for queueing and starting. """ print(f"{current_time()} -> Send kickoff email") tasks = [asyncio.create_task(start_job(i * .01, uuid4().hex)) for i in range(1, 5)] - # Gather all tasks for batch start + # All tasks are instances of Task. They are also instances of Future + # which is important because their completions can be deferred as + # we will see in the next statement + assert all(isinstance(task, asyncio.Task) + and isinstance(task, asyncio.Future) + for task in tasks) + + # Gather all `Task` instances for batch start job_records = await asyncio.gather(*tasks) for record in job_records: assert isinstance(record, JobRecord) From 9a3fedd6305716366cc94a1bcc4a63a39b8b32cb Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 23:14:12 -0700 Subject: [PATCH 201/310] Add Python 3.9 classifier to setup --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7d5f56f3..68fef651 100644 --- a/setup.py +++ b/setup.py @@ -8,5 +8,6 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], ) From 58e3ec0194676de3d213fcfc549009a24da004a5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 23:21:48 -0700 Subject: [PATCH 202/310] Refine disclaimer in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7784143..e586b850 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ like [PyCharm](https://www.jetbrains.com/pycharm/) and in the browser like [Repl.it](https://repl.it/languages/python3). Even a plain old terminal will work with the examples. Most lines have carefully crafted comments which guide a reader through what the programs are doing step-by-step. Users are encouraged to modify -source code anywhere, as long as the `main` routine in each program -[does not break](runner.py). +source code anywhere as long as the `main` routines are not deleted and +[run successfully](runner.py) after each change. :trophy: **Serve as a pure guide** for those who want to revisit core Python concepts. Only [builtin libraries](https://docs.python.org/3/library/) are leveraged so that From 734faf0cf3c1f81700e9814b4694ca37b33f18b7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 25 Aug 2020 23:24:04 -0700 Subject: [PATCH 203/310] Fix term in decorator lesson --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 9493152d..50e064ac 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -12,7 +12,7 @@ def header_section(): is printed out before proceeding with the function call at the point of yielding. - Also notice that `header_section` is a generator that is wrapped by + Also notice that `header_section` is a coroutine that is wrapped by `contextmanager`. The `contextmanager` handles entering and exiting a section of code without defining a full-blown class to handle `__enter__` and `__exit__` use cases. From df2d8c3af25e5564eeb4b05e597ad7f9742a0af9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 26 Aug 2020 19:33:49 -0700 Subject: [PATCH 204/310] Update decorator.py --- ultimatepython/advanced/decorator.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 50e064ac..f2edd60b 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -7,21 +7,17 @@ @contextmanager def header_section(): """Print header line first before running anything. - Notice a context manager is used so that we enter a block where a header is printed out before proceeding with the function call at the point of yielding. - Also notice that `header_section` is a coroutine that is wrapped by `contextmanager`. The `contextmanager` handles entering and exiting a section of code without defining a full-blown class to handle `__enter__` and `__exit__` use cases. - There are many more use cases for context managers, like writing / reading data from a file. Another one is protecting database integrity while sending CREATE / UPDATE / DELETE statements over the network. For more on how context managers work, please consult the Python docs for more information. - https://docs.python.org/3/library/contextlib.html """ print(_HEADER) @@ -30,53 +26,38 @@ def header_section(): def run_with_any(fn): """Run with a string or a collection with strings exclusively. - We define a custom decorator that allows one to convert a function whose input is a single string into a function whose input can be many things. - A function decorator consists of the following: - - An input function to run with - A wrapper function that uses the input function - The `wrapper` does not need to accept the input function as a parameter because it can get that from its parent `run_with_any`. Furthermore, the data that wrapper receives does NOT have to be the same as the data that the input function needs to accept. - The formal specification for function decorators is here: - https://www.python.org/dev/peps/pep-0318/ - The formal specification for class decorators is here: - https://www.python.org/dev/peps/pep-3129/ """ @wraps(fn) def wrapper(stringy): """Apply wrapped function to a string or a collection. - This looks like a policy-based engine which runs a `return` statement if a particular set of rules is true. Otherwise it aborts. This is an example of the Strategy design pattern. - https://en.wikipedia.org/wiki/Strategy_pattern - But instead of writing the logic using classes, we write the logic using a single function that encapsulates all possible rules. """ if isinstance(stringy, str): return fn(stringy) elif isinstance(stringy, dict): - all_string = all(isinstance(item, str) for item in stringy.values()) - transformer = fn if all_string else wrapper - return {name: transformer(item) for name, item in stringy.items()} + return {name: wrapper(item) for name, item in stringy.items()} elif isinstance(stringy, (list, set, tuple)): sequence_kls = type(stringy) - all_string = all(isinstance(item, str) for item in stringy) - transformer = fn if all_string else wrapper - return sequence_kls(transformer(value) for value in stringy) + return sequence_kls(wrapper(value) for value in stringy) raise ValueError("Found item that is neither a string nor a collection.") return wrapper From a835d78825bf65a2b23a70a84422bc491c253f5d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 26 Aug 2020 19:36:34 -0700 Subject: [PATCH 205/310] Restore line breaks in decorator --- ultimatepython/advanced/decorator.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index f2edd60b..c890327b 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -7,17 +7,21 @@ @contextmanager def header_section(): """Print header line first before running anything. + Notice a context manager is used so that we enter a block where a header is printed out before proceeding with the function call at the point of yielding. + Also notice that `header_section` is a coroutine that is wrapped by `contextmanager`. The `contextmanager` handles entering and exiting a section of code without defining a full-blown class to handle `__enter__` and `__exit__` use cases. + There are many more use cases for context managers, like writing / reading data from a file. Another one is protecting database integrity while sending CREATE / UPDATE / DELETE statements over the network. For more on how context managers work, please consult the Python docs for more information. + https://docs.python.org/3/library/contextlib.html """ print(_HEADER) @@ -26,28 +30,39 @@ def header_section(): def run_with_any(fn): """Run with a string or a collection with strings exclusively. + We define a custom decorator that allows one to convert a function whose input is a single string into a function whose input can be many things. + A function decorator consists of the following: + - An input function to run with - A wrapper function that uses the input function + The `wrapper` does not need to accept the input function as a parameter because it can get that from its parent `run_with_any`. Furthermore, the data that wrapper receives does NOT have to be the same as the data that the input function needs to accept. + The formal specification for function decorators is here: + https://www.python.org/dev/peps/pep-0318/ + The formal specification for class decorators is here: + https://www.python.org/dev/peps/pep-3129/ """ @wraps(fn) def wrapper(stringy): """Apply wrapped function to a string or a collection. + This looks like a policy-based engine which runs a `return` statement if a particular set of rules is true. Otherwise it aborts. This is an example of the Strategy design pattern. + https://en.wikipedia.org/wiki/Strategy_pattern + But instead of writing the logic using classes, we write the logic using a single function that encapsulates all possible rules. """ From 16833f3ba6ecdb2ae1e74672654aada0928f7457 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 10:17:46 -0700 Subject: [PATCH 206/310] Create new weak_ref lesson --- README.md | 1 + ultimatepython/advanced/weak_ref.py | 100 ++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 ultimatepython/advanced/weak_ref.py diff --git a/README.md b/README.md index e586b850..a2d0c168 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ inspiring and highly encouraged if your goal is to become a true - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) - Asyncio: [async | await](ultimatepython/advanced/async.py) + - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) ## Additional resources diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py new file mode 100644 index 00000000..951a2fe7 --- /dev/null +++ b/ultimatepython/advanced/weak_ref.py @@ -0,0 +1,100 @@ +import weakref +from datetime import datetime +from uuid import uuid4 + +# Module-level constants +_PROVIDER = "aws" +_APPS = ["yelp", "pinterest", "uber", "twitter"] +_COMPONENTS = ("db", "web", "cache") + + +def log(message, log_level="info"): + """Log a message similar to that of web servers.""" + print(f"{datetime.now()} [{log_level}] - {message}") + + +class Server: + """General server.""" + + @classmethod + def create(cls, role, provider=_PROVIDER): + return cls(uuid4().hex, provider, role) + + def __init__(self, ssid, provider, role): + self.ssid = ssid + self.provider = provider + self.role = role + + def __repr__(self): + return f"" + + def __del__(self): + log(f"{self} has shut down", log_level="warning") + + +class ServerRegistry: + """Server registry with weak references.""" + + def __init__(self): + self._servers = weakref.WeakSet() + + @property + def servers(self): + return {s for s in self._servers} + + @property + def server_count(self): + return len(self.servers) + + def add(self, server): + log(f"Add {server} to registry") + self._servers.add(server) + + +def setup_and_teardown_servers(registry): + """Explicitly setup and implicitly teardown servers.""" + app_servers = {} + + # Create all of the servers and put them in the registry and the + # dictionary and we'll tally things at the end + for app in _APPS: + app_servers[app] = set() + for component in _COMPONENTS: + server = Server.create(f"{app}_{component}") + registry.add(server) + app_servers[app].add(server) + + # All of these counts are equivalent and this is no surprise since + # our for loop unconditionally creates a server for every permutation + # of apps and components, and adds each server to the registry and + # dictionary unconditionally + assert ( + registry.server_count + == len(_APPS) * len(_COMPONENTS) + == len([(app, server) + for app, servers in app_servers.items() + for server in servers]) + ) + + # What's really interesting is that all of this memory goes away after + # a while. Notice how the __del__ calls start happening once we leave + # the scope of this function + + +def main(): + # Initialize a server registry + registry = ServerRegistry() + + # Setup and teardown servers with the registry + setup_and_teardown_servers(registry) + + # Notice that our registry has no recollection of the servers. The + # benefit is that our registry is allowing the garbage collector to do + # its job effectively and remove orphaned servers from the previous + # call, keeping our software memory-efficient + assert registry.servers == set() + assert registry.server_count == 0 + + +if __name__ == '__main__': + main() From fc6cc459f271d1fd244019611883095285a3846c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 10:51:35 -0700 Subject: [PATCH 207/310] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a2d0c168..6627d5cd 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ inspiring and highly encouraged if your goal is to become a true - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) + - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) From 333b6ef7e48b24fc36a46c3a50c7cb508507b1db Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:01:01 -0700 Subject: [PATCH 208/310] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6627d5cd..7e1bfaf3 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ inspiring and highly encouraged if your goal is to become a true 1. **About Python** - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:) - - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:) + - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) + - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) @@ -63,13 +63,13 @@ inspiring and highly encouraged if your goal is to become a true - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) 5. **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) - - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) - - Asyncio: [async | await](ultimatepython/advanced/async.py) - - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) + - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) ## Additional resources From 730996ccba7c4d50d55defe5bf39192c124a398f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:07:14 -0700 Subject: [PATCH 209/310] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7e1bfaf3..62b5b33c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ inspiring and highly encouraged if your goal is to become a true ## Table of contents +:books: = External resource, +:exploding_head: = Advanced topic + 1. **About Python** - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) From f8d2964748e9a5860f6026b822c18b9c1671f962 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:08:07 -0700 Subject: [PATCH 210/310] Update meta_class.py --- ultimatepython/advanced/meta_class.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index c80e55e2..6c47db3d 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -18,10 +18,9 @@ class ModelMeta(type): composition, class inheritance or functions. Simple code is the reason why Python is attractive for 99% of users. - For more on meta-classes, visit the links below: + For more on meta-classes, visit the link below: https://realpython.com/python-metaclasses/ - https://docs.python.org/3/reference/datamodel.html """ # Model table registry From 38c3c75b85711d4bacc6e8e375f16bc308588408 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:15:06 -0700 Subject: [PATCH 211/310] Update README.md --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 62b5b33c..c1bf8cfc 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ inspiring and highly encouraged if your goal is to become a true ## Table of contents :books: = External resource, +:baby: = Beginner topic, :exploding_head: = Advanced topic 1. **About Python** @@ -51,19 +52,19 @@ inspiring and highly encouraged if your goal is to become a true - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) 2. **Syntax** - - Variable: [Built-in literals](ultimatepython/syntax/variable.py) - - Expression: [Numeric operations](ultimatepython/syntax/expression.py) - - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) - - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) - - Function: [def | lambda](ultimatepython/syntax/function.py) + - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:baby:) + - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:baby:) + - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) (:baby:) + - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) (:baby:) + - Function: [def | lambda](ultimatepython/syntax/function.py) (:baby:) 3. **Data Structures** - - List: [List operations](ultimatepython/data_structures/list.py) + - List: [List operations](ultimatepython/data_structures/list.py) (:baby:) - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) - Set: [Set operations](ultimatepython/data_structures/set.py) - - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) + - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) (:baby:) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) 4. **Classes** - - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) + - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:baby:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) From 38f9c78d37687b440942ec75379e2a7f75e5b6b3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:17:51 -0700 Subject: [PATCH 212/310] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c1bf8cfc..0b1af260 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ inspiring and highly encouraged if your goal is to become a true ## Table of contents :books: = External resource, -:baby: = Beginner topic, +:cake: = Beginner topic, :exploding_head: = Advanced topic 1. **About Python** @@ -52,19 +52,19 @@ inspiring and highly encouraged if your goal is to become a true - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) 2. **Syntax** - - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:baby:) - - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:baby:) - - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) (:baby:) - - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) (:baby:) - - Function: [def | lambda](ultimatepython/syntax/function.py) (:baby:) + - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) + - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) + - Conditional: [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) (:cake:) + - Loop: [for-loop | while-loop](ultimatepython/syntax/loop.py) (:cake:) + - Function: [def | lambda](ultimatepython/syntax/function.py) (:cake:) 3. **Data Structures** - - List: [List operations](ultimatepython/data_structures/list.py) (:baby:) + - List: [List operations](ultimatepython/data_structures/list.py) (:cake:) - Tuple: [Tuple operations](ultimatepython/data_structures/tuple.py) - Set: [Set operations](ultimatepython/data_structures/set.py) - - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) (:baby:) + - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) (:cake:) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) 4. **Classes** - - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:baby:) + - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) From b728a03fe562b289cf5e8512c53b4014e92ce177 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:20:49 -0700 Subject: [PATCH 213/310] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0b1af260..52389066 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ inspiring and highly encouraged if your goal is to become a true :books: = External resource, :cake: = Beginner topic, -:exploding_head: = Advanced topic +:telescope: = Advanced topic 1. **About Python** - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) - - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) + - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :telescope:) + - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :telescope:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) @@ -67,13 +67,13 @@ inspiring and highly encouraged if your goal is to become a true - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:telescope:) 5. **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) - - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:telescope:) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:telescope:) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:telescope:) + - Asyncio: [async | await](ultimatepython/advanced/async.py) (:telescope:) + - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:telescope:) ## Additional resources From f41668b28e8070369b19a313c7ebcd625c8b1bb0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:21:56 -0700 Subject: [PATCH 214/310] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 52389066..05f0c75e 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ inspiring and highly encouraged if your goal is to become a true :books: = External resource, :cake: = Beginner topic, -:telescope: = Advanced topic +:mountain: = Advanced topic 1. **About Python** - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :telescope:) - - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :telescope:) + - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :mountain:) + - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :mountain:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) @@ -67,13 +67,13 @@ inspiring and highly encouraged if your goal is to become a true - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:telescope:) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:mountain:) 5. **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:telescope:) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:telescope:) - - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:telescope:) - - Asyncio: [async | await](ultimatepython/advanced/async.py) (:telescope:) - - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:telescope:) + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:mountain:) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:mountain:) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:mountain:) + - Asyncio: [async | await](ultimatepython/advanced/async.py) (:mountain:) + - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:mountain:) ## Additional resources From 11624ea463b4004631a993936942a27ea4ec5d96 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 27 Aug 2020 23:23:51 -0700 Subject: [PATCH 215/310] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 05f0c75e..0b1af260 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ inspiring and highly encouraged if your goal is to become a true :books: = External resource, :cake: = Beginner topic, -:mountain: = Advanced topic +:exploding_head: = Advanced topic 1. **About Python** - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :mountain:) - - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :mountain:) + - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) + - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) @@ -67,13 +67,13 @@ inspiring and highly encouraged if your goal is to become a true - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:mountain:) + - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) 5. **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:mountain:) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:mountain:) - - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:mountain:) - - Asyncio: [async | await](ultimatepython/advanced/async.py) (:mountain:) - - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:mountain:) + - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) + - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) ## Additional resources From cf65bea7643a20afc9147956e28b599c98d4fe52 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:08:23 -0700 Subject: [PATCH 216/310] Update README.md --- README.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0b1af260..ee3e7356 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,22 @@ inspiring and highly encouraged if your goal is to become a true As you start applying Python fundamentals to the real world, read code examples and project ideas from other well-regarded resources. :earth_americas: -Here are some GitHub repositories to get started with: - -- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (Interview) -- [faif/python-patterns](https://github.com/faif/python-patterns) (Design) -- [vinta/awesome-python](https://github.com/vinta/awesome-python) (Examples) -- [geekcomputers/Python](https://github.com/geekcomputers/Python) (Examples) -- [karan/Projects](https://github.com/karan/Projects) (Ideas) -- [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (Ideas) +:necktie: = Interview resource, +:test_tube: = Code samples, +:brain: = Project ideas, +:book: = Best practices + +### GitHub repositories + +- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) +- [faif/python-patterns](https://github.com/faif/python-patterns) (:book:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) (:test_tube:) +- [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) +- [karan/Projects](https://github.com/karan/Projects) (:brain:) +- [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) + +### Practice websites + +- [exercism.io](https://exercism.io/) (:necktie:) +- [leetcode.com](https://leetcode.com/) (:necktie:) +- [codewars.com](https://www.codewars.com/) (:necktie:) From 4e33af76d25ef9109f01877b65d30e5a1d107fc4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:10:32 -0700 Subject: [PATCH 217/310] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee3e7356..01f9c979 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ and project ideas from other well-regarded resources. :earth_americas: ### GitHub repositories - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) -- [faif/python-patterns](https://github.com/faif/python-patterns) (:book:) -- [vinta/awesome-python](https://github.com/vinta/awesome-python) (:test_tube:) +- [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:, :book:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) (:brain:) - [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) - [karan/Projects](https://github.com/karan/Projects) (:brain:) - [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) From 34ff92431e2e5ff760bccec73a5dd782a35dbd42 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:14:17 -0700 Subject: [PATCH 218/310] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01f9c979..b57001a2 100644 --- a/README.md +++ b/README.md @@ -89,13 +89,14 @@ and project ideas from other well-regarded resources. :earth_americas: - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) - [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:, :book:) -- [vinta/awesome-python](https://github.com/vinta/awesome-python) (:brain:) - [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) - [karan/Projects](https://github.com/karan/Projects) (:brain:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) (:brain:) - [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) ### Practice websites -- [exercism.io](https://exercism.io/) (:necktie:) - [leetcode.com](https://leetcode.com/) (:necktie:) -- [codewars.com](https://www.codewars.com/) (:necktie:) +- [exercism.io](https://exercism.io/) +- [codewars.com](https://www.codewars.com/) +- [edabit.com](https://edabit.com/) From 4d45373ef6ff4238dfb541cc867535680da821f3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:15:45 -0700 Subject: [PATCH 219/310] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b57001a2..9d855049 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ and project ideas from other well-regarded resources. :earth_americas: ### Practice websites - [leetcode.com](https://leetcode.com/) (:necktie:) +- [codesignal.com](https://codesignal.com/) (:necktie:) - [exercism.io](https://exercism.io/) - [codewars.com](https://www.codewars.com/) - [edabit.com](https://edabit.com/) From 83a19285490df5af2c0fb5e2a51c946cbfbf041f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:18:44 -0700 Subject: [PATCH 220/310] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d855049..ba894a05 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,10 @@ and project ideas from other well-regarded resources. :earth_americas: - [vinta/awesome-python](https://github.com/vinta/awesome-python) (:brain:) - [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) -### Practice websites +### Interactive practice - [leetcode.com](https://leetcode.com/) (:necktie:) +- [hackerrank.com](https://www.hackerrank.com/) (:necktie:) - [codesignal.com](https://codesignal.com/) (:necktie:) - [exercism.io](https://exercism.io/) - [codewars.com](https://www.codewars.com/) From 3b55bb3228923e823ad77be9fea88d58964e5ce6 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:20:11 -0700 Subject: [PATCH 221/310] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ba894a05..9bf1eded 100644 --- a/README.md +++ b/README.md @@ -100,5 +100,3 @@ and project ideas from other well-regarded resources. :earth_americas: - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) - [codesignal.com](https://codesignal.com/) (:necktie:) - [exercism.io](https://exercism.io/) -- [codewars.com](https://www.codewars.com/) -- [edabit.com](https://edabit.com/) From df09822e041d9cabca91702938c989acf956e50e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:32:49 -0700 Subject: [PATCH 222/310] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bf1eded..d4c4f993 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,9 @@ inspiring and highly encouraged if your goal is to become a true ## Additional resources -As you start applying Python fundamentals to the real world, read code examples -and project ideas from other well-regarded resources. :earth_americas: +Wondering "What's next?" after going through the content above? :thinking: + +Check the sections below for more resources. :necktie: = Interview resource, :test_tube: = Code samples, @@ -87,6 +88,8 @@ and project ideas from other well-regarded resources. :earth_americas: ### GitHub repositories +Keep learning by reading from other well-regarded resources. + - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) - [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:, :book:) - [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) @@ -96,6 +99,8 @@ and project ideas from other well-regarded resources. :earth_americas: ### Interactive practice +Practice coding so that it doesn't get rusty. + - [leetcode.com](https://leetcode.com/) (:necktie:) - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) - [codesignal.com](https://codesignal.com/) (:necktie:) From 3fa344835084cbefc8100bc88e095eaf0a7f24a0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:34:42 -0700 Subject: [PATCH 223/310] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4c4f993..78590b78 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ inspiring and highly encouraged if your goal is to become a true ## Additional resources -Wondering "What's next?" after going through the content above? :thinking: +Wondering "What's next?" after going through the content above? -Check the sections below for more resources. +Check the sections below for further guidance. :necktie: = Interview resource, :test_tube: = Code samples, From c5be91164491646f43a39724689075a7677f62ee Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:35:01 -0700 Subject: [PATCH 224/310] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 78590b78..56ccd3ca 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,6 @@ inspiring and highly encouraged if your goal is to become a true ## Additional resources -Wondering "What's next?" after going through the content above? - -Check the sections below for further guidance. - :necktie: = Interview resource, :test_tube: = Code samples, :brain: = Project ideas, From d79e029635587a05d80b35eeca99e791e8b4e117 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:36:00 -0700 Subject: [PATCH 225/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56ccd3ca..878eb325 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Keep learning by reading from other well-regarded resources. ### Interactive practice -Practice coding so that it doesn't get rusty. +Keep practicing so that your coding skills don't get rusty. - [leetcode.com](https://leetcode.com/) (:necktie:) - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) From 9e89856d84ada40204d2bc29d2f5c004cab09ecd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 07:56:02 -0700 Subject: [PATCH 226/310] Update mro.py --- ultimatepython/advanced/mro.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 0f9ae355..5e2a215d 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -4,20 +4,20 @@ class A: def ping(self): print("ping", self) + def pong(self): + print("pong", self) + class B(A): """B inherits from A.""" def pong(self): - print("pong", self) + print("PONG", self) class C(A): """C inherits from A.""" - def pong(self): - print("PONG", self) - class D(B, C): """D inherits from B and C. From f2948c1368be98ef1248debd994599d7a4a895c8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 09:24:35 -0700 Subject: [PATCH 227/310] Revise weakref lesson for safety --- ultimatepython/advanced/weak_ref.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 951a2fe7..199f4e44 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -1,5 +1,4 @@ import weakref -from datetime import datetime from uuid import uuid4 # Module-level constants @@ -8,11 +7,6 @@ _COMPONENTS = ("db", "web", "cache") -def log(message, log_level="info"): - """Log a message similar to that of web servers.""" - print(f"{datetime.now()} [{log_level}] - {message}") - - class Server: """General server.""" @@ -28,9 +22,6 @@ def __init__(self, ssid, provider, role): def __repr__(self): return f"" - def __del__(self): - log(f"{self} has shut down", log_level="warning") - class ServerRegistry: """Server registry with weak references.""" @@ -47,7 +38,6 @@ def server_count(self): return len(self.servers) def add(self, server): - log(f"Add {server} to registry") self._servers.add(server) @@ -76,6 +66,9 @@ def setup_and_teardown_servers(registry): for server in servers]) ) + # Print server count as proof + print("Server count", registry.server_count) + # What's really interesting is that all of this memory goes away after # a while. Notice how the __del__ calls start happening once we leave # the scope of this function @@ -95,6 +88,9 @@ def main(): assert registry.servers == set() assert registry.server_count == 0 + # Print server count as proof + print("Server count", registry.server_count) + if __name__ == '__main__': main() From b53a1aa37bf868dff181ff04a79d5624a6b527c9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 09:27:19 -0700 Subject: [PATCH 228/310] Remove commented code from loop --- ultimatepython/syntax/loop.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index be08149b..d5c10952 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -49,12 +49,12 @@ def main(): # this statement would not stop the parent loop break else: - # The `continue` statement returns to the - # start of the current while loop + # The `continue` statement returns to the start of the + # current while loop. This statement is placed here + # purely for demonstration purposes continue - # Skipped because of `continue` and `break` - # print("I will never get called") + # Any code here would be skipped because of `break` and `continue if __name__ == "__main__": From d5f52636cb702825aa0562f849e9370ac341173c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 09:28:07 -0700 Subject: [PATCH 229/310] Fill in unclosed backtick --- ultimatepython/syntax/loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index d5c10952..0366b203 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -54,7 +54,7 @@ def main(): # purely for demonstration purposes continue - # Any code here would be skipped because of `break` and `continue + # Any code here would be skipped because of `break` and `continue` if __name__ == "__main__": From 8895493e886df1ffd7da800c758fb854beacd941 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 17:52:29 -0700 Subject: [PATCH 230/310] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 878eb325..c627ce51 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ inspiring and highly encouraged if your goal is to become a true :exploding_head: = Advanced topic 1. **About Python** - - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:) + - Overview: [What is Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:, :cake:) - Design Philosophy: [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) - Style Guide: [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) - Data Model: [Data model](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) From a5d9a6c647be09727b4fcc51bda203bdd736b49d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 18:07:05 -0700 Subject: [PATCH 231/310] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c627ce51..209545e8 100644 --- a/README.md +++ b/README.md @@ -101,3 +101,4 @@ Keep practicing so that your coding skills don't get rusty. - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) - [codesignal.com](https://codesignal.com/) (:necktie:) - [exercism.io](https://exercism.io/) +- [projecteuler.net](https://projecteuler.net/) From 347ee62daacfb55b6c474dcaa0cbf2427d372a1e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 18:29:31 -0700 Subject: [PATCH 232/310] Fixup var names and docs --- ultimatepython/advanced/decorator.py | 33 ++++++++++++++-------------- ultimatepython/advanced/weak_ref.py | 14 ++++++------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index c890327b..7ad4e5af 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -13,14 +13,15 @@ def header_section(): of yielding. Also notice that `header_section` is a coroutine that is wrapped by - `contextmanager`. The `contextmanager` handles entering and exiting a section - of code without defining a full-blown class to handle `__enter__` and - `__exit__` use cases. + `contextmanager`. The `contextmanager` handles entering and exiting a + section of code without defining a full-blown class to handle `__enter__` + and `__exit__` use cases. - There are many more use cases for context managers, like writing / reading - data from a file. Another one is protecting database integrity while sending - CREATE / UPDATE / DELETE statements over the network. For more on how context - managers work, please consult the Python docs for more information. + There are many more use cases for context managers, like + writing / reading data from a file. Another one is protecting database + integrity while sending CREATE / UPDATE / DELETE statements over the + network. For more on how context managers work, please consult the + Python docs for more information. https://docs.python.org/3/library/contextlib.html """ @@ -54,7 +55,7 @@ def run_with_any(fn): """ @wraps(fn) - def wrapper(stringy): + def wrapper(obj): """Apply wrapped function to a string or a collection. This looks like a policy-based engine which runs a `return` statement @@ -66,14 +67,14 @@ def wrapper(stringy): But instead of writing the logic using classes, we write the logic using a single function that encapsulates all possible rules. """ - if isinstance(stringy, str): - return fn(stringy) - elif isinstance(stringy, dict): - return {name: wrapper(item) for name, item in stringy.items()} - elif isinstance(stringy, (list, set, tuple)): - sequence_kls = type(stringy) - return sequence_kls(wrapper(value) for value in stringy) - raise ValueError("Found item that is neither a string nor a collection.") + if isinstance(obj, str): + return fn(obj) + elif isinstance(obj, dict): + return {key: wrapper(value) for key, value in obj.items()} + elif isinstance(obj, (list, set, tuple)): + sequence_kls = type(obj) + return sequence_kls(wrapper(value) for value in obj) + raise ValueError(f"Found an invalid item: {obj}") return wrapper diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 199f4e44..c7846cd7 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -2,16 +2,16 @@ from uuid import uuid4 # Module-level constants -_PROVIDER = "aws" -_APPS = ["yelp", "pinterest", "uber", "twitter"] -_COMPONENTS = ("db", "web", "cache") +_CLOUD_PROVIDER = "aws" +_CLOUD_APPS = ["yelp", "pinterest", "uber", "twitter"] +_CLOUD_APP_COMPONENTS = ("db", "web", "cache") class Server: """General server.""" @classmethod - def create(cls, role, provider=_PROVIDER): + def create(cls, role, provider=_CLOUD_PROVIDER): return cls(uuid4().hex, provider, role) def __init__(self, ssid, provider, role): @@ -47,9 +47,9 @@ def setup_and_teardown_servers(registry): # Create all of the servers and put them in the registry and the # dictionary and we'll tally things at the end - for app in _APPS: + for app in _CLOUD_APPS: app_servers[app] = set() - for component in _COMPONENTS: + for component in _CLOUD_APP_COMPONENTS: server = Server.create(f"{app}_{component}") registry.add(server) app_servers[app].add(server) @@ -60,7 +60,7 @@ def setup_and_teardown_servers(registry): # dictionary unconditionally assert ( registry.server_count - == len(_APPS) * len(_COMPONENTS) + == len(_CLOUD_APPS) * len(_CLOUD_APP_COMPONENTS) == len([(app, server) for app, servers in app_servers.items() for server in servers]) From 2b98090215c8063667f71c343709b12722393310 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 18:30:21 -0700 Subject: [PATCH 233/310] Minimize setup.py description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 68fef651..5cceefbf 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="ultimatepython", packages=find_packages(), - description="Ultimate Python study guide for newcomers and professionals alike.", + description="Ultimate Python study guide", classifiers=[ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", From e3c68397aca179bb3514a52a5e0933356c6cb1e4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 18:32:27 -0700 Subject: [PATCH 234/310] Rename stringy to secrets --- ultimatepython/advanced/decorator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 7ad4e5af..9d6a62ef 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -89,7 +89,7 @@ def hide_content(content): def main(): # There are so many plain-text secrets out in the open - insecure_stringy = [ + insecure_secrets = [ {"username": "johndoe", "password": "s3cret123"}, # User credentials ["123-456=7890", "123-456-7891"], # Social security numbers [("johndoe", "janedoe"), ("bobdoe", "marydoe")], # Couple names @@ -100,10 +100,10 @@ def main(): # of work is the stuff that might be done by a company for GDPR. For more # on that policy, check out the following Wikipedia page: # https://en.wikipedia.org/wiki/General_Data_Protection_Regulation - secure_stringy = hide_content(insecure_stringy) + secure_secrets = hide_content(insecure_secrets) - # See what changed between the insecure stringy and the secure stringy - for insecure_item, secure_item in zip(insecure_stringy, secure_stringy): + # See what changed between the insecure secrets and the secure secrets + for insecure_item, secure_item in zip(insecure_secrets, secure_secrets): with header_section(): print("Insecure item", insecure_item) print("Secure item", secure_item) From ca24fe9dedbb152bbd1d94509c59b17a23606a31 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 19:12:11 -0700 Subject: [PATCH 235/310] Use better class names for MRO --- ultimatepython/advanced/mro.py | 51 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 5e2a215d..898f1f00 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -1,5 +1,5 @@ -class A: - """Base class.""" +class BasePlayer: + """Base player.""" def ping(self): print("ping", self) @@ -8,19 +8,19 @@ def pong(self): print("pong", self) -class B(A): - """B inherits from A.""" +class PongPlayer(BasePlayer): + """Pong player.""" def pong(self): print("PONG", self) -class C(A): - """C inherits from A.""" +class NeutralPlayer(BasePlayer): + """Neutral player.""" -class D(B, C): - """D inherits from B and C. +class ConfusedPlayer(PongPlayer, NeutralPlayer): + """Confused player. This is what we call the diamond problem, where A has multiple child classes that are the same as D's parent classes. Python has the MRO to @@ -41,8 +41,8 @@ def ping_pong(self): super().pong() -class E(C, B): - """E inherits from C and B. +class IndecisivePlayer(NeutralPlayer, PongPlayer): + """Indecisive player. This exhibits the Python MRO as well. Notice that this class was created successfully without any conflicts because of D's existence. @@ -60,25 +60,28 @@ def ping_pong(self): def main(): - # Show how methods in class D are resolved from child to parent - assert D.mro() == [D, B, C, A, object] + # Show how methods in `ConfusedPlayer` are resolved + assert ConfusedPlayer.mro() == [ + ConfusedPlayer, PongPlayer, NeutralPlayer, BasePlayer, object] - # Show how methods in class E are resolved from child to parent - assert E.mro() == [E, C, B, A, object] + # Show how methods in `IndecisivePlayer` are resolved + assert IndecisivePlayer.mro() == [ + IndecisivePlayer, NeutralPlayer, PongPlayer, BasePlayer, object] - # Show D method resolution in action - d_obj = D() - d_obj.ping_pong() + # Show `ConfusedPlayer` method resolution in action + confused_player = ConfusedPlayer() + confused_player.ping_pong() - # Show E method resolution in action - e_obj = E() - e_obj.ping_pong() + # Show `IndecisivePlayer` method resolution in action + indecisive_player = IndecisivePlayer() + indecisive_player.ping_pong() try: - # Creating a new class F from D and E result in a type error - # because D and E have MRO outputs that cannot be merged - # together as one - type("F", (D, E), {}) + # Creating a new class `ConfusedPlayer` and `IndecisivePlayer` + # result in a `TypeError` because both classes have mismatched + # MRO outputs. This means that they cannot be reconciled as + # one class. Hence `MissingPlayer` will not be created + type("MissingPlayer", (ConfusedPlayer, IndecisivePlayer), {}) except TypeError as e: print(e) From 214e30b5b6b90dd2a045095a5450fcb53850f426 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:35:56 -0700 Subject: [PATCH 236/310] Increase coverage and add tools --- .coveragerc | 10 ++++++++++ .gitignore | 4 ++++ requirements.txt | 3 +++ ultimatepython/advanced/decorator.py | 7 +++++++ ultimatepython/advanced/mro.py | 8 ++++---- ultimatepython/advanced/weak_ref.py | 3 --- ultimatepython/classes/abstract_class.py | 4 ++++ ultimatepython/classes/exception_class.py | 10 ++++++++++ ultimatepython/classes/iterator_class.py | 18 ++++++++++++++++-- ultimatepython/syntax/conditional.py | 12 ++++++------ ultimatepython/syntax/loop.py | 1 + 11 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 .coveragerc create mode 100644 requirements.txt diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..4f55136f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +branch = True + +[report] +exclude_lines = + if __name__ == .__main__.: + raise NotImplementedError + .* # skip:(if|else) +omit = + venv/** diff --git a/.gitignore b/.gitignore index 16692b16..18804740 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,9 @@ venv/ __pycache__/ *.egg-info/ +htmlcov/ *.pyc .DS_Store +.coverage +coverage.xml +coverage.json diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..afa7d5ca --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +coverage +flake8 +isort diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 9d6a62ef..9c2ac92c 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -108,6 +108,13 @@ def main(): print("Insecure item", insecure_item) print("Secure item", secure_item) + # Throw an error on object that is neither string nor collection + try: + hide_content(1) + except ValueError as e: + with header_section(): + print(e) + if __name__ == "__main__": main() diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 898f1f00..ccdda8de 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -32,7 +32,7 @@ class ConfusedPlayer(PongPlayer, NeutralPlayer): """ def ping(self): - print("PING", self) + print("pINg", self) def ping_pong(self): self.ping() @@ -45,18 +45,18 @@ class IndecisivePlayer(NeutralPlayer, PongPlayer): """Indecisive player. This exhibits the Python MRO as well. Notice that this class was - created successfully without any conflicts because of D's existence. + created successfully without any conflicts because of `ConfusedPlayer`. All is good in the world. """ def pong(self): - print("pong", self) + print("pONg", self) def ping_pong(self): self.ping() super().ping() self.pong() - super().pong() + super(PongPlayer, self).pong() # bypass MRO to `BasePlayer` def main(): diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index c7846cd7..1aea847c 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -19,9 +19,6 @@ def __init__(self, ssid, provider, role): self.provider = provider self.role = role - def __repr__(self): - return f"" - class ServerRegistry: """Server registry with weak references.""" diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 79c06426..56360056 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -100,6 +100,8 @@ def main(): for engineer in engineers: assert isinstance(engineer, (Engineer, Employee)) assert not isinstance(engineer, Manager) + print("Created", repr(engineer)) + engineer.do_work() engineer.join_meeting() engineer.relax() @@ -110,6 +112,8 @@ def main(): assert isinstance(manager_max, (Manager, Employee)) assert not isinstance(manager_max, Engineer) + print("Created", repr(manager_max)) + manager_max.do_work() manager_max.join_meeting() manager_max.relax() diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index ec1ed1cd..ab437711 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -57,6 +57,16 @@ def main(): divide_positive_numbers(1, 0) except DivisionError as e: print(e) + try: + divide_positive_numbers(-1, 1) + except DivisionError as e: + print(e) + try: + divide_positive_numbers(1, -1) + except DivisionError as e: + print(e) + result = divide_positive_numbers(1, 1) + print(f"Divide(1, 1) = {result}") if __name__ == '__main__': diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 39c7c635..b75d06f9 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -70,7 +70,7 @@ def __next__(self): raise StopIteration employee = self.employees_to_visit.pop() if employee.name in self.employees_visited: - raise RuntimeError("Cyclic loop detected") + raise IterationError("Cyclic loop detected") self.employees_visited.add(employee.name) for report in employee.direct_reports: self.employees_to_visit.append(report) @@ -102,7 +102,7 @@ def employee_generator(top_employee): while len(to_visit) > 0: employee = to_visit.pop() if employee.name in visited: - raise RuntimeError("Cyclic loop detected") + raise IterationError("Cyclic loop detected") visited.add(employee.name) for report in employee.direct_reports: to_visit.append(report) @@ -126,6 +126,20 @@ def main(): assert all(isinstance(emp, Employee) for emp in employees) print(employees) + # This is not a good day for this company + hacker = Employee("Unknown", "Hacker", []) + hacker.direct_reports.append(hacker) + + try: + list(EmployeeIterator(hacker)) + except IterationError as e: + print(e) + + try: + list(employee_generator(hacker)) + except IterationError as e: + print(e) + if __name__ == "__main__": main() diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index ead355ae..249e9d51 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -3,18 +3,18 @@ def main(): x_add_two = x + 2 # This condition is obviously true - if x_add_two == 3: + if x_add_two == 3: # skip:else print("Math wins") # run # A negated condition can also be true - if not x_add_two == 1: + if not x_add_two == 1: # skip:else print("Math wins here too") # run # There are `else` statements as well, which run if the initial condition # fails. Notice that one line gets skipped, and that this conditional # does not help one make a conclusion on the variable's true value if x_add_two == 1: - print("Math lost here...") # skip + print("Math lost here...") # skip:if else: print("Math wins otherwise") # run @@ -23,11 +23,11 @@ def main(): # conditions could have been compressed to `x_add_two != 3` for # simplicity. In this case, less logic results in more clarity if x_add_two == 1: - print("Nope not this one...") # skip + print("Nope not this one...") # skip:if elif x_add_two == 2: - print("Nope not this one either...") # skip + print("Nope not this one either...") # skip:if elif x_add_two < 3 or x_add_two > 3: - print("Nope not quite...") # skip + print("Nope not quite...") # skip:if else: print("Math wins finally") # run diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 0366b203..4fe9bf1c 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -52,6 +52,7 @@ def main(): # The `continue` statement returns to the start of the # current while loop. This statement is placed here # purely for demonstration purposes + print(f"Staying alive at {i}") continue # Any code here would be skipped because of `break` and `continue` From 52305b3a5fb15fa7f71ba2d72848d725a86e2051 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:39:01 -0700 Subject: [PATCH 237/310] Consolidate coverage and flake8 config --- .coveragerc => setup.cfg | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename .coveragerc => setup.cfg (62%) diff --git a/.coveragerc b/setup.cfg similarity index 62% rename from .coveragerc rename to setup.cfg index 4f55136f..4f2ceab6 100644 --- a/.coveragerc +++ b/setup.cfg @@ -1,10 +1,14 @@ -[run] +[coverage:run] branch = True -[report] +[coverage:report] exclude_lines = if __name__ == .__main__.: raise NotImplementedError .* # skip:(if|else) omit = venv/** + +[flake8] +max-line-length = 160 +exclude = venv From 69187c51ebac63744cd276491b41d175a77e3d1a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:43:47 -0700 Subject: [PATCH 238/310] Shuffle params in weak_ref --- ultimatepython/advanced/weak_ref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 1aea847c..53d59616 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -12,12 +12,12 @@ class Server: @classmethod def create(cls, role, provider=_CLOUD_PROVIDER): - return cls(uuid4().hex, provider, role) + return cls(uuid4().hex, role, provider) - def __init__(self, ssid, provider, role): + def __init__(self, ssid, role, provider): self.ssid = ssid - self.provider = provider self.role = role + self.provider = provider class ServerRegistry: From 80dbf0a6867958e9a488a1ee7ceaf56cfd0994f0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:46:48 -0700 Subject: [PATCH 239/310] Make better use of while-loop --- ultimatepython/syntax/loop.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 4fe9bf1c..f9f8df3d 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -26,7 +26,7 @@ def main(): # managed inside of the loop. The loop will continue until the counter # exceeds 5 i = 0 - while i < 5: + while i < 8: print(f"While {i} < 5") i += 2 @@ -41,21 +41,25 @@ def main(): # Putting this conditional after the print statement makes the loop # look like the do-while loop from other programming languages - if i >= 5: + if i >= 8: print(f"Break out! {i} is no longer < 5") # The `break` statement stops the current while loop. # If this `while` loop was nested in another loop, # this statement would not stop the parent loop break - else: + elif i == 2: + print(f"Time to continue from {i}") + # The `continue` statement returns to the start of the # current while loop. This statement is placed here # purely for demonstration purposes - print(f"Staying alive at {i}") continue + else: + print(f"Staying alive at {i}") - # Any code here would be skipped because of `break` and `continue` + # Do nothing in this case. Just let the code roll right + # through and let it be if __name__ == "__main__": From 64926f818673a8ec127a9f45d73f25341e291dfa Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:58:19 -0700 Subject: [PATCH 240/310] Enhance decorator lesson --- ultimatepython/advanced/decorator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 9c2ac92c..4c621510 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -88,9 +88,9 @@ def hide_content(content): def main(): - # There are so many plain-text secrets out in the open - insecure_secrets = [ - {"username": "johndoe", "password": "s3cret123"}, # User credentials + # There are so many plain-text data out in the open + insecure_data = [ + {"username": "johndoe", "country": "USA"}, # User information ["123-456=7890", "123-456-7891"], # Social security numbers [("johndoe", "janedoe"), ("bobdoe", "marydoe")], # Couple names "secretLaunchCode123", # Secret launch code @@ -100,10 +100,10 @@ def main(): # of work is the stuff that might be done by a company for GDPR. For more # on that policy, check out the following Wikipedia page: # https://en.wikipedia.org/wiki/General_Data_Protection_Regulation - secure_secrets = hide_content(insecure_secrets) + secure_data = hide_content(insecure_data) # See what changed between the insecure secrets and the secure secrets - for insecure_item, secure_item in zip(insecure_secrets, secure_secrets): + for insecure_item, secure_item in zip(insecure_data, secure_data): with header_section(): print("Insecure item", insecure_item) print("Secure item", secure_item) From fd218da2a372d8d918adbfc7565c68dc1a0ec693 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 22:59:31 -0700 Subject: [PATCH 241/310] Clarify docs in decorator --- ultimatepython/advanced/decorator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 4c621510..3e0b48f2 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -88,7 +88,7 @@ def hide_content(content): def main(): - # There are so many plain-text data out in the open + # There is so much plain-text data out in the open insecure_data = [ {"username": "johndoe", "country": "USA"}, # User information ["123-456=7890", "123-456-7891"], # Social security numbers @@ -96,13 +96,13 @@ def main(): "secretLaunchCode123", # Secret launch code ] - # Time to encrypt them all so that they can't be snatched away. This kind + # Time to encrypt it all so that it can't be snatched away. This kind # of work is the stuff that might be done by a company for GDPR. For more # on that policy, check out the following Wikipedia page: # https://en.wikipedia.org/wiki/General_Data_Protection_Regulation secure_data = hide_content(insecure_data) - # See what changed between the insecure secrets and the secure secrets + # See what changed between the insecure data and the secure data for insecure_item, secure_item in zip(insecure_data, secure_data): with header_section(): print("Insecure item", insecure_item) From f7ba544547fc8bbb5438a9084e3f9f9dcb274bba Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 23:00:53 -0700 Subject: [PATCH 242/310] Remove code smell in loop lesson --- ultimatepython/syntax/loop.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index f9f8df3d..537071fb 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -48,18 +48,16 @@ def main(): # If this `while` loop was nested in another loop, # this statement would not stop the parent loop break - elif i == 2: + + if i == 2: print(f"Time to continue from {i}") # The `continue` statement returns to the start of the # current while loop. This statement is placed here # purely for demonstration purposes continue - else: - print(f"Staying alive at {i}") - # Do nothing in this case. Just let the code roll right - # through and let it be + print(f"Staying alive at {i}") if __name__ == "__main__": From 8f5e90261ff2aeebcdea9fbfd3bccad608ac93b2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 23:02:17 -0700 Subject: [PATCH 243/310] Fixup loop comment --- ultimatepython/syntax/loop.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 537071fb..f336beb1 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -53,8 +53,7 @@ def main(): print(f"Time to continue from {i}") # The `continue` statement returns to the start of the - # current while loop. This statement is placed here - # purely for demonstration purposes + # current while loop continue print(f"Staying alive at {i}") From e8d870d726a84179f22f4cc4d5caf8a6375c45b0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 28 Aug 2020 23:21:21 -0700 Subject: [PATCH 244/310] Add high bar for coverage level --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 4f2ceab6..3c7fce25 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,7 @@ exclude_lines = if __name__ == .__main__.: raise NotImplementedError .* # skip:(if|else) +fail_under = 80 omit = venv/** From a6273b0f2a7b7c025f9311c48e5456232e64c57a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 08:32:00 -0700 Subject: [PATCH 245/310] Add code coverage to CircleCI config (#2) * Introduce code coverage * Add linting --- .circleci/config.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36e0b066..a65f7f45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,12 +8,18 @@ jobs: executor: python/default steps: - checkout + - python/load-cache + - python/install-deps + - python/save-cache - run: - command: pip install . - name: Build + command: flake8 + name: Lint - run: - command: python runner.py + command: coverage run -m runner name: Test + - run: + command: coverage report + name: Report workflows: main: From 69bc973d79ceee1eb1ee689d9c057efb079bcb68 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 09:26:58 -0700 Subject: [PATCH 246/310] Refine setup.cfg settings --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3c7fce25..56857d69 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,7 +9,10 @@ exclude_lines = fail_under = 80 omit = venv/** + runner.py + **/__init__.py [flake8] max-line-length = 160 -exclude = venv +exclude = + venv From 4e2dda04de517f0e79fb0a233eafb3d6396eaca7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 11:25:00 -0700 Subject: [PATCH 247/310] Refactor exceptions in class lessons --- ultimatepython/classes/exception_class.py | 17 +++++------------ ultimatepython/classes/iterator_class.py | 14 +++++--------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index ab437711..e879d87a 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -53,18 +53,11 @@ def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in assert issubclass(DivisionError, CustomError) - try: - divide_positive_numbers(1, 0) - except DivisionError as e: - print(e) - try: - divide_positive_numbers(-1, 1) - except DivisionError as e: - print(e) - try: - divide_positive_numbers(1, -1) - except DivisionError as e: - print(e) + for dividend, divisor in [(1, 0), (-1, 1), (1, -1)]: + try: + divide_positive_numbers(dividend, divisor) + except DivisionError as e: + print(e) result = divide_positive_numbers(1, 1) print(f"Divide(1, 1) = {result}") diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index b75d06f9..3a33a43f 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -130,15 +130,11 @@ def main(): hacker = Employee("Unknown", "Hacker", []) hacker.direct_reports.append(hacker) - try: - list(EmployeeIterator(hacker)) - except IterationError as e: - print(e) - - try: - list(employee_generator(hacker)) - except IterationError as e: - print(e) + for iter_fn in (EmployeeIterator, employee_generator): + try: + list(iter_fn(hacker)) + except IterationError as e: + print(e) if __name__ == "__main__": From 76695029f759441aec41bfb067fdf59f013f1197 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 11:58:32 -0700 Subject: [PATCH 248/310] Expand explanation in weak_ref lesson --- ultimatepython/advanced/weak_ref.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 53d59616..b23ed4d1 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -66,9 +66,12 @@ def setup_and_teardown_servers(registry): # Print server count as proof print("Server count", registry.server_count) - # What's really interesting is that all of this memory goes away after - # a while. Notice how the __del__ calls start happening once we leave - # the scope of this function + # What's really interesting is that servers go away when we leave the + # scope of this function. In this function, each server is created and + # strongly referenced by the `app_servers` variable. When we leave this + # function, the `app_servers` variable no longer exists which brings + # the reference count for each servers from 1 to 0. This triggered + # the Python garbage collector to initiate cleanup for all servers def main(): From 8f3ce9b362dcdfe176f4979a9c410fad79929e1c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 16:27:13 -0700 Subject: [PATCH 249/310] Revise comments in mro lesson --- ultimatepython/advanced/mro.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index ccdda8de..14596e32 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -60,21 +60,19 @@ def ping_pong(self): def main(): - # Show how methods in `ConfusedPlayer` are resolved + # Methods in `ConfusedPlayer` are resolved from child to parent assert ConfusedPlayer.mro() == [ ConfusedPlayer, PongPlayer, NeutralPlayer, BasePlayer, object] - # Show how methods in `IndecisivePlayer` are resolved + # Methods in `IndecisivePlayer` are resolved from child to parent as well assert IndecisivePlayer.mro() == [ IndecisivePlayer, NeutralPlayer, PongPlayer, BasePlayer, object] # Show `ConfusedPlayer` method resolution in action - confused_player = ConfusedPlayer() - confused_player.ping_pong() + ConfusedPlayer().ping_pong() # Show `IndecisivePlayer` method resolution in action - indecisive_player = IndecisivePlayer() - indecisive_player.ping_pong() + IndecisivePlayer().ping_pong() try: # Creating a new class `ConfusedPlayer` and `IndecisivePlayer` From f30e3f003e7be05b3db9b657d64bb84227362f10 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 16:38:52 -0700 Subject: [PATCH 250/310] Add more content in MRO lesson --- ultimatepython/advanced/mro.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 14596e32..3e869f93 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -22,9 +22,13 @@ class NeutralPlayer(BasePlayer): class ConfusedPlayer(PongPlayer, NeutralPlayer): """Confused player. - This is what we call the diamond problem, where A has multiple child - classes that are the same as D's parent classes. Python has the MRO to - determine which `pong` method is applied when calling `super()`. + This is what we call the diamond problem, where `BasePlayer` child classes + are the same as `ConfusedPlayer` parent classes. Python has the MRO to + determine which `ping` and `pong` methods are called via the `super()` + call followed by the respective method. + + The `super()` call is usually used without any parameters, which + means that we start the MRO process from the current class upwards. For more on the subject, please consult this link: @@ -32,9 +36,11 @@ class ConfusedPlayer(PongPlayer, NeutralPlayer): """ def ping(self): + """Override `ping` method.""" print("pINg", self) def ping_pong(self): + """Run `ping` and `pong` in different ways.""" self.ping() super().ping() self.pong() @@ -44,15 +50,21 @@ def ping_pong(self): class IndecisivePlayer(NeutralPlayer, PongPlayer): """Indecisive player. - This exhibits the Python MRO as well. Notice that this class was - created successfully without any conflicts because of `ConfusedPlayer`. - All is good in the world. + Notice that this class was created successfully without any conflicts + despite the fact that the MRO of `ConfusedPlayer` is different. + + Notice that one of the `super()` calls uses additional parameters to + start the MRO process from another class. This is used for demonstrative + purposes and is highly discouraged as this bypasses the default + resolution process. """ def pong(self): + """Override `pong` method.""" print("pONg", self) def ping_pong(self): + """Run `ping` and `pong` in different ways.""" self.ping() super().ping() self.pong() From eb0622347105671cf6caa3d6eecda7ecf72927f4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 17:03:32 -0700 Subject: [PATCH 251/310] Add pep link for abstract class --- ultimatepython/classes/abstract_class.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 56360056..f83d68fe 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -7,6 +7,10 @@ class Employee(ABC): The Employee class is abstract because it inherits the `ABC` class and it has at least one `abstractmethod`. That means you cannot create an instance directly from its constructor. + + For more about abstract classes, click the link below: + + https://www.python.org/dev/peps/pep-3119/ """ def __init__(self, name, title): From ed946f6d3a571f46784788e6a968a85c6d51bf23 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 17:11:57 -0700 Subject: [PATCH 252/310] Add more docs in exception_class --- ultimatepython/classes/exception_class.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index e879d87a..32849d74 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -1,12 +1,12 @@ class CustomError(Exception): """Custom class of errors. - This is a custom base exception for any issues that arise in this module. + This is a custom exception for any issues that arise in this module. One of the reasons why developers design a class like this is for consumption by downstream services and command-line tools. - If you are designing a standalone application with few downstream - consumers, then it makes little sense to make a base class of exceptions. + If we were to design a standalone application with few downstream + consumers, then it makes little sense to define a hierarchy of exceptions. Try using the existing hierarchy of builtin exception classes, which are listed in the Python docs: @@ -53,6 +53,9 @@ def main(): # Exception classes are no different from concrete classes in that # they all have inheritance baked in assert issubclass(DivisionError, CustomError) + + # Try a couple of inputs that are known to throw an error based on + # the exceptions thrown in `divide_positive_numbers` for dividend, divisor in [(1, 0), (-1, 1), (1, -1)]: try: divide_positive_numbers(dividend, divisor) From d4d69063b2e7e240302c60c5978c70a667f3e3a1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 17:56:36 -0700 Subject: [PATCH 253/310] Refactor code in async lesson --- ultimatepython/advanced/async.py | 52 +++++++++++++++++++------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 649db138..39328f39 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -3,6 +3,10 @@ from datetime import datetime from uuid import uuid4 +# Module-level constants +_MILLISECOND = .001 +_HOUR = 3600 + @dataclass class JobRecord: @@ -28,31 +32,37 @@ async def start_job(delay, job_id): return JobRecord(job_id, queue_time, start_time) -async def start_batch(): - """Start a batch of jobs concurrently. +async def schedule_jobs(): + """Schedule jobs concurrently.""" + print(f"{current_time()} -> Send kickoff email") - Each item in the `tasks` list is a `asyncio.Task` instance. Each task - was created by passing a coroutine instance (created by `start_job`) - into the `asyncio.create_task` function. + # Create a single job + single_job = start_job(_MILLISECOND, uuid4().hex) + assert asyncio.iscoroutine(single_job) - After awaiting the list of tasks, we get a list of `JobRecord` items - with reasonable timestamps for queueing and starting. - """ - print(f"{current_time()} -> Send kickoff email") + # Grab a single record from the job + single_record = await single_job + assert isinstance(single_record, JobRecord) + + # Task is a wrapped coroutine which also represents a future + single_task = asyncio.create_task(start_job(_HOUR, uuid4().hex)) + assert asyncio.isfuture(single_task) + + # Futures are different from coroutines in that they can be cancelled + single_task.cancel() + try: + await single_task + except asyncio.exceptions.CancelledError: + assert single_task.cancelled() - tasks = [asyncio.create_task(start_job(i * .01, uuid4().hex)) - for i in range(1, 5)] + # Gather coroutines for batch start + batch_jobs = [start_job(.01, uuid4().hex) for _ in range(10)] + batch_records = await asyncio.gather(*batch_jobs) - # All tasks are instances of Task. They are also instances of Future - # which is important because their completions can be deferred as - # we will see in the next statement - assert all(isinstance(task, asyncio.Task) - and isinstance(task, asyncio.Future) - for task in tasks) + # We get the same amount of records as we have coroutines + assert len(batch_records) == len(batch_jobs) - # Gather all `Task` instances for batch start - job_records = await asyncio.gather(*tasks) - for record in job_records: + for record in batch_records: assert isinstance(record, JobRecord) assert record.queued_at < record.started_at @@ -60,7 +70,7 @@ async def start_batch(): def main(): - asyncio.run(start_batch()) + asyncio.run(schedule_jobs()) if __name__ == "__main__": From 4093de3bfabcfd335943c5b8f699bf085895550f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 17:58:56 -0700 Subject: [PATCH 254/310] Fixup type hinting on JobRecord --- ultimatepython/advanced/async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 39328f39..1576149d 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -12,7 +12,7 @@ class JobRecord: """Job record with useful metadata.""" - tid: int + tid: str queued_at: datetime started_at: datetime From 3e575afff6f992532047331865acd87aaaf0c2ca Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 17:59:49 -0700 Subject: [PATCH 255/310] Change tid to guid for clarity --- ultimatepython/advanced/async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 1576149d..eeeccbb8 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -12,7 +12,7 @@ class JobRecord: """Job record with useful metadata.""" - tid: str + guid: str queued_at: datetime started_at: datetime From 34750e30bfe64c21ab2bd8a09199e07a5b45cd20 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 18:01:06 -0700 Subject: [PATCH 256/310] Fixup docstring in async lesson --- ultimatepython/advanced/async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index eeeccbb8..be9a8cd4 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -23,7 +23,7 @@ def current_time(): async def start_job(delay, job_id): - """Start task_id after a certain delay in seconds.""" + """Start job_id after a certain delay in seconds.""" queue_time = current_time() print(f"{queue_time} -> Queue job {job_id[:16]}...") await asyncio.sleep(delay) From db653443605e3445f1c6c1422b99ef014aa4621d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 18:02:45 -0700 Subject: [PATCH 257/310] Fixup comments in async lesson once more --- ultimatepython/advanced/async.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index be9a8cd4..757c79a4 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -36,11 +36,11 @@ async def schedule_jobs(): """Schedule jobs concurrently.""" print(f"{current_time()} -> Send kickoff email") - # Create a single job + # Create a job which also represents a coroutine single_job = start_job(_MILLISECOND, uuid4().hex) assert asyncio.iscoroutine(single_job) - # Grab a single record from the job + # Grab a job record from the coroutine single_record = await single_job assert isinstance(single_record, JobRecord) From ae4fad83fa3e97a567a7181b92731d60b0584d89 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 18:09:17 -0700 Subject: [PATCH 258/310] Fixup SSN example in decorator --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 3e0b48f2..12c6adc2 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -91,7 +91,7 @@ def main(): # There is so much plain-text data out in the open insecure_data = [ {"username": "johndoe", "country": "USA"}, # User information - ["123-456=7890", "123-456-7891"], # Social security numbers + ["123-456-7890", "123-456-7891"], # Social security numbers [("johndoe", "janedoe"), ("bobdoe", "marydoe")], # Couple names "secretLaunchCode123", # Secret launch code ] From 11c468557ef974bd554ec512892c5a6efa738505 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 18:12:25 -0700 Subject: [PATCH 259/310] Add comment about decorator constant --- ultimatepython/advanced/decorator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 12c6adc2..2ea96f4e 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -1,6 +1,7 @@ from contextlib import contextmanager from functools import wraps +# Module-level constants _HEADER = "---" From d6fa5e3794b4c70e59ab4ea5b2d3b76335527663 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 20:05:53 -0700 Subject: [PATCH 260/310] Refine comments in syntax lessons --- setup.cfg | 5 +++-- ultimatepython/syntax/conditional.py | 12 ++++++------ ultimatepython/syntax/function.py | 8 ++++---- ultimatepython/syntax/loop.py | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/setup.cfg b/setup.cfg index 56857d69..b32ec17b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,9 +3,10 @@ branch = True [coverage:report] exclude_lines = - if __name__ == .__main__.: + skip: (if|else) + def __repr__ raise NotImplementedError - .* # skip:(if|else) + if __name__ == .__main__.: fail_under = 80 omit = venv/** diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index 249e9d51..c70f99da 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -3,18 +3,18 @@ def main(): x_add_two = x + 2 # This condition is obviously true - if x_add_two == 3: # skip:else + if x_add_two == 3: # skip: else print("Math wins") # run # A negated condition can also be true - if not x_add_two == 1: # skip:else + if not x_add_two == 1: # skip: else print("Math wins here too") # run # There are `else` statements as well, which run if the initial condition # fails. Notice that one line gets skipped, and that this conditional # does not help one make a conclusion on the variable's true value if x_add_two == 1: - print("Math lost here...") # skip:if + print("Math lost here...") # skip: if else: print("Math wins otherwise") # run @@ -23,11 +23,11 @@ def main(): # conditions could have been compressed to `x_add_two != 3` for # simplicity. In this case, less logic results in more clarity if x_add_two == 1: - print("Nope not this one...") # skip:if + print("Nope not this one...") # skip: if elif x_add_two == 2: - print("Nope not this one either...") # skip:if + print("Nope not this one either...") # skip: if elif x_add_two < 3 or x_add_two > 3: - print("Nope not quite...") # skip:if + print("Nope not quite...") # skip: if else: print("Math wins finally") # run diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index a8d709bb..b346569d 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -24,19 +24,19 @@ def run_until(fn, n): def main(): - # The add function can be used for numbers as expected + # The `add` function can be used for numbers as expected add_result_int = add(1, 2) print(f"Add(1, 2) = {add_result_int}") - # The add function can be used for strings as well + # The `add` function can be used for strings as well add_result_string = add("hello", " world") print(f"Add('hello', ' world') = '{add_result_string}'") - # Run the input function twice. Notice that we make use of lambda to + # Run the input function twice. Notice that we make use of `lambda` to # create an anonymous function (i.e. a function without a name) that # accepts one input and does something with it. Anonymous functions # are powerful because they allow one to write functions inline, unlike - # add and run_until + # `add` and `run_until` run_until(lambda i: print(f"hello at {i}"), 2) # Did you want to see the `run_until` docstring? Well you can with the diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index f336beb1..f4d07797 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -39,12 +39,12 @@ def main(): print(f"Do while {i} < 5") i *= 2 - # Putting this conditional after the print statement makes the loop + # Putting this conditional after the `print` statement makes the loop # look like the do-while loop from other programming languages if i >= 8: print(f"Break out! {i} is no longer < 5") - # The `break` statement stops the current while loop. + # The `break` statement stops the current `while` loop. # If this `while` loop was nested in another loop, # this statement would not stop the parent loop break @@ -53,7 +53,7 @@ def main(): print(f"Time to continue from {i}") # The `continue` statement returns to the start of the - # current while loop + # current `while` loop continue print(f"Staying alive at {i}") From a1ea6d11b90f6f2b5ba70e94510e70e6bff62e5a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 20:11:19 -0700 Subject: [PATCH 261/310] Improve function print --- ultimatepython/syntax/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index b346569d..67421419 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -37,7 +37,7 @@ def main(): # accepts one input and does something with it. Anonymous functions # are powerful because they allow one to write functions inline, unlike # `add` and `run_until` - run_until(lambda i: print(f"hello at {i}"), 2) + run_until(lambda i: print(f"Say hello at time = {i}"), 2) # Did you want to see the `run_until` docstring? Well you can with the # `__doc__` magic attribute! Remember this one point - everything in From 32f064727a952c26b13e228a7e650a840c0394ce Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 20:15:38 -0700 Subject: [PATCH 262/310] Improve weak_ref docstring --- ultimatepython/advanced/weak_ref.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index b23ed4d1..f76aae8b 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -70,8 +70,9 @@ def setup_and_teardown_servers(registry): # scope of this function. In this function, each server is created and # strongly referenced by the `app_servers` variable. When we leave this # function, the `app_servers` variable no longer exists which brings - # the reference count for each servers from 1 to 0. This triggered - # the Python garbage collector to initiate cleanup for all servers + # the reference count for each servers from 1 to 0. A reference count of + # 0 for each server triggers the garbage collector to initiate cleanup + # for all the servers def main(): From b98fb57e90e415a18ae8a9b17a55f77c6458e799 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 22:56:08 -0700 Subject: [PATCH 263/310] Use f-string in runner --- runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner.py b/runner.py index 986ec878..a906dc0a 100644 --- a/runner.py +++ b/runner.py @@ -39,7 +39,7 @@ def bold_text(text): def main(): print(bold_text(f"Start {root.__name__} runner")) - for item in walk_packages(root.__path__, root.__name__ + "."): + for item in walk_packages(root.__path__, f"{root.__name__}."): mod = import_module(item.name) # Skip modules without a main object From 179c9aee3ad2c608a6e2ebe0661ebca30f93724c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 23:00:50 -0700 Subject: [PATCH 264/310] Make root refs more explicit in runner --- runner.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/runner.py b/runner.py index a906dc0a..e6fc254c 100644 --- a/runner.py +++ b/runner.py @@ -6,7 +6,8 @@ from os import devnull from pkgutil import walk_packages -import ultimatepython as root +from ultimatepython import __name__ as root_name +from ultimatepython import __path__ as root_path # Module-level constants _STYLE_SUCCESS = "\033[92m" @@ -37,9 +38,9 @@ def bold_text(text): def main(): - print(bold_text(f"Start {root.__name__} runner")) + print(bold_text(f"Start {root_name} runner")) - for item in walk_packages(root.__path__, f"{root.__name__}."): + for item in walk_packages(root_path, f"{root_name}."): mod = import_module(item.name) # Skip modules without a main object @@ -61,7 +62,7 @@ def main(): main_func() print(" [PASS]") - print(success_text(f"Finish {root.__name__} runner")) + print(success_text(f"Finish {root_name} runner")) if __name__ == "__main__": From 9a6ae6365b4bcbfdb7081b2511de53312b0f95bf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 23:16:17 -0700 Subject: [PATCH 265/310] Rename decorator and revise docs --- ultimatepython/advanced/decorator.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 2ea96f4e..65a8dd1d 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -30,11 +30,12 @@ def header_section(): yield -def run_with_any(fn): - """Run with a string or a collection with strings exclusively. +def run_with_stringy(fn): + """Run a string function with a string or a collection of strings. We define a custom decorator that allows one to convert a function whose - input is a single string into a function whose input can be many things. + input is a single string into a function whose input can be a string + or a collection of strings. A function decorator consists of the following: @@ -42,9 +43,11 @@ def run_with_any(fn): - A wrapper function that uses the input function The `wrapper` does not need to accept the input function as a parameter - because it can get that from its parent `run_with_any`. Furthermore, the - data that wrapper receives does NOT have to be the same as the data that - the input function needs to accept. + because it can get that from its parent `run_with_any`. Also, the + parameters that `wrapper` receives do NOT have to be the same as the + ones that the input function `fn` needs to receive. However, it is highly + recommended to have the parameter lists for `wrapper` and `fn` line up so + that developers are less likely to get confused. The formal specification for function decorators is here: @@ -80,7 +83,7 @@ def wrapper(obj): return wrapper -@run_with_any +@run_with_stringy def hide_content(content): """Hide half of the string content.""" start_point = len(content) // 2 From 96f836080ad188397c2c2d7519f623824c711039 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 23:35:57 -0700 Subject: [PATCH 266/310] Add reference to composite design pattern --- ultimatepython/classes/iterator_class.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 3a33a43f..b0f04250 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -4,6 +4,15 @@ class Employee: For this module, we're going to remove the inheritance hierarchy in `abstract_class` and make all employees have a `direct_reports` attribute. + + Notice that if we continue adding employees in the `direct_reports` + attribute, those same employees have a `direct_reports` attribute + as well. + + The tree-like structure of this class resembles the Composite design + pattern, and you can find it on Wikipedia: + + https://en.wikipedia.org/wiki/Composite_pattern """ def __init__(self, name, title, direct_reports): From 778a7a37fd997924b515be826541fdd7e6fa5ef0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 29 Aug 2020 23:40:00 -0700 Subject: [PATCH 267/310] Provide a reasonable input to decorator --- ultimatepython/advanced/decorator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 65a8dd1d..54b5ce4b 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -112,9 +112,9 @@ def main(): print("Insecure item", insecure_item) print("Secure item", secure_item) - # Throw an error on object that is neither string nor collection + # Throw an error on a collection with non-string objects try: - hide_content(1) + hide_content([1]) except ValueError as e: with header_section(): print(e) From daff9487d50d000d6a3b64b6e863d4de55d0350f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:06:19 -0700 Subject: [PATCH 268/310] Add more assertions in variable lesson --- ultimatepython/syntax/variable.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 00b0485c..23d30b21 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -8,17 +8,22 @@ def main(): # Notice that each type is a class. Each of the variables above refers # to an instance of the class it belongs to. For example, `a` refers to # the value `1` which belongs to the integer type `int` - a_type = type(a) # - b_type = type(b) # - c_type = type(c) # - d_type = type(d) # + a_type = type(a) + b_type = type(b) + c_type = type(c) + d_type = type(d) - # This leads to an important point: everything is an object in Python. - # Notice that all instances and classes are objects. Also, say hello - # to the `assert` keyword! This is a debugging aid that we use to validate - # the code as we progress through the `main` functions. These statements - # are used to validate the correctness of the data and to reduce - # the amount of standard output that is sent when running `main` + # Also, say hello to the `assert` keyword! This is a debugging aid that + # we will use to validate the code as we progress through `main` + # functions. These statements are used to validate the correctness of + # the data and to reduce the amount of output sent to the screen + assert a_type is int + assert b_type is float + assert c_type is bool + assert d_type is str + + # Everything is an `object` in Python. That means instances are objects + # and classes are objects as well assert isinstance(a, object) and isinstance(a_type, object) assert isinstance(b, object) and isinstance(b_type, object) assert isinstance(c, object) and isinstance(c_type, object) From 09700a65fddc7a5614ae4406931ced8bb0a7569a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:08:13 -0700 Subject: [PATCH 269/310] Revove some backticks from variable --- ultimatepython/syntax/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 23d30b21..844ad965 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -22,7 +22,7 @@ def main(): assert c_type is bool assert d_type is str - # Everything is an `object` in Python. That means instances are objects + # Everything is an object in Python. That means instances are objects # and classes are objects as well assert isinstance(a, object) and isinstance(a_type, object) assert isinstance(b, object) and isinstance(b_type, object) From 1e40fce1e3e328f73b54419ca170c2768bb46e84 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:09:23 -0700 Subject: [PATCH 270/310] Remove more text from variable --- ultimatepython/syntax/variable.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 844ad965..bd825bd3 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -6,8 +6,7 @@ def main(): d = "hello" # Notice that each type is a class. Each of the variables above refers - # to an instance of the class it belongs to. For example, `a` refers to - # the value `1` which belongs to the integer type `int` + # to an instance of the class it belongs to a_type = type(a) b_type = type(b) c_type = type(c) From 33fdc0c9875591acc2a4f5bba4206bfcc6572dec Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:12:10 -0700 Subject: [PATCH 271/310] Make function docstring precise --- ultimatepython/syntax/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index 67421419..d5cbe800 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -17,7 +17,7 @@ def run_until(fn, n): The fact that a function can be passed into `run_until` highlights a core concept that was mentioned before: everything in Python is an object, and - that includes the docstring you are reading right now! + that includes this docstring! """ for i in range(n): fn(i) From 0a32bbf7b41c80bbcd2d75f3db577c2cb3be305f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:13:40 -0700 Subject: [PATCH 272/310] Fixup exceeds comment in loop --- ultimatepython/syntax/loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index f4d07797..6c32b150 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -24,7 +24,7 @@ def main(): # This is a simple `while` loop, similar to a `for` loop except that the # counter is declared outside of the loop and its state is explicitly # managed inside of the loop. The loop will continue until the counter - # exceeds 5 + # exceeds 8 i = 0 while i < 8: print(f"While {i} < 5") @@ -33,7 +33,7 @@ def main(): # This is a `while` loop that is stopped with `break` and its counter is # multiplied in the loop, showing that you can do anything to the # counter. Like the previous `while` loop, this one continues until - # the counter exceeds 5 + # the counter exceeds 8 i = 1 while True: print(f"Do while {i} < 5") From 3c7c22e25410982241ae7dbb3a63eb0ea8d316f8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:18:21 -0700 Subject: [PATCH 273/310] Expand assert statements --- ultimatepython/data_structures/set.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index b4a29365..5efbff3b 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -24,7 +24,8 @@ def main(): # You can compute exclusive multiples multiples_two_exclusive = multiples_two.difference(multiples_four) multiples_four_exclusive = multiples_four.difference(multiples_two) - assert len(multiples_two_exclusive) and len(multiples_four_exclusive) + assert len(multiples_two_exclusive) > 0 + assert len(multiples_four_exclusive) > 0 # Numbers in this bracket are greater than 2 * 9 and less than 4 * 10 for number in multiples_four_exclusive: From 497ed4c75584115382b264a30571bfca23af1bc2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 08:20:34 -0700 Subject: [PATCH 274/310] Move design pattern block up to top --- ultimatepython/classes/iterator_class.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index b0f04250..2fe15fde 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -13,6 +13,12 @@ class Employee: pattern, and you can find it on Wikipedia: https://en.wikipedia.org/wiki/Composite_pattern + + Design patterns are battle-tested ways of structuring code to handle + common problems encountered while writing software in a team setting. + Here's a Wikipedia link for more design patterns: + + https://en.wikipedia.org/wiki/Design_Patterns """ def __init__(self, name, title, direct_reports): @@ -49,12 +55,6 @@ class EmployeeIterator: can find it on Wikipedia: https://en.wikipedia.org/wiki/Iterator_pattern - - Design patterns are battle-tested ways of structuring code to handle - common problems encountered while writing software in a team setting. - Here's a Wikipedia link for more design patterns: - - https://en.wikipedia.org/wiki/Design_Patterns """ def __init__(self, employee): From 2084f296d96bc45b681a55331e8da7874b90f17a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 10:59:05 -0700 Subject: [PATCH 275/310] Create new benchmark lesson --- README.md | 1 + ultimatepython/advanced/benchmark.py | 51 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 ultimatepython/advanced/benchmark.py diff --git a/README.md b/README.md index 209545e8..f83c9c55 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ inspiring and highly encouraged if your goal is to become a true - Method Resolution Order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) + - Benchmark: [cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py new file mode 100644 index 00000000..6378b0c4 --- /dev/null +++ b/ultimatepython/advanced/benchmark.py @@ -0,0 +1,51 @@ +import cProfile +import pstats +import time + +# Module-level constants +_SLEEP_DURATION = .001 + + +def finish_slower(): + """Finish slower.""" + for i in range(20): + time.sleep(_SLEEP_DURATION) + + +def finish_faster(): + """Finish faster.""" + for i in range(10): + time.sleep(_SLEEP_DURATION) + + +def main(): + # Create a profile instance + profile = cProfile.Profile() + + profile.enable() + + for _ in range(2): + finish_slower() + finish_faster() + + profile.disable() + + # Sort statistics by cumulative time spent for a given function call. + # There are many other ways to sort the stats by, but this is the most + # common way of doing so. For more info, please consult Python docs: + # https://docs.python.org/3/library/profile.html + ps = pstats.Stats(profile).sort_stats('cumulative') + + # Notice how many times each function was called. In this case, the main + # bottleneck for `finish_slower` and `finish_faster` is `time.sleep` + # which occurred 60 times. From reading the code and the statistics, we + # can infer that 40 occurrences came from `finish_slower` and 20 came + # from `finish_faster`. It is clear why the latter function runs faster + # in this case, but identifying insights like this are not simple in + # large projects. Consider profiling in isolation when analyzing complex + # classes and functions + ps.print_stats() + + +if __name__ == "__main__": + main() From add67cafc686ded7ae16b971a1d5f0ee2abbea99 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 11:01:36 -0700 Subject: [PATCH 276/310] Fixup comments in benchmark --- ultimatepython/advanced/benchmark.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 6378b0c4..d0b79011 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -30,8 +30,8 @@ def main(): profile.disable() - # Sort statistics by cumulative time spent for a given function call. - # There are many other ways to sort the stats by, but this is the most + # Sort statistics by cumulative time spent for each function call. + # There are other ways to sort the stats by, but this is the most # common way of doing so. For more info, please consult Python docs: # https://docs.python.org/3/library/profile.html ps = pstats.Stats(profile).sort_stats('cumulative') From f467385a821484a75616a57885f50657c7442ab0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 11:02:37 -0700 Subject: [PATCH 277/310] Fix grammar in benchmark --- ultimatepython/advanced/benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index d0b79011..d98fadf3 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -38,7 +38,7 @@ def main(): # Notice how many times each function was called. In this case, the main # bottleneck for `finish_slower` and `finish_faster` is `time.sleep` - # which occurred 60 times. From reading the code and the statistics, we + # which occurred 60 times. By reading the code and the statistics, we # can infer that 40 occurrences came from `finish_slower` and 20 came # from `finish_faster`. It is clear why the latter function runs faster # in this case, but identifying insights like this are not simple in From 9e8bb61ef52562710bffbbe9eed7c3105a8ce10a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 11:04:42 -0700 Subject: [PATCH 278/310] Remove unused vars from benchmark --- ultimatepython/advanced/benchmark.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index d98fadf3..54919ace 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -8,13 +8,13 @@ def finish_slower(): """Finish slower.""" - for i in range(20): + for _ in range(20): time.sleep(_SLEEP_DURATION) def finish_faster(): """Finish faster.""" - for i in range(10): + for _ in range(10): time.sleep(_SLEEP_DURATION) From 082eff5d2dad4a4558ca47b7ddc5539efd2a051b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 11:15:15 -0700 Subject: [PATCH 279/310] Fixup comments in weak_ref --- ultimatepython/advanced/weak_ref.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index f76aae8b..736f15ca 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -72,7 +72,7 @@ def setup_and_teardown_servers(registry): # function, the `app_servers` variable no longer exists which brings # the reference count for each servers from 1 to 0. A reference count of # 0 for each server triggers the garbage collector to initiate cleanup - # for all the servers + # for all of the servers in this function scope def main(): @@ -82,10 +82,10 @@ def main(): # Setup and teardown servers with the registry setup_and_teardown_servers(registry) - # Notice that our registry has no recollection of the servers. The - # benefit is that our registry is allowing the garbage collector to do - # its job effectively and remove orphaned servers from the previous - # call, keeping our software memory-efficient + # Notice that our registry has no recollection of the servers because + # it uses weak references. The benefit is that our registry allows the + # garbage collector to do its job effectively and remove servers from + # the previous call, keeping our software memory-efficient assert registry.servers == set() assert registry.server_count == 0 From 03dfbc3ddc4fb19febdf6666db2b526fa3c0c2a1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 30 Aug 2020 15:36:08 -0700 Subject: [PATCH 280/310] Remove best practices category --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f83c9c55..eb77d9cc 100644 --- a/README.md +++ b/README.md @@ -80,15 +80,14 @@ inspiring and highly encouraged if your goal is to become a true :necktie: = Interview resource, :test_tube: = Code samples, -:brain: = Project ideas, -:book: = Best practices +:brain: = Project ideas ### GitHub repositories Keep learning by reading from other well-regarded resources. - [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) -- [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:, :book:) +- [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:) - [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) - [karan/Projects](https://github.com/karan/Projects) (:brain:) - [vinta/awesome-python](https://github.com/vinta/awesome-python) (:brain:) From 80fb773f786176121625355710fe20039bc1e6b5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 31 Aug 2020 21:59:54 -0700 Subject: [PATCH 281/310] Revise README slightly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb77d9cc..b86726bb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ print("Ultimate Python study guide") I created a GitHub repo to share what I've learned about [core Python](https://www.python.org/) over the past 5+ years of using it as a college graduate, an employee at -large-scale companies and as an open-source contributor of repositories like +large-scale companies and an open-source contributor of repositories like [Celery](https://github.com/celery/celery) and [Full Stack Python](https://github.com/mattmakai/fullstackpython.com). I look forward to seeing more people learn Python and pursue their passions From b5917a1cd1a2c10e47efb0eed8d2c4cfa21fddae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 31 Aug 2020 22:12:06 -0700 Subject: [PATCH 282/310] Fix weak_ref comment --- ultimatepython/advanced/weak_ref.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 736f15ca..348870bc 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -70,9 +70,9 @@ def setup_and_teardown_servers(registry): # scope of this function. In this function, each server is created and # strongly referenced by the `app_servers` variable. When we leave this # function, the `app_servers` variable no longer exists which brings - # the reference count for each servers from 1 to 0. A reference count of - # 0 for each server triggers the garbage collector to initiate cleanup - # for all of the servers in this function scope + # the reference count for each server from 1 to 0. A reference count of + # 0 for each server triggers the garbage collector to run the cleanup + # process for all of the servers in this function scope def main(): From 020adb06b480a2e185d00a12f97236c7dc496ba2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 1 Sep 2020 11:25:20 -0700 Subject: [PATCH 283/310] Refine wording in exception_class --- ultimatepython/classes/exception_class.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 32849d74..1e68bfdc 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -5,9 +5,9 @@ class CustomError(Exception): One of the reasons why developers design a class like this is for consumption by downstream services and command-line tools. - If we were to design a standalone application with few downstream - consumers, then it makes little sense to define a hierarchy of exceptions. - Try using the existing hierarchy of builtin exception classes, which are + If we designed a standalone application with no downstream consumers, then + it makes little sense to define a custom hierarchy of exceptions. Instead, + try using the existing hierarchy of builtin exception classes, which are listed in the Python docs: https://docs.python.org/3/library/exceptions.html @@ -24,10 +24,9 @@ class DivisionError(CustomError): - NegativeDividendError - NegativeDivisorError - That being said, there's a point of diminishing returns when - we design too many exceptions. It's better to design a few exceptions - that most developers handle than design many exceptions that - few developers handle. + That being said, there's a point of diminishing returns when we design + too many exceptions. It is better to design few exceptions that many + developers handle than design many exceptions that few developers handle. """ From 48ecb3a59ecdf3fb0c127afdda6899383fdd7f0e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 1 Sep 2020 11:31:22 -0700 Subject: [PATCH 284/310] Replace one with us in decorator lesson --- ultimatepython/advanced/decorator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 54b5ce4b..b2ea197d 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -33,7 +33,7 @@ def header_section(): def run_with_stringy(fn): """Run a string function with a string or a collection of strings. - We define a custom decorator that allows one to convert a function whose + We define a custom decorator that allows us to convert a function whose input is a single string into a function whose input can be a string or a collection of strings. From 51ec181a4e70d1aa897c0e3abb246d2d0db14b19 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 1 Sep 2020 21:42:13 -0700 Subject: [PATCH 285/310] Create new string lesson --- README.md | 1 + ultimatepython/data_structures/string.py | 46 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 ultimatepython/data_structures/string.py diff --git a/README.md b/README.md index b86726bb..dcd1a48b 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ inspiring and highly encouraged if your goal is to become a true - Set: [Set operations](ultimatepython/data_structures/set.py) - Dict: [Dictionary operations](ultimatepython/data_structures/dict.py) (:cake:) - Comprehension: [list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) + - String: [String operations](ultimatepython/data_structures/string.py) (:cake:) 4. **Classes** - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py new file mode 100644 index 00000000..f868fe7a --- /dev/null +++ b/ultimatepython/data_structures/string.py @@ -0,0 +1,46 @@ +# Module-level constants +_DELIMITER = " | " +_PADDING = 10 + + +def main(): + # String is one of the most robust data structures around + content = "Ultimate Python study guide" + + # We can compute its length just like all other data structures + assert len(content) > 0 + + # Notice that we pad the right of the label with spaces + print("Original:".ljust(_PADDING), content) + + # Like tuples, we cannot change the data in a string. However, we can + # create a new string from an existing string + new_content = f"{content.upper()}{_DELIMITER}{content.lower()}" + print("New:".ljust(_PADDING), new_content) + + # We can split one string into a list of strings + split_content = new_content.split(_DELIMITER) + assert isinstance(split_content, list) + assert len(split_content) == 2 + assert all(isinstance(item, str) for item in split_content) + + # A two-element list can be decomposed as two variables + upper_content, lower_content = split_content + assert upper_content.isupper() and lower_content.islower() + + # Keep in mind that the two content variables reference substrings + # in the original string they were split from + assert upper_content in new_content + assert new_content.startswith(upper_content) + assert lower_content in new_content + assert new_content.endswith(lower_content) + + # We can also join multiple strings into one string + joined_content = _DELIMITER.join(split_content) + assert isinstance(joined_content, str) + assert new_content == joined_content + print("Joined:".ljust(_PADDING), joined_content) + + +if __name__ == '__main__': + main() From af21f68ad5ceed2d772b80f2e15a70f6e303cb62 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 1 Sep 2020 21:54:59 -0700 Subject: [PATCH 286/310] Refactor label logic --- ultimatepython/data_structures/string.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index f868fe7a..bd29da5c 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -3,6 +3,11 @@ _PADDING = 10 +def label(name, padding=_PADDING): + """Get name as title with right-side padding.""" + return f"{name.title()}:".ljust(padding) + + def main(): # String is one of the most robust data structures around content = "Ultimate Python study guide" @@ -10,13 +15,13 @@ def main(): # We can compute its length just like all other data structures assert len(content) > 0 - # Notice that we pad the right of the label with spaces - print("Original:".ljust(_PADDING), content) + # And unsurprisingly, we can print its contents as well + print(label("original"), content) # Like tuples, we cannot change the data in a string. However, we can - # create a new string from an existing string + # create a new string from existing strings new_content = f"{content.upper()}{_DELIMITER}{content.lower()}" - print("New:".ljust(_PADDING), new_content) + print(label("new"), new_content) # We can split one string into a list of strings split_content = new_content.split(_DELIMITER) @@ -28,7 +33,7 @@ def main(): upper_content, lower_content = split_content assert upper_content.isupper() and lower_content.islower() - # Keep in mind that the two content variables reference substrings + # Keep in mind that the content variables reference substrings # in the original string they were split from assert upper_content in new_content assert new_content.startswith(upper_content) @@ -39,7 +44,7 @@ def main(): joined_content = _DELIMITER.join(split_content) assert isinstance(joined_content, str) assert new_content == joined_content - print("Joined:".ljust(_PADDING), joined_content) + print(label("joined"), joined_content) if __name__ == '__main__': From c87c106a3037227b7a0fe0055575761ac6f898a4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 2 Sep 2020 06:06:55 -0700 Subject: [PATCH 287/310] Add is_valid_record to async --- ultimatepython/advanced/async.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 757c79a4..fcea1ea2 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -17,6 +17,11 @@ class JobRecord: started_at: datetime +def is_valid_record(record): + """Check whether job record is valid or not.""" + return record.queued_at < record.started_at + + def current_time(): """Return current time that is timezone-naive.""" return datetime.now() @@ -42,7 +47,7 @@ async def schedule_jobs(): # Grab a job record from the coroutine single_record = await single_job - assert isinstance(single_record, JobRecord) + assert is_valid_record(single_record) # Task is a wrapped coroutine which also represents a future single_task = asyncio.create_task(start_job(_HOUR, uuid4().hex)) @@ -62,9 +67,8 @@ async def schedule_jobs(): # We get the same amount of records as we have coroutines assert len(batch_records) == len(batch_jobs) - for record in batch_records: - assert isinstance(record, JobRecord) - assert record.queued_at < record.started_at + for batch_record in batch_records: + assert is_valid_record(batch_record) print(f"{current_time()} -> Send confirmation email") From d157a395f19176c57d80de52416256d18293a022 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 2 Sep 2020 06:08:16 -0700 Subject: [PATCH 288/310] Add _ to private routines --- ultimatepython/advanced/async.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index fcea1ea2..ab59d449 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -17,29 +17,29 @@ class JobRecord: started_at: datetime -def is_valid_record(record): +def _is_valid_record(record): """Check whether job record is valid or not.""" return record.queued_at < record.started_at -def current_time(): +def _current_time(): """Return current time that is timezone-naive.""" return datetime.now() async def start_job(delay, job_id): """Start job_id after a certain delay in seconds.""" - queue_time = current_time() + queue_time = _current_time() print(f"{queue_time} -> Queue job {job_id[:16]}...") await asyncio.sleep(delay) - start_time = current_time() + start_time = _current_time() print(f"{start_time} -> Start job {job_id[:16]}...") return JobRecord(job_id, queue_time, start_time) async def schedule_jobs(): """Schedule jobs concurrently.""" - print(f"{current_time()} -> Send kickoff email") + print(f"{_current_time()} -> Send kickoff email") # Create a job which also represents a coroutine single_job = start_job(_MILLISECOND, uuid4().hex) @@ -47,7 +47,7 @@ async def schedule_jobs(): # Grab a job record from the coroutine single_record = await single_job - assert is_valid_record(single_record) + assert _is_valid_record(single_record) # Task is a wrapped coroutine which also represents a future single_task = asyncio.create_task(start_job(_HOUR, uuid4().hex)) @@ -68,9 +68,9 @@ async def schedule_jobs(): assert len(batch_records) == len(batch_jobs) for batch_record in batch_records: - assert is_valid_record(batch_record) + assert _is_valid_record(batch_record) - print(f"{current_time()} -> Send confirmation email") + print(f"{_current_time()} -> Send confirmation email") def main(): From 927fa8c66a1d9fca32a8149555e96a7c618660f4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 2 Sep 2020 06:17:51 -0700 Subject: [PATCH 289/310] Add more docstring at BaseModel --- ultimatepython/advanced/meta_class.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 6c47db3d..dd07ab67 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -102,7 +102,15 @@ class IntegerField(BaseField): class BaseModel(metaclass=ModelMeta): - """Base model.""" + """Base model. + + Notice how `ModelMeta` is injected at the base class. The base class + and its subclasses will be processed by the method `__new__` in the + `ModelMeta` class before being created. + + In short, think of a metaclass as the creator of classes. This is + very similar to how classes are the creator of instances. + """ __abstract__ = True # This is NOT a real table row_id = IntegerField() From 3f5725ea8319b78958a074ddcfbb8b095764cac4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 3 Sep 2020 11:15:47 -0700 Subject: [PATCH 290/310] Update string.py --- ultimatepython/data_structures/string.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index bd29da5c..7c536fac 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -15,6 +15,10 @@ def main(): # We can compute its length just like all other data structures assert len(content) > 0 + # We can use range slices to get substrings from the content + assert content[:8] == "Ultimate" + assert content[9:15] == "Python" + # And unsurprisingly, we can print its contents as well print(label("original"), content) From cdc962ebbba754225b11710e125491539550dae8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 3 Sep 2020 11:17:18 -0700 Subject: [PATCH 291/310] Update string.py --- ultimatepython/data_structures/string.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 7c536fac..afb22884 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -18,6 +18,7 @@ def main(): # We can use range slices to get substrings from the content assert content[:8] == "Ultimate" assert content[9:15] == "Python" + assert content[::-1] == "ediug yduts nohtyP etamitlU" # And unsurprisingly, we can print its contents as well print(label("original"), content) From e028a980543907f50d0f6e92fce83203a2f3a49c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 4 Sep 2020 15:37:15 -0700 Subject: [PATCH 292/310] Update variable.py --- ultimatepython/syntax/variable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index bd825bd3..015a3e79 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -13,8 +13,8 @@ def main(): d_type = type(d) # Also, say hello to the `assert` keyword! This is a debugging aid that - # we will use to validate the code as we progress through `main` - # functions. These statements are used to validate the correctness of + # we will use to validate the code as we progress through each `main` + # function. These statements are used to validate the correctness of # the data and to reduce the amount of output sent to the screen assert a_type is int assert b_type is float From c3acc803ebfe2b196e4a1602426711d1e1384dd3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 4 Sep 2020 21:14:31 -0700 Subject: [PATCH 293/310] FIxup comment in weak_ref --- ultimatepython/advanced/weak_ref.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 348870bc..c021dc9f 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -82,10 +82,11 @@ def main(): # Setup and teardown servers with the registry setup_and_teardown_servers(registry) - # Notice that our registry has no recollection of the servers because - # it uses weak references. The benefit is that our registry allows the - # garbage collector to do its job effectively and remove servers from - # the previous call, keeping our software memory-efficient + # Notice that our registry does not remember the servers because + # it uses weak references. Because there are no strong references + # to the created servers in `setup_and_teardown_servers`, the + # garbage collector cleans up the servers. This behavior is usually + # desired if we want to keep our software memory-efficient assert registry.servers == set() assert registry.server_count == 0 From 81702cf815f24e0b72f25370e3bff36ea74e30bf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 4 Sep 2020 21:16:11 -0700 Subject: [PATCH 294/310] Fixup IndecisivePlayer docs --- ultimatepython/advanced/mro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 3e869f93..71442fb3 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -51,7 +51,7 @@ class IndecisivePlayer(NeutralPlayer, PongPlayer): """Indecisive player. Notice that this class was created successfully without any conflicts - despite the fact that the MRO of `ConfusedPlayer` is different. + even though the MRO of `ConfusedPlayer` is different. Notice that one of the `super()` calls uses additional parameters to start the MRO process from another class. This is used for demonstrative From dcde8ad303b7c29f054012a91b0c1db1fe67d050 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 4 Sep 2020 21:17:47 -0700 Subject: [PATCH 295/310] Fix mro main comments --- ultimatepython/advanced/mro.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 71442fb3..89bdd6c3 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -72,11 +72,11 @@ def ping_pong(self): def main(): - # Methods in `ConfusedPlayer` are resolved from child to parent + # `ConfusedPlayer` methods are resolved from child to parent like this assert ConfusedPlayer.mro() == [ ConfusedPlayer, PongPlayer, NeutralPlayer, BasePlayer, object] - # Methods in `IndecisivePlayer` are resolved from child to parent as well + # `IndecisivePlayer` methods are resolved from child to parent like this assert IndecisivePlayer.mro() == [ IndecisivePlayer, NeutralPlayer, PongPlayer, BasePlayer, object] From 2a3b03e0c42fe5c3a1b59182d83b2fdb4e10937e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 4 Sep 2020 21:22:16 -0700 Subject: [PATCH 296/310] Fix meta_class docs --- ultimatepython/advanced/meta_class.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index dd07ab67..855b20c9 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -5,20 +5,19 @@ class ModelMeta(type): """Model metaclass. By studying how SQLAlchemy and Django ORM work under the hood, we can see - how one may create a rudimentary metaclass for adding useful abstractions - to class definitions at runtime. That being said, this metaclass is a toy - example and does not reflect everything that happens in the metaclass - definitions defined in either framework. + a metaclass can add useful abstractions to class definitions at runtime. + That being said, this metaclass is a toy example and does not reflect + everything that happens in either framework. - The main use cases for a metaclass are to (A) modify a class before - it is visible to a developer and (B) add a class to a dynamic registry + The main use cases for a metaclass are (A) to modify a class before + it is visible to a developer and (B) to add a class to a dynamic registry for further automation. Do NOT use a metaclass if a task can be done more simply with class composition, class inheritance or functions. Simple code is the reason why Python is attractive for 99% of users. - For more on meta-classes, visit the link below: + For more on metaclass mechanisms, visit the link below: https://realpython.com/python-metaclasses/ """ From 62b4dd6937e39bddad144026e0fbd78bd5309d94 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 5 Sep 2020 07:44:08 -0700 Subject: [PATCH 297/310] Remove extra word from abstract_class --- ultimatepython/classes/abstract_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index f83d68fe..ee2947ec 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -5,7 +5,7 @@ class Employee(ABC): """Abstract definition of an employee. The Employee class is abstract because it inherits the `ABC` class - and it has at least one `abstractmethod`. That means you cannot create + and has at least one `abstractmethod`. That means you cannot create an instance directly from its constructor. For more about abstract classes, click the link below: From 7d927e9fdd71d0576059db24213e08528ba2a9a7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 5 Sep 2020 11:59:08 -0700 Subject: [PATCH 298/310] Add new content to basic_class --- ultimatepython/classes/basic_class.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 30688b96..6ba34ccf 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,3 +1,6 @@ +from inspect import signature + + class Car: """Basic definition of a car. @@ -45,6 +48,21 @@ def main(): assert issubclass(Car, object) assert isinstance(Car, object) + # Now let's look at the `drive` method in more detail + driving = getattr(car, "drive") + + # The variable method is the same as the instance method + assert driving == car.drive + + # The method is bound to the instance + driving.__self__ == car + + # And there is only one parameter for `driving` because `__self__` + # binding is implicit + driving_params = signature(driving).parameters + assert len(driving_params) == 1 + assert "rate_in_mph" in driving_params + if __name__ == "__main__": main() From 05c3582de37dc8c7038ac2aef584785ff22d35e5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 5 Sep 2020 12:02:13 -0700 Subject: [PATCH 299/310] Add missing assert keyword --- ultimatepython/classes/basic_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 6ba34ccf..e9112826 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -55,7 +55,7 @@ def main(): assert driving == car.drive # The method is bound to the instance - driving.__self__ == car + assert driving.__self__ == car # And there is only one parameter for `driving` because `__self__` # binding is implicit From 16fc1cdcb5641ad1d165095d19e28fc65d47d661 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 11:29:15 -0700 Subject: [PATCH 300/310] Fixup comment in basic_class --- ultimatepython/classes/basic_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index e9112826..741513dc 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -54,7 +54,7 @@ def main(): # The variable method is the same as the instance method assert driving == car.drive - # The method is bound to the instance + # The variable method is bound to the instance assert driving.__self__ == car # And there is only one parameter for `driving` because `__self__` From a37a3bb5011bc9a374f0fb21d0ad940c64a5b6f8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 11:34:33 -0700 Subject: [PATCH 301/310] Fix more comments in basic_class --- ultimatepython/classes/basic_class.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 741513dc..f4c51dbd 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -43,12 +43,13 @@ def main(): # As a reminder: everything in Python is an object! And that applies # to classes in the most interesting way - because they're not only # subclasses of object - they are also instances of object. This - # means that you can modify the Car class at runtime just like any + # means that you can modify the Car class at runtime, just like any # other piece of data we define in Python assert issubclass(Car, object) assert isinstance(Car, object) - # Now let's look at the `drive` method in more detail + # To emphasize the idea that everything is an object, let's look at + # the `drive` method in more detail driving = getattr(car, "drive") # The variable method is the same as the instance method From 9e360e0769db1a72ea833fd8c151d4a3f3fd8c49 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 11:54:21 -0700 Subject: [PATCH 302/310] Add assertions and content to string lesson --- ultimatepython/data_structures/string.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index afb22884..6866a61a 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -38,14 +38,27 @@ def main(): upper_content, lower_content = split_content assert upper_content.isupper() and lower_content.islower() - # Keep in mind that the content variables reference substrings - # in the original string they were split from + # Notice that the data in `upper_content` and `lower_content` exists + # in the `new_content` variable as expected assert upper_content in new_content assert new_content.startswith(upper_content) assert lower_content in new_content assert new_content.endswith(lower_content) - # We can also join multiple strings into one string + # Let's print the split variables for visual proof + print(label("upper"), upper_content) + print(label("lower"), lower_content) + + # Notice that `upper_content` and `lower_content` are smaller + # than `new_content` and have the same length as the original + # `content` they were derived from + assert len(upper_content) < len(new_content) + assert len(lower_content) < len(new_content) + assert len(upper_content) == len(lower_content) == len(content) + + # We can also join `upper_content` and `lower_content` back into one + # string with the same contents as `new_content`. The `join` method is + # useful for joining an arbitrary amount of text items together. joined_content = _DELIMITER.join(split_content) assert isinstance(joined_content, str) assert new_content == joined_content From a8ca298255e5a9e5ebe8223caeff7b0e048bf388 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 11:59:37 -0700 Subject: [PATCH 303/310] Fix wording in set lesson --- ultimatepython/data_structures/set.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 5efbff3b..f5a756e8 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -11,17 +11,17 @@ def main(): print("Multiples of two", multiples_two) print("Multiples of three", multiples_four) - # One cannot decide in which order the numbers come out - so what - # we're looking for is fundamental truths like divisibility against + # We cannot decide in which order the numbers come out - so let's + # look for fundamental truths instead, such as divisibility against # 2 and 4. We do this by checking whether the modulus of 2 and 4 - # yields 0 (i.e. no remainder from division) + # yields 0 (i.e. no remainder from performing a division) multiples_common = multiples_two.intersection(multiples_four) for number in multiples_common: assert number % 2 == 0 and number % 4 == 0 print("Multiples in common", multiples_common) - # You can compute exclusive multiples + # We can compute exclusive multiples multiples_two_exclusive = multiples_two.difference(multiples_four) multiples_four_exclusive = multiples_four.difference(multiples_two) assert len(multiples_two_exclusive) > 0 From 45b3c67fc4aec6b201962f541c9a298af1b49585 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 12:02:06 -0700 Subject: [PATCH 304/310] Fixup comment in list lesson --- ultimatepython/data_structures/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index d021a3fb..011fe3e8 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -33,7 +33,7 @@ def main(): for letter, number in zip(letters, numbers): print("Letter and number", letter, number) - # The for loop worked because the lengths of both lists are equal + # The `for` loop worked because the lengths of both lists are equal assert len(letters) == len(numbers) # To see the indices and values of a list at the same time, you can use From e3d6a78148bb5f3a4964bc84b36657384fb93b73 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 15:44:01 -0700 Subject: [PATCH 305/310] Improve content in dict lesson --- ultimatepython/data_structures/dict.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 4db66145..3bfe20ee 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,22 +1,28 @@ def main(): - # Each student has a name key and a GPA value + # Let's create a mapping of student name to GPA student_gpa = {"john": 3.5, "jane": 4.0, "bob": 2.8, "mary": 3.2} - # You can access the value of a particular key - assert student_gpa["john"] == 3.5 + # There are four students + assert len(student_gpa) == 4 + + # Each student has a name key and a GPA value + assert len(student_gpa.keys()) == len(student_gpa.values()) - # You can access the dictionary keys in isolation + # You can get the names in isolation for student in student_gpa.keys(): assert len(student) > 2 - # You can access the dictionary values in isolation + # You can get the GPAs in isolation for gpa in student_gpa.values(): assert gpa > 2.0 - # You can access the dictionary keys and values simultaneously + # You can get the GPA for a specific student + assert student_gpa["john"] == 3.5 + + # You can access the student and GPA simultaneously for student, gpa in student_gpa.items(): print(f"Student {student} has a {gpa} GPA") From b24546cf488c2792404d8780c2a7b8d45a74d08c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 15:48:13 -0700 Subject: [PATCH 306/310] Fix first two comments in dict lesson --- ultimatepython/data_structures/dict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 3bfe20ee..10577c1a 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,11 +1,11 @@ def main(): - # Let's create a mapping of student name to GPA + # Let's create a dictionary with student keys and GPA values student_gpa = {"john": 3.5, "jane": 4.0, "bob": 2.8, "mary": 3.2} - # There are four students + # There are four student records in this dictionary assert len(student_gpa) == 4 # Each student has a name key and a GPA value From e7213e8af7dcc2937fcdd5fd4d93f4f1b5a215d7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 16:06:52 -0700 Subject: [PATCH 307/310] Fix comments about range in lessons --- ultimatepython/data_structures/list.py | 2 +- ultimatepython/data_structures/string.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 011fe3e8..037205b9 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -12,7 +12,7 @@ def main(): # Each of the strings is a letter assert letter.isalpha() - # You can get a subset of letters with the range selector + # You can get a subset of letters with range slices assert letters[1:] == ["b", "c", "d", "e"] assert letters[:-1] == ["a", "b", "c", "d"] assert letters[1:-2] == ["b", "c"] diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 6866a61a..5894d041 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -9,10 +9,10 @@ def label(name, padding=_PADDING): def main(): - # String is one of the most robust data structures around + # Strings are some of the most robust data structures around content = "Ultimate Python study guide" - # We can compute its length just like all other data structures + # We can compute a string's length just like all other data structures assert len(content) > 0 # We can use range slices to get substrings from the content From 7653da99c58f72252aa4143bf988a4172f517033 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 16:15:18 -0700 Subject: [PATCH 308/310] Rename you to we/us in lessons --- ultimatepython/classes/abstract_class.py | 2 +- ultimatepython/classes/basic_class.py | 2 +- ultimatepython/classes/iterator_class.py | 8 ++++---- ultimatepython/data_structures/comprehension.py | 2 +- ultimatepython/data_structures/dict.py | 8 ++++---- ultimatepython/data_structures/list.py | 4 ++-- ultimatepython/data_structures/tuple.py | 2 +- ultimatepython/syntax/expression.py | 12 ++++++------ ultimatepython/syntax/function.py | 5 ++--- ultimatepython/syntax/loop.py | 2 +- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index ee2947ec..3b9a7183 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -5,7 +5,7 @@ class Employee(ABC): """Abstract definition of an employee. The Employee class is abstract because it inherits the `ABC` class - and has at least one `abstractmethod`. That means you cannot create + and has at least one `abstractmethod`. That means we cannot create an instance directly from its constructor. For more about abstract classes, click the link below: diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index f4c51dbd..f4940df4 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -43,7 +43,7 @@ def main(): # As a reminder: everything in Python is an object! And that applies # to classes in the most interesting way - because they're not only # subclasses of object - they are also instances of object. This - # means that you can modify the Car class at runtime, just like any + # means that we can modify the Car class at runtime, just like any # other piece of data we define in Python assert issubclass(Car, object) assert isinstance(Car, object) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 2fe15fde..3091e1c5 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -10,7 +10,7 @@ class Employee: as well. The tree-like structure of this class resembles the Composite design - pattern, and you can find it on Wikipedia: + pattern, and it can be found on Wikipedia: https://en.wikipedia.org/wiki/Composite_pattern @@ -51,8 +51,8 @@ class EmployeeIterator: We do this by providing what are called magic methods. Other people call them d-under methods because they have double-underscores. - An iterator class resembles the Iterator design pattern, and you - can find it on Wikipedia: + An iterator class resembles the Iterator design pattern, and it + can be found on Wikipedia: https://en.wikipedia.org/wiki/Iterator_pattern """ @@ -71,7 +71,7 @@ def __next__(self): The logic may seem complex, but it's actually a common algorithm used in traversing a relationship graph. It is called depth-first - search and you can find it on Wikipedia: + search and it can be found on Wikipedia: https://en.wikipedia.org/wiki/Depth-first_search """ diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index 188a6397..f7b71fbc 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -1,5 +1,5 @@ def main(): - # One interesting fact about data structures is that you can build + # One interesting fact about data structures is that we can build # them with comprehensions. Let's explain how the first one works: # we just want to create zeros so our expression is set to `0` # since no computing is required; because `0` is a constant value, diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 10577c1a..1c46c54c 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -11,18 +11,18 @@ def main(): # Each student has a name key and a GPA value assert len(student_gpa.keys()) == len(student_gpa.values()) - # You can get the names in isolation + # We can get the names in isolation for student in student_gpa.keys(): assert len(student) > 2 - # You can get the GPAs in isolation + # We can get the GPAs in isolation for gpa in student_gpa.values(): assert gpa > 2.0 - # You can get the GPA for a specific student + # We can get the GPA for a specific student assert student_gpa["john"] == 3.5 - # You can access the student and GPA simultaneously + # We can access the student and GPA simultaneously for student, gpa in student_gpa.items(): print(f"Student {student} has a {gpa} GPA") diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 037205b9..71d643ea 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -12,7 +12,7 @@ def main(): # Each of the strings is a letter assert letter.isalpha() - # You can get a subset of letters with range slices + # We can get a subset of letters with range slices assert letters[1:] == ["b", "c", "d", "e"] assert letters[:-1] == ["a", "b", "c", "d"] assert letters[1:-2] == ["b", "c"] @@ -36,7 +36,7 @@ def main(): # The `for` loop worked because the lengths of both lists are equal assert len(letters) == len(numbers) - # To see the indices and values of a list at the same time, you can use + # To see the indices and values of a list at the same time, we can use # `enumerate` to transform the list of values into an iterator of # index-number pairs for index, number in enumerate(numbers): diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index a6bf4e95..8cc766b8 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -10,7 +10,7 @@ def main(): for number in immutable: print("Immutable", number) - # But its contents cannot be changed. As an alternative, you can + # But its contents cannot be changed. As an alternative, we can # create new tuples from existing tuples bigger_immutable = immutable + (5, 6) print(bigger_immutable) diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 6a3c8144..62495fe4 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -6,7 +6,7 @@ def main(): print("Add integer", x + 1) # An expression can be chained indefinitely. This concept of chaining - # expressions is powerful because it allows you to compose simple pieces + # expressions is powerful because it allows us to compose simple pieces # of code into larger pieces of code over time print("Multiply integers", x * 2 * 2 * 2) @@ -14,13 +14,13 @@ def main(): # of type 'float' by default print("Divide as float", x / 2) - # If an integer division is desired, then an extra slash - # must be added to the expression + # If an integer division is desired, then an extra slash must be + # added to the expression print("Divide by integer", x // 2) - # Powers of an integer can be leveraged too. If you want more math - # features, then you will have to leverage the builtin `math` library, - # a third-party library or your own library + # Powers of an integer can be leveraged too. If more features are + # needed, then leverage the builtin `math` library or a third-party + # library. Otherwise, we have to build our own math library print("Power of integer", x * 2 ** 3) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index d5cbe800..e18792fa 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -39,9 +39,8 @@ def main(): # `add` and `run_until` run_until(lambda i: print(f"Say hello at time = {i}"), 2) - # Did you want to see the `run_until` docstring? Well you can with the - # `__doc__` magic attribute! Remember this one point - everything in - # Python is an object + # We can see the `run_until` docstring by accessing the `__doc__` magic + # attribute! Remember this - everything in Python is an object print(run_until.__doc__) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 6c32b150..e516b3af 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -31,7 +31,7 @@ def main(): i += 2 # This is a `while` loop that is stopped with `break` and its counter is - # multiplied in the loop, showing that you can do anything to the + # multiplied in the loop, showing that we can do anything to the # counter. Like the previous `while` loop, this one continues until # the counter exceeds 8 i = 1 From b670601c59d00e68fdde4e6432638b05b5457fe5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 16:20:40 -0700 Subject: [PATCH 309/310] Refactor __repr__ in Employee class --- ultimatepython/classes/abstract_class.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 3b9a7183..a2abdb62 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -20,6 +20,9 @@ def __init__(self, name, title): def __str__(self): return f"{self.name} ({self.title})" + def __repr__(self): + return f"<{type(self).__name__} name={self.name}>" + @abstractmethod def do_work(self): raise NotImplementedError @@ -48,9 +51,6 @@ def __init__(self, name, title, skill): super().__init__(name, title) self.skill = skill - def __repr__(self): - return f"" - def do_work(self): print(f"{self} is coding in {self.skill}") @@ -78,9 +78,6 @@ def __init__(self, name, title, direct_reports): super().__init__(name, title) self.direct_reports = direct_reports - def __repr__(self): - return f"" - def do_work(self): print(f"{self} is meeting up with {self.direct_reports}") From 0adbfb0d8a02f9be117cf7d1cbe893fafe3829a0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 6 Sep 2020 16:25:49 -0700 Subject: [PATCH 310/310] Fix double-quote for benchmark lesson --- ultimatepython/advanced/benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 54919ace..e8bcceb4 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -34,7 +34,7 @@ def main(): # There are other ways to sort the stats by, but this is the most # common way of doing so. For more info, please consult Python docs: # https://docs.python.org/3/library/profile.html - ps = pstats.Stats(profile).sort_stats('cumulative') + ps = pstats.Stats(profile).sort_stats("cumulative") # Notice how many times each function was called. In this case, the main # bottleneck for `finish_slower` and `finish_faster` is `time.sleep`