From c4b2852f746d67fe688b12c0b22297127bc57e02 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 2 May 2020 17:32:30 -0700 Subject: [PATCH 001/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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/553] 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` From 2bcc8233af7b21388b587812d3e5124189b8cdec Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 00:05:13 -0700 Subject: [PATCH 311/553] Add more content to list --- ultimatepython/data_structures/list.py | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 71d643ea..3e0e7d86 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -27,6 +27,17 @@ def main(): numbers = [1, 2, 3, 4, 5] print("Numbers", numbers) + # Note that a list is ordered and mutable. If we want to reverse the order + # of the `numbers` list, we can start at index 0 and end halfway. At each + # cycle, we swap values in the front with values in the back + for ix_front in range(len(numbers) // 2): + ix_back = len(numbers) - ix_front - 1 + numbers[ix_front], numbers[ix_back] = numbers[ix_back], numbers[ix_front] + + # Suppose that we want to go back to the original order, we can use the + # builtin `reverse` method in lists + numbers.reverse() + # 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 @@ -51,16 +62,13 @@ def main(): 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, 0, 0, 0] - mutable.append(0) - mutable.pop() # pop out the fifth zero - mutable[0] = 100 # first item - mutable[1] = 30 # second item - mutable[-1] = 50 # last item - mutable[-2] = 20 # second to last item - print("Mutable list", mutable) + # Notice that lists have variable length and can be modified to have + # more elements. Lists can also be modified to have fewer elements + lengthy = [] + for i in range(5): # [0, 1, 2, 3, 4] + lengthy.append(i) + lengthy.pop() # pop out the 4 + print("Lengthy list", lengthy) if __name__ == "__main__": From d2993749ceb2b97aa34da40438197d759f548b56 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 09:51:45 -0700 Subject: [PATCH 312/553] Remove print statements (#5) --- setup.cfg | 1 + ultimatepython/advanced/async.py | 6 -- ultimatepython/advanced/benchmark.py | 10 +++- ultimatepython/advanced/decorator.py | 40 ++----------- ultimatepython/advanced/meta_class.py | 11 ++-- ultimatepython/advanced/mro.py | 36 +++++++----- ultimatepython/advanced/weak_ref.py | 6 -- ultimatepython/classes/abstract_class.py | 57 +++++++------------ ultimatepython/classes/basic_class.py | 4 +- ultimatepython/classes/exception_class.py | 6 +- ultimatepython/classes/iterator_class.py | 11 ++-- .../data_structures/comprehension.py | 12 ++-- ultimatepython/data_structures/dict.py | 2 +- ultimatepython/data_structures/list.py | 35 ++++++------ ultimatepython/data_structures/set.py | 27 ++++++--- ultimatepython/data_structures/string.py | 16 +----- ultimatepython/data_structures/tuple.py | 10 ++-- ultimatepython/syntax/conditional.py | 24 +++++--- ultimatepython/syntax/expression.py | 10 ++-- ultimatepython/syntax/function.py | 29 +++++----- ultimatepython/syntax/loop.py | 18 +++--- ultimatepython/syntax/variable.py | 7 --- 22 files changed, 166 insertions(+), 212 deletions(-) diff --git a/setup.cfg b/setup.cfg index b32ec17b..db4d247a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ exclude_lines = def __repr__ raise NotImplementedError if __name__ == .__main__.: + any\( fail_under = 80 omit = venv/** diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index ab59d449..fbad9c95 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -30,17 +30,13 @@ def _current_time(): async def start_job(delay, job_id): """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) 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") - # Create a job which also represents a coroutine single_job = start_job(_MILLISECOND, uuid4().hex) assert asyncio.iscoroutine(single_job) @@ -70,8 +66,6 @@ async def schedule_jobs(): for batch_record in batch_records: assert _is_valid_record(batch_record) - print(f"{_current_time()} -> Send confirmation email") - def main(): asyncio.run(schedule_jobs()) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index e8bcceb4..f0a19f86 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -3,6 +3,8 @@ import time # Module-level constants +from io import StringIO + _SLEEP_DURATION = .001 @@ -34,7 +36,8 @@ 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") + bytes_obj = StringIO() + ps = pstats.Stats(profile, stream=bytes_obj).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` @@ -46,6 +49,11 @@ def main(): # classes and functions ps.print_stats() + lines = bytes_obj.getvalue().split("\n") + time_sleep_called = any("60" in line and "time.sleep" in line + for line in lines) + assert time_sleep_called is True + if __name__ == "__main__": main() diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index b2ea197d..b0bcddd6 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -1,34 +1,5 @@ -from contextlib import contextmanager from functools import wraps -# Module-level constants -_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 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) - yield - def run_with_stringy(fn): """Run a string function with a string or a collection of strings. @@ -108,16 +79,15 @@ def main(): # 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) - print("Secure item", secure_item) + assert insecure_item != secure_item # Throw an error on a collection with non-string objects + input_fails = False try: hide_content([1]) - except ValueError as e: - with header_section(): - print(e) + except ValueError: + input_fails = True + assert input_fails is True if __name__ == "__main__": diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 855b20c9..766f79f8 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -133,15 +133,12 @@ class AddressModel(BaseModel): def main(): # Each model was modified at runtime with ModelMeta - for real_model in BaseModel.__subclasses__(): - 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) + assert UserModel.model_name == "user_rocks" + assert AddressModel.model_name == "address" # Each model was registered at runtime with ModelMeta - for meta_table in ModelMeta.tables.values(): - print("ModelMeta table", meta_table) + assert UserModel.model_name in ModelMeta.tables + assert AddressModel.model_name in ModelMeta.tables # Base model was given special treatment, as expected assert not BaseModel.is_registered diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 89bdd6c3..0d2fac3f 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -2,17 +2,17 @@ class BasePlayer: """Base player.""" def ping(self): - print("ping", self) + return f"ping {type(self).__name__}" def pong(self): - print("pong", self) + return f"pong {type(self).__name__}" class PongPlayer(BasePlayer): """Pong player.""" def pong(self): - print("PONG", self) + return f"PONG {type(self).__name__}" class NeutralPlayer(BasePlayer): @@ -37,14 +37,16 @@ class ConfusedPlayer(PongPlayer, NeutralPlayer): def ping(self): """Override `ping` method.""" - print("pINg", self) + return f"pINg {type(self).__name__}" def ping_pong(self): """Run `ping` and `pong` in different ways.""" - self.ping() - super().ping() - self.pong() - super().pong() + return [ + self.ping(), + super().ping(), + self.pong(), + super().pong() + ] class IndecisivePlayer(NeutralPlayer, PongPlayer): @@ -61,14 +63,16 @@ class IndecisivePlayer(NeutralPlayer, PongPlayer): def pong(self): """Override `pong` method.""" - print("pONg", self) + return f"pONg {type(self).__name__}" def ping_pong(self): """Run `ping` and `pong` in different ways.""" - self.ping() - super().ping() - self.pong() - super(PongPlayer, self).pong() # bypass MRO to `BasePlayer` + return [ + self.ping(), + super().ping(), + self.pong(), + super(PongPlayer, self).pong() # bypass MRO to `BasePlayer` + ] def main(): @@ -86,14 +90,16 @@ def main(): # Show `IndecisivePlayer` method resolution in action IndecisivePlayer().ping_pong() + class_creation_fails = False try: # 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) + except TypeError: + class_creation_fails = True + assert class_creation_fails is True if __name__ == '__main__': diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index c021dc9f..e746e8ef 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -63,9 +63,6 @@ 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 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 @@ -90,9 +87,6 @@ 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() diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index a2abdb62..6dfee9a7 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -18,21 +18,14 @@ def __init__(self, name, title): self.title = title def __str__(self): - return f"{self.name} ({self.title})" - - def __repr__(self): - return f"<{type(self).__name__} name={self.name}>" + return self.name @abstractmethod def do_work(self): raise NotImplementedError @abstractmethod - def join_meeting(self): - raise NotImplementedError - - @abstractmethod - def relax(self): + def do_relax(self): raise NotImplementedError @@ -52,17 +45,14 @@ def __init__(self, name, title, skill): self.skill = skill def do_work(self): - print(f"{self} is coding in {self.skill}") - - def join_meeting(self): - print(f"{self} is joining a meeting on {self.skill}") + return f"{self} is coding in {self.skill}" - def relax(self): - print(f"{self} is relaxing by watching YouTube") + def do_relax(self): + return f"{self} is watching YouTube" def do_refactor(self): """Do the hard work of refactoring code, unlike managers.""" - print(f"{self} is refactoring code") + return f"{self} is refactoring code" class Manager(Employee): @@ -79,17 +69,14 @@ def __init__(self, name, title, direct_reports): self.direct_reports = direct_reports def do_work(self): - print(f"{self} is meeting up with {self.direct_reports}") + return f"{self} is meeting up with {len(self.direct_reports)} reports" - def join_meeting(self): - print(f"{self} is joining a meeting with {self.direct_reports}") - - def relax(self): - print(f"{self} is taking a trip to the Bahamas") + def do_relax(self): + return 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") + return f"{self} is hiring employees" def main(): @@ -97,28 +84,22 @@ def main(): 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, Employee)) - assert not isinstance(engineer, Manager) - print("Created", repr(engineer)) - - engineer.do_work() - engineer.join_meeting() - engineer.relax() - engineer.do_refactor() + assert isinstance(engineer_john, (Engineer, Employee)) + assert not isinstance(engineer_john, Manager) + assert engineer_john.do_work() == "John Doe is coding in Android" + assert engineer_john.do_relax() == "John Doe is watching YouTube" + assert engineer_john.do_refactor() == "John Doe is refactoring code" # Declare manager with engineers as direct reports + engineers = [engineer_john, engineer_jane] manager_max = Manager("Max Doe", "Engineering Manager", engineers) 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() - manager_max.do_hire() + assert manager_max.do_work() == "Max Doe is meeting up with 2 reports" + assert manager_max.do_relax() == "Max Doe is taking a trip to the Bahamas" + assert manager_max.do_hire() == "Max Doe is hiring employees" if __name__ == '__main__': diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index f4940df4..07b23ad3 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -27,7 +27,7 @@ def __str__(self): def drive(self, rate_in_mph): """Drive car at a certain rate.""" - print(f"{self} is driving at {rate_in_mph} MPH") + return f"{self} is driving at {rate_in_mph} MPH" def main(): @@ -38,7 +38,7 @@ def main(): assert repr(car) != str(car) # Call a method on the class constructor - car.drive(75) + assert car.drive(75) == "Bumble Bee (2000) is driving at 75 MPH" # As a reminder: everything in Python is an object! And that applies # to classes in the most interesting way - because they're not only diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 1e68bfdc..df9c2baa 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -59,9 +59,11 @@ def main(): try: divide_positive_numbers(dividend, divisor) except DivisionError as e: - print(e) + assert str(e).startswith("Cannot have a") + + # Now let's do it correctly to skip all the exceptions result = divide_positive_numbers(1, 1) - print(f"Divide(1, 1) = {result}") + assert result == 1 if __name__ == '__main__': diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 3091e1c5..7a2d0ea5 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,3 +1,7 @@ +# Module-level constants +_ITERATION_MESSAGE = "Cyclic loop detected" + + class Employee: """Generic employee class. @@ -79,7 +83,7 @@ def __next__(self): raise StopIteration employee = self.employees_to_visit.pop() if employee.name in self.employees_visited: - raise IterationError("Cyclic loop detected") + raise IterationError(_ITERATION_MESSAGE) self.employees_visited.add(employee.name) for report in employee.direct_reports: self.employees_to_visit.append(report) @@ -111,7 +115,7 @@ 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 IterationError(_ITERATION_MESSAGE) visited.add(employee.name) for report in employee.direct_reports: to_visit.append(report) @@ -133,7 +137,6 @@ def main(): # Make sure that the employees are who we expect them to be assert all(isinstance(emp, Employee) for emp in employees) - print(employees) # This is not a good day for this company hacker = Employee("Unknown", "Hacker", []) @@ -143,7 +146,7 @@ def main(): try: list(iter_fn(hacker)) except IterationError as e: - print(e) + assert str(e) == _ITERATION_MESSAGE if __name__ == "__main__": diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index f7b71fbc..ba44191b 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -6,24 +6,26 @@ def main(): # 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)` list_comp = [0 for _ in range(5)] - print("List of zeros", list_comp) + assert list_comp == [0] * 5 words = ["cat", "mice", "horse", "bat"] # 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) + assert tuple_comp == (3, 4, 5, 3) # 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) + assert set_comp == {3, 4, 5} # 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) + assert dict_comp == {"cat": 3, + "mice": 4, + "horse": 5, + "bat": 3} if __name__ == "__main__": diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 1c46c54c..d5944510 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -24,7 +24,7 @@ def main(): # We can access the student and GPA simultaneously for student, gpa in student_gpa.items(): - print(f"Student {student} has a {gpa} GPA") + assert student_gpa[student] == gpa if __name__ == "__main__": diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 3e0e7d86..e847b6e3 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -3,7 +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"] - print("Letters", letters) + assert letters[0] == "a" + assert letters[4] == letters[-1] == "e" for letter in letters: # Each of the strings is one character @@ -22,10 +23,11 @@ def main(): assert letters[::-1] == ["e", "d", "c", "b", "a"] # This is a list of integers where - # 1 is an integer at index 0 + # 1 is an integer at index 0 and # 5 is an integer at index 4 numbers = [1, 2, 3, 4, 5] - print("Numbers", numbers) + assert numbers[0] == 1 + assert numbers[4] == numbers[-1] == 5 # Note that a list is ordered and mutable. If we want to reverse the order # of the `numbers` list, we can start at index 0 and end halfway. At each @@ -40,22 +42,20 @@ def main(): # 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) + # do the same for the remaining indices. 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, (letter, number) in enumerate(zip(letters, numbers)): + assert letters[index] == letter + assert numbers[index] == number # 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, we can use - # `enumerate` to transform the list of values into an iterator of - # index-number pairs - for index, number in enumerate(numbers): - 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) + assert matrix[1][0] == 4 + assert matrix[0][1] == 2 # 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 @@ -65,10 +65,11 @@ def main(): # Notice that lists have variable length and can be modified to have # more elements. Lists can also be modified to have fewer elements lengthy = [] - for i in range(5): # [0, 1, 2, 3, 4] - lengthy.append(i) - lengthy.pop() # pop out the 4 - print("Lengthy list", lengthy) + for i in range(5): + lengthy.append(i) # add 0..4 to the back + assert lengthy == [0, 1, 2, 3, 4] + lengthy.pop() # pop out the 4 from the back + assert lengthy == [0, 1, 2, 3] if __name__ == "__main__": diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index f5a756e8..60ed4e7a 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -1,5 +1,19 @@ def main(): - # Define two `set` collections + # Let's define one `set` for starters + simple_set = {0, 1, 2} + + # A set is dynamic like a `list` and `tuple` + simple_set.add(3) + simple_set.add(4) + + # Unlike a `list and `tuple`, it is not an ordered sequence as it + # prevent duplicates from being added + for _ in range(5): + simple_set.add(0) + simple_set.add(4) + assert simple_set == {0, 1, 2, 3, 4} + + # Now let's define two `set` collections multiples_two = set() multiples_four = set() @@ -8,8 +22,9 @@ def main(): multiples_two.add(i * 2) multiples_four.add(i * 4) - print("Multiples of two", multiples_two) - print("Multiples of three", multiples_four) + # As we can see, both sets have similarities and differences + assert multiples_two == {0, 2, 4, 6, 8, 10, 12, 14, 16, 18} + assert multiples_four == {0, 4, 8, 12, 16, 20, 24, 28, 32, 36} # We cannot decide in which order the numbers come out - so let's # look for fundamental truths instead, such as divisibility against @@ -19,8 +34,6 @@ def main(): for number in multiples_common: assert number % 2 == 0 and number % 4 == 0 - print("Multiples in common", multiples_common) - # We can compute exclusive multiples multiples_two_exclusive = multiples_two.difference(multiples_four) multiples_four_exclusive = multiples_four.difference(multiples_two) @@ -31,13 +44,9 @@ def main(): 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 a subset of set B assert multiples_four_exclusive.issubset(multiples_four) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 5894d041..d77bd18d 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -1,11 +1,5 @@ # Module-level constants _DELIMITER = " | " -_PADDING = 10 - - -def label(name, padding=_PADDING): - """Get name as title with right-side padding.""" - return f"{name.title()}:".ljust(padding) def main(): @@ -20,13 +14,10 @@ def main(): 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) - # Like tuples, we cannot change the data in a string. However, we can # create a new string from existing strings new_content = f"{content.upper()}{_DELIMITER}{content.lower()}" - print(label("new"), new_content) + assert _DELIMITER in new_content # We can split one string into a list of strings split_content = new_content.split(_DELIMITER) @@ -45,10 +36,6 @@ def main(): assert lower_content in new_content assert new_content.endswith(lower_content) - # 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 @@ -62,7 +49,6 @@ def main(): joined_content = _DELIMITER.join(split_content) assert isinstance(joined_content, str) assert new_content == joined_content - print(label("joined"), joined_content) if __name__ == '__main__': diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 8cc766b8..310ac1ea 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -1,21 +1,21 @@ 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 + assert immutable[-1] == 4 # It can be iterated over like a list - for number in immutable: - print("Immutable", number) + for ix, number in enumerate(immutable): + assert immutable[ix] == number # 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) + assert bigger_immutable == (1, 2, 3, 4, 5, 6) smaller_immutable = immutable[0:2] - print(smaller_immutable) + assert smaller_immutable == (1, 2) if __name__ == "__main__": diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index c70f99da..c093b001 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -3,33 +3,41 @@ def main(): x_add_two = x + 2 # This condition is obviously true + ran_1 = False if x_add_two == 3: # skip: else - print("Math wins") # run + ran_1 = True # run + assert ran_1 is True # A negated condition can also be true + ran_2 = False if not x_add_two == 1: # skip: else - print("Math wins here too") # run + ran_2 = True # run + assert ran_2 is True # 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 + ran_3 = False if x_add_two == 1: - print("Math lost here...") # skip: if + ran_3 = False # skip: if else: - print("Math wins otherwise") # run + ran_3 = True # run + assert ran_3 is True # 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 + ran_4 = False if x_add_two == 1: - print("Nope not this one...") # skip: if + ran_4 = False # skip: if elif x_add_two == 2: - print("Nope not this one either...") # skip: if + ran_4 = False # skip: if elif x_add_two < 3 or x_add_two > 3: - print("Nope not quite...") # skip: if + ran_4 = False # skip: if else: - print("Math wins finally") # run + ran_4 = True # run + assert ran_4 is True if __name__ == "__main__": diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 62495fe4..4132fae8 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("Add integer", x + 1) + assert x + 1 == 2 # An expression can be chained indefinitely. This concept of chaining # 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) + assert x * 2 * 2 * 2 == 8 # Division is a bit tricky in Python because it returns a result # of type 'float' by default - print("Divide as float", x / 2) + assert x / 2 == 0.5 # If an integer division is desired, then an extra slash must be # added to the expression - print("Divide by integer", x // 2) + assert x // 2 == 0 # 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) + assert x * 2 ** 3 == 8 if __name__ == "__main__": diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index e18792fa..bec654e7 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -9,39 +9,42 @@ def add(x, y): return x + y -def run_until(fn, n): - """Run a function from 0 until n - 1. +def sum_until(fn, n): + """Sum a function output 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. Like `add`, `run_until` returns 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 this docstring! """ + total = 0 for i in range(n): - fn(i) + total += fn(i) + return total 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}") + assert add_result_int == 3 # The `add` function can be used for strings as well add_result_string = add("hello", " world") - print(f"Add('hello', ' world') = '{add_result_string}'") + assert add_result_string == "hello world" - # 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"Say hello at time = {i}"), 2) + # Run the input function multiple times. 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_results = sum_until(lambda i: i * 100, 5) + assert run_results == 1000, run_results # 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__) + assert "includes this docstring!" in sum_until.__doc__ if __name__ == "__main__": diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index e516b3af..0dc6b9a7 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -8,7 +8,7 @@ def main(): total += i # The answer is...10! - print(f"Sum(0..4) = {total}") + assert total == 10 # 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 @@ -19,7 +19,7 @@ def main(): fib *= i # The answer is...120! - print(f"Fibonacci(5..1) = {fib}") + assert fib == 120 # 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 @@ -27,36 +27,32 @@ def main(): # exceeds 8 i = 0 while i < 8: - print(f"While {i} < 5") i += 2 + # The `while` loop terminated at this value + assert i == 8 + # This is a `while` loop that is stopped with `break` and its counter is # 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 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 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 if i == 2: - print(f"Time to continue from {i}") - # The `continue` statement returns to the start of the # current `while` loop continue - print(f"Staying alive at {i}") + # The `while` loop terminated at this value + assert i == 8 if __name__ == "__main__": diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 015a3e79..1d95fe81 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -28,13 +28,6 @@ def main(): 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, a_type) - print("b", b, b_type) - print("c", c, c_type) - print("d", d, d_type) - if __name__ == "__main__": main() From b00bb4d1087113d964d53f5df15d89d093e15d8a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 09:52:22 -0700 Subject: [PATCH 313/553] Fix import ordering in benchmark --- ultimatepython/advanced/benchmark.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index f0a19f86..7d2704a9 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -1,7 +1,6 @@ import cProfile import pstats import time - # Module-level constants from io import StringIO From 439ba652f26ab71cb33892c07d5127351aba9177 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 10:35:25 -0700 Subject: [PATCH 314/553] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f6b36c29 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at samhuang91@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From b8bf652bbdfe5b592cfc77ef609968e28d9e1a10 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 21:20:32 -0700 Subject: [PATCH 315/553] Add missing assertions in mro --- ultimatepython/advanced/mro.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 0d2fac3f..28cef803 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -2,17 +2,17 @@ class BasePlayer: """Base player.""" def ping(self): - return f"ping {type(self).__name__}" + return "ping" def pong(self): - return f"pong {type(self).__name__}" + return "pong" class PongPlayer(BasePlayer): """Pong player.""" def pong(self): - return f"PONG {type(self).__name__}" + return "PONG" class NeutralPlayer(BasePlayer): @@ -37,7 +37,7 @@ class ConfusedPlayer(PongPlayer, NeutralPlayer): def ping(self): """Override `ping` method.""" - return f"pINg {type(self).__name__}" + return "pINg" def ping_pong(self): """Run `ping` and `pong` in different ways.""" @@ -63,7 +63,7 @@ class IndecisivePlayer(NeutralPlayer, PongPlayer): def pong(self): """Override `pong` method.""" - return f"pONg {type(self).__name__}" + return "pONg" def ping_pong(self): """Run `ping` and `pong` in different ways.""" @@ -85,10 +85,10 @@ def main(): IndecisivePlayer, NeutralPlayer, PongPlayer, BasePlayer, object] # Show `ConfusedPlayer` method resolution in action - ConfusedPlayer().ping_pong() + assert ConfusedPlayer().ping_pong() == ["pINg", "ping", "PONG", "PONG"] # Show `IndecisivePlayer` method resolution in action - IndecisivePlayer().ping_pong() + assert IndecisivePlayer().ping_pong() == ["ping", "ping", "pONg", "pong"] class_creation_fails = False try: From c286d5911bfde95ec021da86a1818163974a376f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 7 Sep 2020 21:49:49 -0700 Subject: [PATCH 316/553] Fixup imports in benchmark --- ultimatepython/advanced/benchmark.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 7d2704a9..74618efd 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -1,9 +1,9 @@ import cProfile +import io import pstats import time -# Module-level constants -from io import StringIO +# Module-level constants _SLEEP_DURATION = .001 @@ -35,7 +35,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 - bytes_obj = StringIO() + bytes_obj = io.StringIO() ps = pstats.Stats(profile, stream=bytes_obj).sort_stats("cumulative") # Notice how many times each function was called. In this case, the main From 0ed30ae5ed4f33a473436938078425b866eaa33a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:02:20 -0700 Subject: [PATCH 317/553] Flesh out basic_class representations --- ultimatepython/classes/basic_class.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 07b23ad3..0eb64038 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -35,7 +35,8 @@ def main(): car = Car("Bumble", "Bee", 2000, 200000.0) # Formal and informal representations are not the same - assert repr(car) != str(car) + assert repr(car) == "" + assert str(car) == "Bumble Bee (2000)" # Call a method on the class constructor assert car.drive(75) == "Bumble Bee (2000) is driving at 75 MPH" From 9a8ae7f008447a5d5b6d4d704a2215a885df61d9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:07:24 -0700 Subject: [PATCH 318/553] Add comments to abstract_class --- ultimatepython/classes/abstract_class.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 6dfee9a7..8f21745b 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -83,20 +83,25 @@ 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] + + # These engineers are employees but not managers + assert all(isinstance(engineer, Employee) for engineer in engineers) + assert all(not isinstance(engineer, Manager) for engineer in engineers) - assert isinstance(engineer_john, (Engineer, Employee)) - assert not isinstance(engineer_john, Manager) + # They can work, relax and refactor assert engineer_john.do_work() == "John Doe is coding in Android" assert engineer_john.do_relax() == "John Doe is watching YouTube" assert engineer_john.do_refactor() == "John Doe is refactoring code" # Declare manager with engineers as direct reports - engineers = [engineer_john, engineer_jane] manager_max = Manager("Max Doe", "Engineering Manager", engineers) - assert isinstance(manager_max, (Manager, Employee)) + # Managers are employees but not engineers + assert isinstance(manager_max, Employee) assert not isinstance(manager_max, Engineer) + # They can work, relax and hire assert manager_max.do_work() == "Max Doe is meeting up with 2 reports" assert manager_max.do_relax() == "Max Doe is taking a trip to the Bahamas" assert manager_max.do_hire() == "Max Doe is hiring employees" From 9f5d8428850c6cbc90f263b6198e6d58410e9d53 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:12:30 -0700 Subject: [PATCH 319/553] Add more text to mro lesson --- 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 28cef803..dcdf7fda 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -57,7 +57,7 @@ class IndecisivePlayer(NeutralPlayer, PongPlayer): 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 + purposes and is highly discouraged as this bypasses the default method resolution process. """ From 7f3d80f953b16826b1d568579fabb81896467615 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:13:09 -0700 Subject: [PATCH 320/553] Fix another comment in mro lesson --- 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 dcdf7fda..791f5710 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -93,7 +93,7 @@ def main(): class_creation_fails = False try: # Creating a new class `ConfusedPlayer` and `IndecisivePlayer` - # result in a `TypeError` because both classes have mismatched + # results 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), {}) From 4ef885e18cfd0801dbc3e8d917c54f3abe4bf6a4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:20:59 -0700 Subject: [PATCH 321/553] Add more assertions in meta_class --- ultimatepython/advanced/meta_class.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 766f79f8..54dc5f50 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -132,11 +132,11 @@ class AddressModel(BaseModel): def main(): - # Each model was modified at runtime with ModelMeta + # Each model was modified at runtime with `ModelMeta` assert UserModel.model_name == "user_rocks" assert AddressModel.model_name == "address" - # Each model was registered at runtime with ModelMeta + # Each model was registered at runtime with `ModelMeta` assert UserModel.model_name in ModelMeta.tables assert AddressModel.model_name in ModelMeta.tables @@ -145,6 +145,21 @@ def main(): assert BaseModel.model_name is None assert BaseModel.model_table is None + # Each model was created by `ModelMeta` + assert isinstance(BaseModel, ModelMeta) + assert all(isinstance(model, ModelMeta) for model in BaseModel.__subclasses__()) + + # And `ModelMeta` was created by `type` + assert isinstance(ModelMeta, type) + + # And `type` was created by `type` itself + assert isinstance(type, type) + + # And everything in Python is an object + assert isinstance(UserModel, object) + assert isinstance(ModelMeta, object) + assert isinstance(type, object) + if __name__ == '__main__': main() From 55ada097bcc7fc39021bab594e63df70177eb5f2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:22:44 -0700 Subject: [PATCH 322/553] Use BaseModel instead of UserModel --- 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 54dc5f50..88cf56fc 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -156,7 +156,7 @@ def main(): assert isinstance(type, type) # And everything in Python is an object - assert isinstance(UserModel, object) + assert isinstance(BaseModel, object) assert isinstance(ModelMeta, object) assert isinstance(type, object) From 736fb30d380d5ac23a8ffddb74d95e4459ce67f8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:25:34 -0700 Subject: [PATCH 323/553] Add another round of assertions in meta_class --- ultimatepython/advanced/meta_class.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 88cf56fc..7805c397 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -137,7 +137,9 @@ def main(): assert AddressModel.model_name == "address" # Each model was registered at runtime with `ModelMeta` + assert UserModel.is_registered assert UserModel.model_name in ModelMeta.tables + assert AddressModel.is_registered assert AddressModel.model_name in ModelMeta.tables # Base model was given special treatment, as expected From 4b59417d6c2896214c5bf468da868b0d819b1ce5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:32:15 -0700 Subject: [PATCH 324/553] Add more assertions to meta_class --- ultimatepython/advanced/meta_class.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 7805c397..a4d5f01e 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -132,24 +132,27 @@ class AddressModel(BaseModel): def main(): - # Each model was modified at runtime with `ModelMeta` + # Real models were modified at runtime with `ModelMeta` assert UserModel.model_name == "user_rocks" assert AddressModel.model_name == "address" - # Each model was registered at runtime with `ModelMeta` + # Real models were registered at runtime with `ModelMeta` assert UserModel.is_registered - assert UserModel.model_name in ModelMeta.tables assert AddressModel.is_registered - assert AddressModel.model_name in ModelMeta.tables - # Base model was given special treatment, as expected + # Real models have a `ModelTable` that creates a database table + assert isinstance(ModelMeta.tables[UserModel.model_name], ModelTable) + assert isinstance(ModelMeta.tables[AddressModel.model_name], ModelTable) + + # Base model was given special treatment at runtime assert not BaseModel.is_registered assert BaseModel.model_name is None assert BaseModel.model_table is None - # Each model was created by `ModelMeta` + # Every model was created by `ModelMeta` assert isinstance(BaseModel, ModelMeta) - assert all(isinstance(model, ModelMeta) for model in BaseModel.__subclasses__()) + assert all(isinstance(model, ModelMeta) + for model in BaseModel.__subclasses__()) # And `ModelMeta` was created by `type` assert isinstance(ModelMeta, type) @@ -157,7 +160,7 @@ def main(): # And `type` was created by `type` itself assert isinstance(type, type) - # And everything in Python is an object + # And everything in Python is an object! assert isinstance(BaseModel, object) assert isinstance(ModelMeta, object) assert isinstance(type, object) From 88afb308387394fe22a90202e11888f9abd492bc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:33:01 -0700 Subject: [PATCH 325/553] Fix single comment in meta_class --- 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 a4d5f01e..10037ae8 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -140,7 +140,7 @@ def main(): assert UserModel.is_registered assert AddressModel.is_registered - # Real models have a `ModelTable` that creates a database table + # Real models have a `ModelTable` that can be used for DB setup assert isinstance(ModelMeta.tables[UserModel.model_name], ModelTable) assert isinstance(ModelMeta.tables[AddressModel.model_name], ModelTable) From 44579fb831b8d5a26b50d4ccf5b1a41749a81e3a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:42:14 -0700 Subject: [PATCH 326/553] Remove unused variables in conditional --- ultimatepython/syntax/conditional.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index c093b001..7c0dc031 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -17,7 +17,6 @@ def main(): # 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 - ran_3 = False if x_add_two == 1: ran_3 = False # skip: if else: @@ -28,7 +27,6 @@ def main(): # 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 - ran_4 = False if x_add_two == 1: ran_4 = False # skip: if elif x_add_two == 2: From 5e63868976b3b0c6fec0da3eea81c1189023ca31 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:45:13 -0700 Subject: [PATCH 327/553] Add comments to comprehension lesson --- ultimatepython/data_structures/comprehension.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index ba44191b..fd38ecbf 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -8,6 +8,8 @@ def main(): list_comp = [0 for _ in range(5)] assert list_comp == [0] * 5 + # For the next comprehension operations, let's see what we can do + # with a list of 3-5 letter words words = ["cat", "mice", "horse", "bat"] # Tuple comprehension can find the length for each word From 45d233e074b387dc8bc4ec081b55f0ae7e688c46 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:46:34 -0700 Subject: [PATCH 328/553] Use consistent comments in set lesson --- ultimatepython/data_structures/set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 60ed4e7a..b6937904 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -52,7 +52,7 @@ def main(): assert multiples_four_exclusive.issubset(multiples_four) assert multiples_four.issubset(multiples_all) - # Check that set A is a subset and superset of itself + # Check if set A is a subset and superset of itself assert multiples_all.issubset(multiples_all) assert multiples_all.issuperset(multiples_all) From 9fabc3cbe3caa7ef5168e90c92d649a459fed726 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 07:50:40 -0700 Subject: [PATCH 329/553] Reuse constant 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 fbad9c95..62bfdcc5 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -57,7 +57,7 @@ async def schedule_jobs(): assert single_task.cancelled() # Gather coroutines for batch start - batch_jobs = [start_job(.01, uuid4().hex) for _ in range(10)] + batch_jobs = [start_job(_MILLISECOND, uuid4().hex) for _ in range(10)] batch_records = await asyncio.gather(*batch_jobs) # We get the same amount of records as we have coroutines From 18846e96d2cc93f1688429dcb19ec87890050440 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 08:53:57 -0700 Subject: [PATCH 330/553] Change past to present tense in meta_class --- ultimatepython/advanced/meta_class.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 10037ae8..c41ff339 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -132,11 +132,11 @@ class AddressModel(BaseModel): def main(): - # Real models were modified at runtime with `ModelMeta` + # Real models are modified at runtime with `ModelMeta` assert UserModel.model_name == "user_rocks" assert AddressModel.model_name == "address" - # Real models were registered at runtime with `ModelMeta` + # Real models are registered at runtime with `ModelMeta` assert UserModel.is_registered assert AddressModel.is_registered @@ -144,20 +144,20 @@ def main(): assert isinstance(ModelMeta.tables[UserModel.model_name], ModelTable) assert isinstance(ModelMeta.tables[AddressModel.model_name], ModelTable) - # Base model was given special treatment at runtime + # Base model is given special treatment at runtime assert not BaseModel.is_registered assert BaseModel.model_name is None assert BaseModel.model_table is None - # Every model was created by `ModelMeta` + # Every model is created by `ModelMeta` assert isinstance(BaseModel, ModelMeta) assert all(isinstance(model, ModelMeta) for model in BaseModel.__subclasses__()) - # And `ModelMeta` was created by `type` + # And `ModelMeta` is created by `type` assert isinstance(ModelMeta, type) - # And `type` was created by `type` itself + # And `type` is created by `type` itself assert isinstance(type, type) # And everything in Python is an object! From dc1aa2e7ef416e9943db5746b4d63312604e6b01 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 08:56:46 -0700 Subject: [PATCH 331/553] Optimize lines in benchmark lesson --- ultimatepython/advanced/benchmark.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 74618efd..ad3bef0c 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -47,10 +47,8 @@ def main(): # large projects. Consider profiling in isolation when analyzing complex # classes and functions ps.print_stats() - - lines = bytes_obj.getvalue().split("\n") time_sleep_called = any("60" in line and "time.sleep" in line - for line in lines) + for line in bytes_obj.getvalue().split("\n")) assert time_sleep_called is True From 0a6539c1daa5cda29a6bbc55549ec7cab71b6eb4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 21:27:46 -0700 Subject: [PATCH 332/553] Fix grammar in set lesson --- ultimatepython/data_structures/set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index b6937904..7df337d0 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -7,13 +7,13 @@ def main(): simple_set.add(4) # Unlike a `list and `tuple`, it is not an ordered sequence as it - # prevent duplicates from being added + # does not allow duplicates to be added for _ in range(5): simple_set.add(0) simple_set.add(4) assert simple_set == {0, 1, 2, 3, 4} - # Now let's define two `set` collections + # Now let's define two new `set` collections multiples_two = set() multiples_four = set() From 30dbe1c946d2a0938d2cc43b8ac29ad33600c298 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 21:28:01 -0700 Subject: [PATCH 333/553] Add assertions in tuple lesson --- ultimatepython/data_structures/tuple.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 310ac1ea..5d96ebd9 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -6,6 +6,10 @@ def main(): assert immutable[0] == 1 assert immutable[-1] == 4 + # It can be sliced like a list + assert immutable[1:3] == (2, 3) + assert immutable[3:4] == (4,) + # It can be iterated over like a list for ix, number in enumerate(immutable): assert immutable[ix] == number From 655f1e07191623f9789c43623915490442ac4379 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 21:30:01 -0700 Subject: [PATCH 334/553] Fixup comments in abstract_class --- ultimatepython/classes/abstract_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 8f21745b..5f7a9979 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -89,7 +89,7 @@ def main(): assert all(isinstance(engineer, Employee) for engineer in engineers) assert all(not isinstance(engineer, Manager) for engineer in engineers) - # They can work, relax and refactor + # Engineers can work, relax and refactor assert engineer_john.do_work() == "John Doe is coding in Android" assert engineer_john.do_relax() == "John Doe is watching YouTube" assert engineer_john.do_refactor() == "John Doe is refactoring code" @@ -101,7 +101,7 @@ def main(): assert isinstance(manager_max, Employee) assert not isinstance(manager_max, Engineer) - # They can work, relax and hire + # Managers can work, relax and hire assert manager_max.do_work() == "Max Doe is meeting up with 2 reports" assert manager_max.do_relax() == "Max Doe is taking a trip to the Bahamas" assert manager_max.do_hire() == "Max Doe is hiring employees" From ebba408d7e945f8a699444d62da8357feb4475e5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 21:31:47 -0700 Subject: [PATCH 335/553] Optimize async lesson --- ultimatepython/advanced/async.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index 62bfdcc5..7847c548 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -62,9 +62,7 @@ async def schedule_jobs(): # We get the same amount of records as we have coroutines assert len(batch_records) == len(batch_jobs) - - for batch_record in batch_records: - assert _is_valid_record(batch_record) + assert all(_is_valid_record(record) for record in batch_records) def main(): From b194f705bcc97f71472b7b3b9ee3845a680faf11 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 8 Sep 2020 21:34:15 -0700 Subject: [PATCH 336/553] Rename bytes_obj to buffer in benchmark --- ultimatepython/advanced/benchmark.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index ad3bef0c..604f87a5 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -35,8 +35,8 @@ 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 - bytes_obj = io.StringIO() - ps = pstats.Stats(profile, stream=bytes_obj).sort_stats("cumulative") + buffer = io.StringIO() + ps = pstats.Stats(profile, stream=buffer).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` @@ -48,7 +48,7 @@ def main(): # classes and functions ps.print_stats() time_sleep_called = any("60" in line and "time.sleep" in line - for line in bytes_obj.getvalue().split("\n")) + for line in buffer.getvalue().split("\n")) assert time_sleep_called is True From 53ec3a5f119e229b959e0964c2c14dade3b1eb6e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 08:21:19 -0700 Subject: [PATCH 337/553] Add disclaimers to expression lesson --- ultimatepython/syntax/expression.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 4132fae8..3e0bdc84 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -11,11 +11,13 @@ def main(): assert x * 2 * 2 * 2 == 8 # Division is a bit tricky in Python because it returns a result - # of type 'float' by default + # of type 'float' in versions Python 3.x. For Python 2.x, this line + # will actually fail and return a 0 assert x / 2 == 0.5 # If an integer division is desired, then an extra slash must be - # added to the expression + # added to the expression. In Python 2.x and Python 3.x, the behavior + # is exactly the same assert x // 2 == 0 # Powers of an integer can be leveraged too. If more features are From 7f6e256cd856c0269d3d0d0ad962c1029bccfa20 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 08:38:49 -0700 Subject: [PATCH 338/553] Optimize wording in basic_class lesson --- 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 0eb64038..6c39f40f 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -4,7 +4,7 @@ class Car: """Basic definition of a car. - A car is a good entity for defining with a class because it has state + A car is a good entity to define a class with because it has state 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 a7afef4e4a34a521e4403d4e2dad3a8c01774a2c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 08:41:08 -0700 Subject: [PATCH 339/553] Flesh out basic_class lesson more --- ultimatepython/classes/basic_class.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 6c39f40f..78a2b0cd 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -5,9 +5,9 @@ class Car: """Basic definition of a car. A car is a good entity to define a class with because it has state - 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. + and methods associated with it. We begin with a simple mental model + of what a car is. That way, we can start talking about core concepts + that are associated with a class definition. """ def __init__(self, make, model, year, miles): From 5125185437e9a794153ad53bc281ed200ac4b129 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 08:43:23 -0700 Subject: [PATCH 340/553] Enhance drive docstring in basic_class lesson --- 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 78a2b0cd..9590bae2 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -26,7 +26,7 @@ def __str__(self): return f"{self.make} {self.model} ({self.year})" def drive(self, rate_in_mph): - """Drive car at a certain rate.""" + """Drive car at a certain rate in MPH.""" return f"{self} is driving at {rate_in_mph} MPH" From 821e510e9584425f0436c10b25df8f95e464719c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 08:56:10 -0700 Subject: [PATCH 341/553] Revise expression caveat again --- ultimatepython/syntax/expression.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 3e0bdc84..2be08e80 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -10,9 +10,9 @@ def main(): # of code into larger pieces of code over time assert x * 2 * 2 * 2 == 8 - # Division is a bit tricky in Python because it returns a result - # of type 'float' in versions Python 3.x. For Python 2.x, this line - # will actually fail and return a 0 + # Division is tricky because Python 3.x returns 0.5 of type `float` + # whereas Python 2.x returns 0 of type `int`. If this line fails, it + # is a sign that the wrong version of Python was used assert x / 2 == 0.5 # If an integer division is desired, then an extra slash must be From 4530074630f5bac8b0715bddae97ec14b75531a7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:10:04 -0700 Subject: [PATCH 342/553] Fix run_until with sum_until references --- ultimatepython/syntax/function.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index bec654e7..e868e3e2 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -13,9 +13,9 @@ def sum_until(fn, n): """Sum a function output from 0 until n - 1. This expects a function to be provided as its first input and an integer - as its second input. Like `add`, `run_until` returns a value. + as its second input. Like `add`, `sum_until` returns 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 `sum_until` highlights a core concept that was mentioned before: everything in Python is an object, and that includes this docstring! """ @@ -38,11 +38,11 @@ def main(): # `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` + # inline, unlike `add` and `sum_until` run_results = sum_until(lambda i: i * 100, 5) assert run_results == 1000, run_results - # We can see the `run_until` docstring by accessing the `__doc__` magic + # We can see the `sum_until` docstring by accessing the `__doc__` magic # attribute! Remember this - everything in Python is an object assert "includes this docstring!" in sum_until.__doc__ From 2f1b3de2021254edb6769207031cf242e5e754c2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:16:04 -0700 Subject: [PATCH 343/553] Add assertions on driving in basic_class --- ultimatepython/classes/basic_class.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 9590bae2..6fa7b6b6 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,4 +1,4 @@ -from inspect import signature +from inspect import isfunction, ismethod, signature class Car: @@ -59,6 +59,10 @@ def main(): # The variable method is bound to the instance assert driving.__self__ == car + # That is why `driving` is a method and NOT a function + assert not isfunction(driving) + assert ismethod(driving) + # And there is only one parameter for `driving` because `__self__` # binding is implicit driving_params = signature(driving).parameters From dbdbe5a5983fb3eb271bf9a8b04c7887af1c7fff Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:16:39 -0700 Subject: [PATCH 344/553] Change code to match wording 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 6fa7b6b6..6799e7b6 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -60,8 +60,8 @@ def main(): assert driving.__self__ == car # That is why `driving` is a method and NOT a function - assert not isfunction(driving) assert ismethod(driving) + assert not isfunction(driving) # And there is only one parameter for `driving` because `__self__` # binding is implicit From 372be07747b0bb9e50d0f590d41e2c092ac66eba Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:17:28 -0700 Subject: [PATCH 345/553] Compress conditions into one line --- ultimatepython/classes/basic_class.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 6799e7b6..b7b1d930 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -60,8 +60,7 @@ def main(): assert driving.__self__ == car # That is why `driving` is a method and NOT a function - assert ismethod(driving) - assert not isfunction(driving) + assert ismethod(driving) and not isfunction(driving) # And there is only one parameter for `driving` because `__self__` # binding is implicit From 91fde711a03c909f373fd169adaa175c171675cf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:19:18 -0700 Subject: [PATCH 346/553] Fixup basic_class lesson again --- ultimatepython/classes/basic_class.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index b7b1d930..a1955b78 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -44,10 +44,9 @@ 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 we 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) + assert issubclass(Car, object) and isinstance(Car, object) # To emphasize the idea that everything is an object, let's look at # the `drive` method in more detail From eeff9d3ab2d75ac02f13474c7e766c34c9ad1c02 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:21:36 -0700 Subject: [PATCH 347/553] Improve wording again 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 a1955b78..0c3ee214 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -58,7 +58,7 @@ def main(): # The variable method is bound to the instance assert driving.__self__ == car - # That is why `driving` is a method and NOT a function + # That is why `driving` is considered a method and not a function assert ismethod(driving) and not isfunction(driving) # And there is only one parameter for `driving` because `__self__` From 17e92991c35e67ae9dc9fe10af0a65ff104f2c1b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:34:03 -0700 Subject: [PATCH 348/553] Remove codesignal from practice resources --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index dcd1a48b..57db6c01 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,5 @@ Keep practicing so that your coding skills don't get rusty. - [leetcode.com](https://leetcode.com/) (:necktie:) - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) -- [codesignal.com](https://codesignal.com/) (:necktie:) - [exercism.io](https://exercism.io/) - [projecteuler.net](https://projecteuler.net/) From 3734c6fd0a452c59176a9ff7fee1c49e57c2a4a9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:44:19 -0700 Subject: [PATCH 349/553] Fixup comments in string lesson --- ultimatepython/data_structures/string.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index d77bd18d..70977f5e 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -36,16 +36,16 @@ def main(): assert lower_content in new_content assert new_content.endswith(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 + # Notice that `upper_content` and `lower_content` are smaller in length + # 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. + # 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 03abe2a6dfeb37c55a85923d44dd7758df2d2bff Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 09:50:45 -0700 Subject: [PATCH 350/553] Fixup decorator subtitle in ToC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57db6c01..da256b3e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ inspiring and highly encouraged if your goal is to become a true - Exception class: [Exception definition](ultimatepython/classes/exception_class.py) - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) 5. **Advanced** - - Decorator: [contextlib | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) + - Decorator: [Decorator definition | 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:) From a4785c37526a7cef8d1c5e6396afe2d35b67f135 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 16:44:13 -0700 Subject: [PATCH 351/553] Add assertions for while loop --- ultimatepython/syntax/loop.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 0dc6b9a7..4e7745b5 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -37,6 +37,8 @@ def main(): # counter. Like the previous `while` loop, this one continues until # the counter exceeds 8 i = 1 + break_hit = False + continue_hit = False while True: i *= 2 @@ -44,16 +46,22 @@ def main(): # 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_hit = True break if i == 2: # The `continue` statement returns to the start of the # current `while` loop + continue_hit = True continue # The `while` loop terminated at this value assert i == 8 + # The `while` loop hit the `break` and `continue` blocks + assert break_hit is True + assert continue_hit is True + if __name__ == "__main__": main() From cee0d705fee747f944466fedb8bef89525e4acfd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 16:54:39 -0700 Subject: [PATCH 352/553] Add assertions to decorator lesson --- ultimatepython/advanced/decorator.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index b0bcddd6..2355fbf1 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -1,5 +1,8 @@ from functools import wraps +# Module-level constants +_MASKING = "*" + def run_with_stringy(fn): """Run a string function with a string or a collection of strings. @@ -59,7 +62,16 @@ def hide_content(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 + return content[:start_point] + _MASKING * num_of_asterisks + + +def _is_hidden(obj): + """Check whether string or collection is hidden.""" + if isinstance(obj, str): + return _MASKING in obj + elif isinstance(obj, dict): + return all(_is_hidden(value) for value in obj.values()) + return all(_is_hidden(value) for value in obj) def main(): @@ -80,6 +92,8 @@ def main(): # See what changed between the insecure data and the secure data for insecure_item, secure_item in zip(insecure_data, secure_data): assert insecure_item != secure_item + assert not _is_hidden(insecure_item) + assert _is_hidden(secure_item) # Throw an error on a collection with non-string objects input_fails = False From a1bc613f59e13ebd2daabc67271c50563daab2ae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 16:58:44 -0700 Subject: [PATCH 353/553] Add extra assertion in meta_class --- ultimatepython/advanced/meta_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index c41ff339..bdf60db0 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -164,6 +164,7 @@ def main(): assert isinstance(BaseModel, object) assert isinstance(ModelMeta, object) assert isinstance(type, object) + assert isinstance(object, object) if __name__ == '__main__': From 1f8f61d4d524b83f6dd07078ada4e7a2d13cbf7d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 17:07:35 -0700 Subject: [PATCH 354/553] Add more assertions to meta_class --- ultimatepython/advanced/meta_class.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index bdf60db0..e72aae6e 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -132,10 +132,16 @@ class AddressModel(BaseModel): def main(): - # Real models are modified at runtime with `ModelMeta` + # Real models are given a name at runtime with `ModelMeta` assert UserModel.model_name == "user_rocks" assert AddressModel.model_name == "address" + # Real models are given fields at runtime with `ModelMeta` + assert "row_id" in UserModel.model_fields + assert "row_id" in AddressModel.model_fields + assert "username" in UserModel.model_fields + assert "address" in AddressModel.model_fields + # Real models are registered at runtime with `ModelMeta` assert UserModel.is_registered assert AddressModel.is_registered From 35b5927a56dc5dd2c54f447e4f32592cffa38403 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 21:49:46 -0700 Subject: [PATCH 355/553] Fixup iterator_class --- ultimatepython/classes/iterator_class.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 7a2d0ea5..f17df1d6 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -142,11 +142,13 @@ def main(): hacker = Employee("Unknown", "Hacker", []) hacker.direct_reports.append(hacker) - for iter_fn in (EmployeeIterator, employee_generator): + for obj in (EmployeeIterator, employee_generator): + call_fails = False try: - list(iter_fn(hacker)) - except IterationError as e: - assert str(e) == _ITERATION_MESSAGE + list(obj(hacker)) + except IterationError: + call_fails = True + assert call_fails is True if __name__ == "__main__": From 971492a790851ca081684c69e23d2caf567e6dfd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 22:07:40 -0700 Subject: [PATCH 356/553] Create new context_manager lesson --- README.md | 1 + ultimatepython/advanced/context_manager.py | 70 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 ultimatepython/advanced/context_manager.py diff --git a/README.md b/README.md index da256b3e..ff5a451f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ inspiring and highly encouraged if your goal is to become a true - 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:) + - Context managers: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py new file mode 100644 index 00000000..8441d327 --- /dev/null +++ b/ultimatepython/advanced/context_manager.py @@ -0,0 +1,70 @@ +from contextlib import contextmanager + +# Module-level constants +_FILESYSTEM = { + "a.txt": "Hello World", + "b.xml": "Hello World", + "c.out": "10101010" +} + + +class ContentBuffer: + """Content buffer.""" + + def __init__(self, content): + self.content = content + + def read(self): + return self.content + + def close(self): + self.content = None + + +@contextmanager +def file(filename): + """File context manager.""" + buffer = ContentBuffer(_FILESYSTEM[filename]) + try: + yield buffer + finally: + # Close the buffer unconditionally + buffer.close() + + +class FileHandler: + """File handler context manager.""" + + def __init__(self, filename): + self.buffer = ContentBuffer(_FILESYSTEM[filename]) + + def __enter__(self): + return self.buffer + + def __exit__(self, *args): + # Close the buffer unconditionally + self.buffer.close() + + +def main(): + # An example of a function-based context manager + with file("a.txt") as txt_buffer: + assert txt_buffer.read() == "Hello World" + + # An example of a class-based context manager + with FileHandler("b.xml") as xml_buffer: + assert xml_buffer.read() == "Hello World" + + # Examples of how a context manager might fail + for obj in (file, FileHandler): + call_fails = False + try: + with obj("c.out") as _: + raise RuntimeError("System crash. Abort!") + except RuntimeError: + call_fails = True + assert call_fails is True + + +if __name__ == '__main__': + main() From 22ff01963e23d0593379a98582c8750781c9b3b5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 22:09:37 -0700 Subject: [PATCH 357/553] Fixup section title in ToC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ff5a451f..53b9c219 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ inspiring and highly encouraged if your goal is to become a true - 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:) - - Context managers: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) + - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) ## Additional resources From 6e2c65850855cd8572029764d0e40130e8237113 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 22:15:36 -0700 Subject: [PATCH 358/553] Add more about context_manager --- ultimatepython/advanced/context_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index 8441d327..e20f59fc 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -55,10 +55,13 @@ def main(): with FileHandler("b.xml") as xml_buffer: assert xml_buffer.read() == "Hello World" - # Examples of how a context manager might fail + # Examples of context manager failures for obj in (file, FileHandler): call_fails = False try: + # Whenever any error happens in the context block, the buffer + # in the context manager gets closed automatically and the + # error gets raised to the outer block with obj("c.out") as _: raise RuntimeError("System crash. Abort!") except RuntimeError: @@ -66,5 +69,5 @@ def main(): assert call_fails is True -if __name__ == '__main__': +if __name__ == "__main__": main() From 753e3fd360297357eea34597902594de8a8baaae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 9 Sep 2020 22:20:25 -0700 Subject: [PATCH 359/553] Add extra comment and assert to loop --- ultimatepython/syntax/loop.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index 4e7745b5..fe24b916 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -39,6 +39,7 @@ def main(): i = 1 break_hit = False continue_hit = False + other_hit = False while True: i *= 2 @@ -55,12 +56,16 @@ def main(): continue_hit = True continue + # This statement runs when the counter equals 4 + other_hit = True + # The `while` loop terminated at this value assert i == 8 # The `while` loop hit the `break` and `continue` blocks assert break_hit is True assert continue_hit is True + assert other_hit is True if __name__ == "__main__": From 5f3d2b3088254381ecff7cd8f0e2320e14b3670b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 07:06:41 -0700 Subject: [PATCH 360/553] Replace custom buffer with StringIO --- ultimatepython/advanced/context_manager.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index e20f59fc..0043965b 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -1,4 +1,5 @@ from contextlib import contextmanager +from io import StringIO # Module-level constants _FILESYSTEM = { @@ -8,23 +9,10 @@ } -class ContentBuffer: - """Content buffer.""" - - def __init__(self, content): - self.content = content - - def read(self): - return self.content - - def close(self): - self.content = None - - @contextmanager def file(filename): """File context manager.""" - buffer = ContentBuffer(_FILESYSTEM[filename]) + buffer = StringIO(_FILESYSTEM[filename]) try: yield buffer finally: @@ -36,7 +24,7 @@ class FileHandler: """File handler context manager.""" def __init__(self, filename): - self.buffer = ContentBuffer(_FILESYSTEM[filename]) + self.buffer = StringIO(_FILESYSTEM[filename]) def __enter__(self): return self.buffer From fea80823e4ee9d1013587a278a403d133d706eab Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 07:11:24 -0700 Subject: [PATCH 361/553] Fixup context_manager comments --- ultimatepython/advanced/context_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index 0043965b..70ed6486 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -14,6 +14,7 @@ def file(filename): """File context manager.""" buffer = StringIO(_FILESYSTEM[filename]) try: + # Pass the buffer to the context block yield buffer finally: # Close the buffer unconditionally @@ -27,10 +28,11 @@ def __init__(self, filename): self.buffer = StringIO(_FILESYSTEM[filename]) def __enter__(self): + """Pass the buffer to the context block.""" return self.buffer def __exit__(self, *args): - # Close the buffer unconditionally + """Close the buffer unconditionally.""" self.buffer.close() From 417c1677df8422ed66872b80d8df7a5872e160ce Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 07:17:16 -0700 Subject: [PATCH 362/553] Replace single quotes in modules --- ultimatepython/advanced/meta_class.py | 2 +- ultimatepython/advanced/mro.py | 2 +- ultimatepython/advanced/weak_ref.py | 2 +- ultimatepython/classes/abstract_class.py | 2 +- ultimatepython/classes/exception_class.py | 2 +- ultimatepython/data_structures/string.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index e72aae6e..27deaad8 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -173,5 +173,5 @@ def main(): assert isinstance(object, object) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 791f5710..24efdad6 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -102,5 +102,5 @@ def main(): assert class_creation_fails is True -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index e746e8ef..2a046aee 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -88,5 +88,5 @@ def main(): assert registry.server_count == 0 -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 5f7a9979..5fe6caf7 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -107,5 +107,5 @@ def main(): assert manager_max.do_hire() == "Max Doe is hiring employees" -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index df9c2baa..55e317ea 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -66,5 +66,5 @@ def main(): assert result == 1 -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 70977f5e..1dc364f1 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -51,5 +51,5 @@ def main(): assert new_content == joined_content -if __name__ == '__main__': +if __name__ == "__main__": main() From da8609b35f3c12c4eec962b5d1c3ff20978d04ce Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 07:30:02 -0700 Subject: [PATCH 363/553] Add time complexity and revise ToC --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 53b9c219..9463e6b6 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ 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:, :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:) + - 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:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) @@ -64,6 +64,7 @@ inspiring and highly encouraged if your goal is to become a true - 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:) + - Time complexity: [cPython operations](https://wiki.python.org/moin/TimeComplexity) (:books:, :exploding_head:) 4. **Classes** - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) - Abstract class: [Abstract definition](ultimatepython/classes/abstract_class.py) @@ -72,7 +73,7 @@ inspiring and highly encouraged if your goal is to become a true 5. **Advanced** - Decorator: [Decorator definition | 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:) + - 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:) From cbcebe2ddcb839078ec9b6bca60527f6a17ea970 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:10:35 -0700 Subject: [PATCH 364/553] Create CONTRIBUTING.md --- CONTRIBUTING.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..73db078d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing + +Thanks for taking the time to understand how you can contribute to the +Ultimate Python Study guide! + +Please take a look at the [code of conduct](CODE_OF_CONDUCT.md) before +proceeding further. + +--- + +The repository consists of content and code. + +## README contributions + +The `README.md` is important because it is the most frequently viewed page +in this repository. Any changes here must be of high value. Specifically: + +- Python modules must be referenced in the ToC +- Links must point to HTTPS resources that return a `2xx` status +- Links to GitHub repositories should have at least 1k stars +- Links to Python documentation must be valuable to newcomers and professionals + +## Standalone modules + +Standalone modules act as lessons or notes of how to leverage core Python. + +### Standard block + +Every standalone Python module consists of the following: + +```python +# Main function +def main(): + # Assertion comments + assert 1 + 1 == 2 + assert True is not False + + +# Main function conditional +if __name__ == '__main__': + main() +``` + +### Code coverage + +Each module should aim for 80-100% code coverage with the test runner, which +is [runner.py](runner.py). The reason for this high standard is that the +repository code is relatively simple to test since all of the learning should +happen in the `main` function. Furthermore, having high code coverage means +that the code proactively lets developers know when they made a mistake. This +is valuable feedback, and helps the developer to improve quickly. + +To validate code coverage, run the following commands: + +```bash +$ coverage run -m runner +$ coverage html +$ open htmlcov/index.html +``` From 8f3beb90199a34d8ba8ecef8d8ad91c58f346334 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:17:22 -0700 Subject: [PATCH 365/553] Enhance content on Python modules --- CONTRIBUTING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73db078d..b93986b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ proceeding further. The repository consists of content and code. -## README contributions +## README documentation The `README.md` is important because it is the most frequently viewed page in this repository. Any changes here must be of high value. Specifically: @@ -20,9 +20,12 @@ in this repository. Any changes here must be of high value. Specifically: - Links to GitHub repositories should have at least 1k stars - Links to Python documentation must be valuable to newcomers and professionals -## Standalone modules +## Python modules -Standalone modules act as lessons or notes of how to leverage core Python. +Standalone modules are lessons which act as building blocks that help +newcomers and professionals develop an intuition for core Python. The +modules tend to avoid physical I/O operations as they may be run in a +constrained / restricted resource such as a browser. ### Standard block From 581b9ea24c74d3e114e56458febb348dffcf0cb2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:20:10 -0700 Subject: [PATCH 366/553] Fixup first line of section --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b93986b1..e439e031 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ proceeding further. --- -The repository consists of content and code. +The repository consists of documentation and modules. ## README documentation From 875d3fe69ce3ae92a0d5ce5965174ed0480e21b6 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:22:44 -0700 Subject: [PATCH 367/553] Refine README section --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e439e031..1e302bc9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,8 @@ The repository consists of documentation and modules. ## README documentation The `README.md` is important because it is the most frequently viewed page -in this repository. Any changes here must be of high value. Specifically: +in this repository. As such, any changes that are made to this page must be +legitimate and meaningful. Specifically: - Python modules must be referenced in the ToC - Links must point to HTTPS resources that return a `2xx` status From 9dc9cdb75419f075142fae14be928e6423771ba2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:25:27 -0700 Subject: [PATCH 368/553] Add clarifying notes on Python modules --- CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e302bc9..07bd72e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,10 +23,9 @@ legitimate and meaningful. Specifically: ## Python modules -Standalone modules are lessons which act as building blocks that help -newcomers and professionals develop an intuition for core Python. The -modules tend to avoid physical I/O operations as they may be run in a -constrained / restricted resource such as a browser. +Every Python modules is a lesson which helps developers build their own +intuition for core Python. The modules tend to avoid physical I/O operations +as they may be run in a constrained or restricted resource like a web browser. ### Standard block From 3214f0c4cdde8a50fd243e00368cf81881539b6f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:29:59 -0700 Subject: [PATCH 369/553] Add more on code coverage --- CONTRIBUTING.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07bd72e3..c9422812 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,10 +48,12 @@ if __name__ == '__main__': Each module should aim for 80-100% code coverage with the test runner, which is [runner.py](runner.py). The reason for this high standard is that the -repository code is relatively simple to test since all of the learning should -happen in the `main` function. Furthermore, having high code coverage means -that the code proactively lets developers know when they made a mistake. This -is valuable feedback, and helps the developer to improve quickly. +repository code is relatively simple. All of the interactive learning tends +to revolve around the `main` function since that is where the assertions are. + +Having high standards for code coverage also means that each module lets +developers know when they made a mistake. This is valuable feedback, and +helps them improve quickly. To validate code coverage, run the following commands: From 89dad7fc4f012c4f6fa16ccb0a736fdc4f151ffc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:34:33 -0700 Subject: [PATCH 370/553] Revise code coverage section --- CONTRIBUTING.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9422812..007a9a69 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,13 +46,11 @@ if __name__ == '__main__': ### Code coverage -Each module should aim for 80-100% code coverage with the test runner, which -is [runner.py](runner.py). The reason for this high standard is that the -repository code is relatively simple. All of the interactive learning tends -to revolve around the `main` function since that is where the assertions are. - -Having high standards for code coverage also means that each module lets -developers know when they made a mistake. This is valuable feedback, and +Each module should aim for 80-100% code coverage with the [test runner](runner.py). +The reason for this high standard is that the repository code is relatively +simple. All interactive learning tends to revolve around the `main` function +since that is where the assertions are. That way, developers know immediately +when they made a mistake in the module. This is valuable feedback because it helps them improve quickly. To validate code coverage, run the following commands: From 4f3a046bafe5b15e7ae84eb7b847773f89c22ada Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:37:31 -0700 Subject: [PATCH 371/553] Hyperlink the README in contrib docs --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 007a9a69..f2913b0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,9 +12,9 @@ The repository consists of documentation and modules. ## README documentation -The `README.md` is important because it is the most frequently viewed page -in this repository. As such, any changes that are made to this page must be -legitimate and meaningful. Specifically: +The [README](README.md) is important because it is the most frequently viewed +page in this repository. As such, any changes that are made to this page +must be legitimate and meaningful. Specifically: - Python modules must be referenced in the ToC - Links must point to HTTPS resources that return a `2xx` status From 7bf305e429f6dd15a3e0c55b511b84bb5e9c080c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:42:56 -0700 Subject: [PATCH 372/553] Add more substance to the first sentence --- CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2913b0e..aab4e299 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,10 @@ proceeding further. --- -The repository consists of documentation and modules. +The repository consists of documentation and modules. If you intend to +contribute by creating or updating resources of either type, please review +the standards defined in the upcoming sections to learn how you can make a +big impact for the developers consuming this content. ## README documentation From a081a5bfcec358fe64c5222d4915278b272c949b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:44:24 -0700 Subject: [PATCH 373/553] Fix grammar in contrib docs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aab4e299..2d95766e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ must be legitimate and meaningful. Specifically: ## Python modules -Every Python modules is a lesson which helps developers build their own +Every Python module is a lesson which helps developers build their own intuition for core Python. The modules tend to avoid physical I/O operations as they may be run in a constrained or restricted resource like a web browser. From a8df47ae11e5687afd0b2b3b96fcbe046cf9ba93 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 08:47:10 -0700 Subject: [PATCH 374/553] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 92a89847c0a453a15b65d4454cc6929e46d5136f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 12:54:35 -0700 Subject: [PATCH 375/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d95766e..a99c87aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ must be legitimate and meaningful. Specifically: Every Python module is a lesson which helps developers build their own intuition for core Python. The modules tend to avoid physical I/O operations -as they may be run in a constrained or restricted resource like a web browser. +as they may be run in a constrained environment like a web browser. ### Standard block From be4267fb85bb3e0715271562baa68633fc89cb8f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 14:24:58 -0700 Subject: [PATCH 376/553] Add another set assertion --- 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 7df337d0..06d714a1 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -4,7 +4,8 @@ def main(): # A set is dynamic like a `list` and `tuple` simple_set.add(3) - simple_set.add(4) + simple_set.remove(0) + assert simple_set == {1, 2, 3} # Unlike a `list and `tuple`, it is not an ordered sequence as it # does not allow duplicates to be added From 201a7cc5f476bfdaced553d3851aa466bc553aef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 14:31:04 -0700 Subject: [PATCH 377/553] Optimize assert in comprehension lesson --- ultimatepython/data_structures/comprehension.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index fd38ecbf..e0b4aee8 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -5,8 +5,7 @@ def main(): # 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)` - list_comp = [0 for _ in range(5)] - assert list_comp == [0] * 5 + assert [0 for _ in range(5)] == [0] * 5 == [0, 0, 0, 0, 0] # For the next comprehension operations, let's see what we can do # with a list of 3-5 letter words From e1502eaf6129a09a587ddf982492246375f7551e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 17:26:49 -0700 Subject: [PATCH 378/553] Add pull request template --- .github/pull_request_template.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..a83351b4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +--- +name: Pull request +about: Suggest a change for this project +title: '' +labels: '' +assignees: '' + +--- + +**Describe the change** +A clear and concise description of what the change is doing. + +**Additional context** +Add any other context or screenshots about the feature request here. From 42ce0dd09a510ba869b99ad5ab469c1cb7843037 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 17:30:47 -0700 Subject: [PATCH 379/553] Shorten first line of coverage section --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a99c87aa..588f3608 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ if __name__ == '__main__': ### Code coverage -Each module should aim for 80-100% code coverage with the [test runner](runner.py). +Each module should have 80-100% code coverage with the [test runner](runner.py). The reason for this high standard is that the repository code is relatively simple. All interactive learning tends to revolve around the `main` function since that is where the assertions are. That way, developers know immediately From cd795699a6079a035ef62458c77c281b6445dbc9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 21:23:45 -0700 Subject: [PATCH 380/553] Add ML and DS resources in README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9463e6b6..4ece7ea3 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,9 @@ Keep learning by reading from other well-regarded resources. - [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:) +- [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) (:test_tube:) +- [josephmisiti/awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning) (:brain:) +- [ZuzooVn/machine-learning-for-software-engineers](https://github.com/ZuzooVn/machine-learning-for-software-engineers) (:brain:) ### Interactive practice @@ -102,5 +105,6 @@ Keep practicing so that your coding skills don't get rusty. - [leetcode.com](https://leetcode.com/) (:necktie:) - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) +- [kaggle.com](https://www.kaggle.com/) (:necktie:, :brain:) - [exercism.io](https://exercism.io/) - [projecteuler.net](https://projecteuler.net/) From a75c02072c94e4843167adc65d98f65cc07c3929 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 22:22:22 -0700 Subject: [PATCH 381/553] Reorganize README resources --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4ece7ea3..031c5396 100644 --- a/README.md +++ b/README.md @@ -92,12 +92,13 @@ 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:) - [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) +- [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) (: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:) -- [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) (:test_tube:) -- [josephmisiti/awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning) (:brain:) -- [ZuzooVn/machine-learning-for-software-engineers](https://github.com/ZuzooVn/machine-learning-for-software-engineers) (:brain:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) +- [academic/awesome-datascience](https://github.com/academic/awesome-datascience) +- [josephmisiti/awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning) +- [ZuzooVn/machine-learning-for-software-engineers](https://github.com/ZuzooVn/machine-learning-for-software-engineers) ### Interactive practice @@ -105,6 +106,6 @@ Keep practicing so that your coding skills don't get rusty. - [leetcode.com](https://leetcode.com/) (:necktie:) - [hackerrank.com](https://www.hackerrank.com/) (:necktie:) -- [kaggle.com](https://www.kaggle.com/) (:necktie:, :brain:) +- [kaggle.com](https://www.kaggle.com/) (:brain:) - [exercism.io](https://exercism.io/) - [projecteuler.net](https://projecteuler.net/) From 5a0be6d9e242bb212bf84c3f08798bca1c38eb8e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 22:32:27 -0700 Subject: [PATCH 382/553] Add extra paragraph to contrib docs --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 588f3608..d8ad7ede 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,10 @@ if __name__ == '__main__': main() ``` +If the module involves additional functions and classes, they should be +placed above the `main` function. Also module-level constants will be +defined with the `_UNDER_SCORE_FIRST` convention. + ### Code coverage Each module should have 80-100% code coverage with the [test runner](runner.py). From 01110b8adc7e3808d5e766d95c93c7bc1af37667 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 10 Sep 2020 22:36:21 -0700 Subject: [PATCH 383/553] Fixup added content to contrib docs --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8ad7ede..3d0fca2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,8 +47,8 @@ if __name__ == '__main__': main() ``` -If the module involves additional functions and classes, they should be -placed above the `main` function. Also module-level constants will be +If the module involves additional functions and classes, they are placed +above the `main` function. Additionally, module-level constants are defined with the `_UNDER_SCORE_FIRST` convention. ### Code coverage From 09d05fb26529b48ad7762e70ff10ef50ba17daef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:29:36 -0700 Subject: [PATCH 384/553] Add assertions to list lesson --- 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 e847b6e3..90e3b257 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -36,10 +36,16 @@ def main(): ix_back = len(numbers) - ix_front - 1 numbers[ix_front], numbers[ix_back] = numbers[ix_back], numbers[ix_front] + # Let's check that `numbers` is in reverse order + assert numbers == [5, 4, 3, 2, 1] + # Suppose that we want to go back to the original order, we can use the # builtin `reverse` method in lists numbers.reverse() + # Let's check that `numbers` is in original order + assert 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. To see the indices and values From 8b3ea5b34d0529d59a85d2eecec6fdeb641287a1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:30:49 -0700 Subject: [PATCH 385/553] Fix typo 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 90e3b257..6ede7b39 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -63,7 +63,7 @@ def main(): assert matrix[1][0] == 4 assert matrix[0][1] == 2 - # This matrix just so happens to be a square so the the length of each + # This matrix just so happens to be a square so that 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) From e60e8d5c9f127baf8667383815e8eaa527ec71c0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:31:55 -0700 Subject: [PATCH 386/553] Replace fewer with less 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 6ede7b39..2fe51e91 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -69,7 +69,7 @@ def main(): assert len(matrix) == len(row) # Notice that lists have variable length and can be modified to have - # more elements. Lists can also be modified to have fewer elements + # more elements. Lists can also be modified to have less elements lengthy = [] for i in range(5): lengthy.append(i) # add 0..4 to the back From 5ff42b8dadc817bae0adc77a778080f97eb610cf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:48:26 -0700 Subject: [PATCH 387/553] Refine pull request template --- .github/pull_request_template.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a83351b4..ff9a94a3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,7 @@ ---- -name: Pull request -about: Suggest a change for this project -title: '' -labels: '' -assignees: '' +Please read these before submitting a pull request: + +- [Contributing guidelines](../CONTRIBUTING.md) +- [Code of conduct](../CODE_OF_CONDUCT.md) --- From cc1eb168ad2d05296225f204a67b44e6017b6026 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:51:42 -0700 Subject: [PATCH 388/553] Simplify pull request template --- .github/pull_request_template.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ff9a94a3..16a75877 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,4 @@ -Please read these before submitting a pull request: - -- [Contributing guidelines](../CONTRIBUTING.md) -- [Code of conduct](../CODE_OF_CONDUCT.md) +Please read the [contributing guidelines](https://github.com/huangsam/ultimate-python/blob/master/CONTRIBUTING.md) before submitting a pull request. --- From 0cf938d335fddfc7107fb90b4d1c115910848e88 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 06:53:13 -0700 Subject: [PATCH 389/553] Shorten help text for first input --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 16a75877..82bace16 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,7 +3,7 @@ Please read the [contributing guidelines](https://github.com/huangsam/ultimate-p --- **Describe the change** -A clear and concise description of what the change is doing. +A clear and concise description of what the change is. **Additional context** Add any other context or screenshots about the feature request here. From f32de1bbda89bd4cc310fd2a77510aebffeb9ea2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 07:02:21 -0700 Subject: [PATCH 390/553] Replace fails with failed (#7) --- ultimatepython/advanced/context_manager.py | 6 +++--- ultimatepython/advanced/decorator.py | 6 +++--- ultimatepython/advanced/mro.py | 6 +++--- ultimatepython/classes/iterator_class.py | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index 70ed6486..db123a28 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -47,7 +47,7 @@ def main(): # Examples of context manager failures for obj in (file, FileHandler): - call_fails = False + call_failed = False try: # Whenever any error happens in the context block, the buffer # in the context manager gets closed automatically and the @@ -55,8 +55,8 @@ def main(): with obj("c.out") as _: raise RuntimeError("System crash. Abort!") except RuntimeError: - call_fails = True - assert call_fails is True + call_failed = True + assert call_failed is True if __name__ == "__main__": diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index 2355fbf1..fd3340ed 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -96,12 +96,12 @@ def main(): assert _is_hidden(secure_item) # Throw an error on a collection with non-string objects - input_fails = False + input_failed = False try: hide_content([1]) except ValueError: - input_fails = True - assert input_fails is True + input_failed = True + assert input_failed is True if __name__ == "__main__": diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 24efdad6..809a5f9f 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -90,7 +90,7 @@ def main(): # Show `IndecisivePlayer` method resolution in action assert IndecisivePlayer().ping_pong() == ["ping", "ping", "pONg", "pong"] - class_creation_fails = False + class_creation_failed = False try: # Creating a new class `ConfusedPlayer` and `IndecisivePlayer` # results in a `TypeError` because both classes have mismatched @@ -98,8 +98,8 @@ def main(): # one class. Hence `MissingPlayer` will not be created type("MissingPlayer", (ConfusedPlayer, IndecisivePlayer), {}) except TypeError: - class_creation_fails = True - assert class_creation_fails is True + class_creation_failed = True + assert class_creation_failed is True if __name__ == "__main__": diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index f17df1d6..8af055b7 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -143,12 +143,12 @@ def main(): hacker.direct_reports.append(hacker) for obj in (EmployeeIterator, employee_generator): - call_fails = False + call_failed = False try: list(obj(hacker)) except IterationError: - call_fails = True - assert call_fails is True + call_failed = True + assert call_failed is True if __name__ == "__main__": From ae3d168641dbafd9c117903060dd050dc645b943 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 07:09:07 -0700 Subject: [PATCH 391/553] Fix __main__ quoting in contrib docs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d0fca2a..d03b0b75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ def main(): # Main function conditional -if __name__ == '__main__': +if __name__ == "__main__": main() ``` From 0531163f15eecc4244c968e460aa286790fae5dc Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 07:11:11 -0700 Subject: [PATCH 392/553] Fix wording in code coverage section --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d03b0b75..aa578852 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,8 +56,8 @@ defined with the `_UNDER_SCORE_FIRST` convention. Each module should have 80-100% code coverage with the [test runner](runner.py). The reason for this high standard is that the repository code is relatively simple. All interactive learning tends to revolve around the `main` function -since that is where the assertions are. That way, developers know immediately -when they made a mistake in the module. This is valuable feedback because it +since that is where the assertions are. That way, developers immediately know +when they make a mistake in the module. This is valuable feedback because it helps them improve quickly. To validate code coverage, run the following commands: From 3951e19e5cdaf6340b979dbd1cf58295a42fdbb9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 08:35:49 -0700 Subject: [PATCH 393/553] Update pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 82bace16..d7054f30 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,4 +6,4 @@ Please read the [contributing guidelines](https://github.com/huangsam/ultimate-p A clear and concise description of what the change is. **Additional context** -Add any other context or screenshots about the feature request here. +Add any other context or screenshots about the pull request here. From b102a8b6764d6f5956015988401c46156dff9aa8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 08:38:38 -0700 Subject: [PATCH 394/553] Update conditional.py --- ultimatepython/syntax/conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index 7c0dc031..a6112962 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -23,7 +23,7 @@ def main(): ran_3 = True # run assert ran_3 is True - # The `else` statement also run once all other `if` and `elif` conditions + # The `else` statement also runs 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 From 3f219d428921407533930ce698369ea4b1380589 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 08:41:33 -0700 Subject: [PATCH 395/553] Update 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 2fe51e91..dfd84470 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -50,7 +50,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. 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 + # list of values into an iterator of index-value pairs for index, (letter, number) in enumerate(zip(letters, numbers)): assert letters[index] == letter assert numbers[index] == number From 063dbd58d1321c2f2be45691508efa68990a9fa6 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 08:46:37 -0700 Subject: [PATCH 396/553] Update meta_class.py --- ultimatepython/advanced/meta_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 27deaad8..1a687066 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -30,7 +30,7 @@ def __new__(mcs, name, bases, attrs): 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. + # We will leverage this fact later on this routine if attrs.get("__abstract__") is True: kls.model_name = None else: @@ -57,7 +57,7 @@ def __new__(mcs, name, bases, attrs): # 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). + # for the database of choice (i.e. PostgreSQL, MySQL) if kls.model_name: kls.model_table = ModelTable(kls.model_name, kls.model_fields) ModelMeta.tables[kls.model_name] = kls.model_table From 7297617f50cc3e3d5c7443510febb14575b666b1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 09:38:14 -0700 Subject: [PATCH 397/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa578852..7c33b633 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,10 +8,10 @@ proceeding further. --- -The repository consists of documentation and modules. If you intend to -contribute by creating or updating resources of either type, please review -the standards defined in the upcoming sections to learn how you can make a -big impact for the developers consuming this content. +The repository consists of documentation and Python modules. If you intend +to contribute by creating or updating resources of either type, please +review the standards defined in the upcoming sections to learn how you +can make a big impact for the developers consuming this content. ## README documentation @@ -20,9 +20,10 @@ page in this repository. As such, any changes that are made to this page must be legitimate and meaningful. Specifically: - Python modules must be referenced in the ToC -- Links must point to HTTPS resources that return a `2xx` status -- Links to GitHub repositories should have at least 1k stars -- Links to Python documentation must be valuable to newcomers and professionals +- All links must point to HTTPS resources that return a `2xx` status +- External Python documentation must be useful for newcomers and professionals +- External GitHub repositories should have at least 1k stars +- External practice resources must have an option for Python ## Python modules From 1ef99cd119285cca356e0ed23dccfe99ab97d0c8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 09:38:53 -0700 Subject: [PATCH 398/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c33b633..ea540f12 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ must be legitimate and meaningful. Specifically: - All links must point to HTTPS resources that return a `2xx` status - External Python documentation must be useful for newcomers and professionals - External GitHub repositories should have at least 1k stars -- External practice resources must have an option for Python +- External practice resources must have Python exercises ## Python modules From 1306c35e095b1eb6d10205e6970bd0ae4cb15ec9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 09:41:25 -0700 Subject: [PATCH 399/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea540f12..cd24432c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ The [README](README.md) is important because it is the most frequently viewed page in this repository. As such, any changes that are made to this page must be legitimate and meaningful. Specifically: -- Python modules must be referenced in the ToC +- All Python modules must be referenced in the ToC - All links must point to HTTPS resources that return a `2xx` status - External Python documentation must be useful for newcomers and professionals - External GitHub repositories should have at least 1k stars From 7dd6e9c63865cbbd5cf4040937c1f213b0485c0f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 11 Sep 2020 10:01:17 -0700 Subject: [PATCH 400/553] Add assertions in list lesson --- ultimatepython/data_structures/list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index dfd84470..9483df33 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -60,8 +60,10 @@ def main(): # Lists can be nested at arbitrary levels matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - assert matrix[1][0] == 4 + assert matrix[0][0] == 1 assert matrix[0][1] == 2 + assert matrix[1][0] == 4 + assert matrix[1][1] == 5 # This matrix just so happens to be a square so that the length of each # row is the same as the number of rows in the matrix From d146e88bccc3140bae27ad8ac6788a373d645d2b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 12:01:52 -0700 Subject: [PATCH 401/553] Add extra docs to context_manager lesson --- ultimatepython/advanced/context_manager.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index db123a28..ebf310b6 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -11,7 +11,12 @@ @contextmanager def file(filename): - """File context manager.""" + """File context manager. + + This is the function variant of the context manager. Context managers + are useful for resources that need to be opened and closed such as + files, database connections and sockets. + """ buffer = StringIO(_FILESYSTEM[filename]) try: # Pass the buffer to the context block @@ -22,7 +27,12 @@ def file(filename): class FileHandler: - """File handler context manager.""" + """File handler context manager. + + This is the class variant of the context manager. Just like the iterator + lesson, it depends on context and preference that you choose one style + over the other. + """ def __init__(self, filename): self.buffer = StringIO(_FILESYSTEM[filename]) From 6b640bc5ca9f884239506f2ea22dab7ac28dfda7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 12:06:29 -0700 Subject: [PATCH 402/553] Refine docs 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 604f87a5..9cfe3018 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -8,13 +8,13 @@ def finish_slower(): - """Finish slower.""" + """Finish slower by sleeping more.""" for _ in range(20): time.sleep(_SLEEP_DURATION) def finish_faster(): - """Finish faster.""" + """Finish faster by sleeping less.""" for _ in range(10): time.sleep(_SLEEP_DURATION) From 215f5a8dfc457a1eaa94f95618fd13cf76f69030 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 14:43:48 -0700 Subject: [PATCH 403/553] Fix grammar in expression lesson --- ultimatepython/syntax/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 2be08e80..45f80c11 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -2,7 +2,7 @@ def main(): # This is a simple integer x = 1 - # Its value can used as part of expressions + # Its value can be used as part of expressions assert x + 1 == 2 # An expression can be chained indefinitely. This concept of chaining From 15aa972ee9f2ba1ac267579398f153c3b62e9ff7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 15:56:05 -0700 Subject: [PATCH 404/553] Create new mocking lesson --- README.md | 1 + ultimatepython/advanced/mocking.py | 78 ++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 ultimatepython/advanced/mocking.py diff --git a/README.md b/README.md index 031c5396..5c6ab8a2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ inspiring and highly encouraged if your goal is to become a true - Weak reference: [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) - Benchmark: [cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) + - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py new file mode 100644 index 00000000..e2401d74 --- /dev/null +++ b/ultimatepython/advanced/mocking.py @@ -0,0 +1,78 @@ +import random +from unittest.mock import MagicMock, PropertyMock, patch + +# Module-level constants +_PROTOCOL_HTTP = "http" +_PROTOCOL_HTTPS = "https" +_FAKE_BASE_URL = f"{_PROTOCOL_HTTPS}://www.google.com:443" +_FAKE_PID = 127 + + +class AppServer: + """Application server. + + Normally this isn't mocked because it is the runtime environment for + business logic, database endpoints, network sockets and more. But + this server definition is rather lightweight, so it's acceptable + in this case. + """ + + def __init__(self, host, port, proto): + self._host = host + self._port = port + self._proto = proto + self._pid = -1 + + @property + def base_url(self): + return f"{self._proto}://{self._host}:{self._port}" + + def get_pid(self): + return self._pid + + def start_server(self): + self._pid = random.randint(0, 255) + return f"Started server: {self.base_url}" + + +class FakeServer(AppServer): + """Subclass parent and fake some routines.""" + + @property + def base_url(self): + return _FAKE_BASE_URL + + def get_pid(self): + return _FAKE_PID + + +def main(): + # This is the original class and it works as expected + app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) + assert app_server.base_url == "http://localhost:8000" + assert app_server.start_server() == "Started server: http://localhost:8000" + assert app_server.get_pid() >= 0 + + # Approach 1: Use a `MagicMock` for surface-level checks + mock_kls = MagicMock() + mock_server = mock_kls("localhost", 8000, _PROTOCOL_HTTP) + assert isinstance(mock_kls, MagicMock) + assert isinstance(mock_server, MagicMock) + assert isinstance(mock_server.start_server(), MagicMock) + mock_server.start_server.assert_called() + + # Approach 2: Patch a method in the original class + with patch.object(AppServer, "base_url", PropertyMock(return_value=_FAKE_BASE_URL)): + patch_server = AppServer("localhost", 8080, _PROTOCOL_HTTP) + assert isinstance(patch_server, AppServer) + assert patch_server.base_url == _FAKE_BASE_URL + + # Approach 3: Create a new class that inherits the original class + fake_server = FakeServer("localhost", 8080, _PROTOCOL_HTTP) + assert isinstance(fake_server, AppServer) + assert fake_server.base_url == _FAKE_BASE_URL + assert fake_server.get_pid() == _FAKE_PID + + +if __name__ == "__main__": + main() From fc9ac7b52d382678015237b83d15d2fc37147e5f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 16:04:22 -0700 Subject: [PATCH 405/553] Fixup assertions in mocking --- ultimatepython/advanced/mocking.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index e2401d74..2c12029f 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -27,7 +27,11 @@ def __init__(self, host, port, proto): def base_url(self): return f"{self._proto}://{self._host}:{self._port}" - def get_pid(self): + @property + def started(self): + return self._get_pid() > -1 + + def _get_pid(self): return self._pid def start_server(self): @@ -40,9 +44,11 @@ class FakeServer(AppServer): @property def base_url(self): + """Mock output of public URL.""" return _FAKE_BASE_URL - def get_pid(self): + def _get_pid(self): + """Mock output of private PID.""" return _FAKE_PID @@ -51,27 +57,27 @@ def main(): app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) assert app_server.base_url == "http://localhost:8000" assert app_server.start_server() == "Started server: http://localhost:8000" - assert app_server.get_pid() >= 0 + assert app_server.started is True - # Approach 1: Use a `MagicMock` for surface-level checks - mock_kls = MagicMock() - mock_server = mock_kls("localhost", 8000, _PROTOCOL_HTTP) - assert isinstance(mock_kls, MagicMock) + # Approach 1: Use a `MagicMock` in place of the real thing + mock_server = MagicMock() assert isinstance(mock_server, MagicMock) assert isinstance(mock_server.start_server(), MagicMock) mock_server.start_server.assert_called() + mock_server.base_url.assert_not_called() # Approach 2: Patch a method in the original class with patch.object(AppServer, "base_url", PropertyMock(return_value=_FAKE_BASE_URL)): patch_server = AppServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(patch_server, AppServer) assert patch_server.base_url == _FAKE_BASE_URL + assert patch_server.started is False # Approach 3: Create a new class that inherits the original class fake_server = FakeServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(fake_server, AppServer) assert fake_server.base_url == _FAKE_BASE_URL - assert fake_server.get_pid() == _FAKE_PID + assert fake_server.started is True if __name__ == "__main__": From ab8dc3bcfb58454bb9ef7aeadad2338472c54aef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 16:11:05 -0700 Subject: [PATCH 406/553] Add extra comments in mocking lesson --- ultimatepython/advanced/mocking.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 2c12029f..00eba22d 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -59,6 +59,11 @@ def main(): assert app_server.start_server() == "Started server: http://localhost:8000" assert app_server.started is True + # But sometimes you cannot test the finer details of a class because + # its methods depend on the availability of external resources. This + # is where mocking comes to the rescue. There are three main + # approaches that developers use when it comes to mocking a class + # Approach 1: Use a `MagicMock` in place of the real thing mock_server = MagicMock() assert isinstance(mock_server, MagicMock) From d4b2eace6b0d4329e2bfab384ddbeac9680b2539 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 16:20:53 -0700 Subject: [PATCH 407/553] Fix security concern from SonarCloud --- ultimatepython/advanced/mocking.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 00eba22d..654cd646 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -1,7 +1,8 @@ -import random +from collections import Counter from unittest.mock import MagicMock, PropertyMock, patch # Module-level constants +_COUNTER = Counter(pid=1) _PROTOCOL_HTTP = "http" _PROTOCOL_HTTPS = "https" _FAKE_BASE_URL = f"{_PROTOCOL_HTTPS}://www.google.com:443" @@ -29,13 +30,14 @@ def base_url(self): @property def started(self): - return self._get_pid() > -1 + return self._get_pid() > 0 def _get_pid(self): return self._pid def start_server(self): - self._pid = random.randint(0, 255) + self._pid = _COUNTER["pid"] + _COUNTER["pid"] += 1 return f"Started server: {self.base_url}" From d1feb8c5ac38f4ca29cc48986548d65e5e7526d9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 16:24:18 -0700 Subject: [PATCH 408/553] Fixup mocking lesson a bit more --- ultimatepython/advanced/mocking.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 654cd646..6661b0eb 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -63,10 +63,10 @@ def main(): # But sometimes you cannot test the finer details of a class because # its methods depend on the availability of external resources. This - # is where mocking comes to the rescue. There are three main - # approaches that developers use when it comes to mocking a class + # is where mocking comes to the rescue. There are a couple approaches + # that developers use when it comes to mocking - # Approach 1: Use a `MagicMock` in place of the real thing + # Approach 1: Use a `MagicMock` in place of a real class instance mock_server = MagicMock() assert isinstance(mock_server, MagicMock) assert isinstance(mock_server.start_server(), MagicMock) From 3ef312c273bf621298144577b40498ce0708adb9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 16:36:08 -0700 Subject: [PATCH 409/553] Rename property methods in app server --- ultimatepython/advanced/mocking.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 6661b0eb..82b962e6 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -25,39 +25,41 @@ def __init__(self, host, port, proto): self._pid = -1 @property - def base_url(self): + def endpoint(self): return f"{self._proto}://{self._host}:{self._port}" @property - def started(self): - return self._get_pid() > 0 - - def _get_pid(self): + def pid(self): return self._pid + @property + def started(self): + return self.pid > 0 + def start_server(self): self._pid = _COUNTER["pid"] _COUNTER["pid"] += 1 - return f"Started server: {self.base_url}" + return f"Started server: {self.endpoint}" class FakeServer(AppServer): """Subclass parent and fake some routines.""" @property - def base_url(self): + def endpoint(self): """Mock output of public URL.""" return _FAKE_BASE_URL - def _get_pid(self): - """Mock output of private PID.""" + @property + def pid(self): + """Mock output of public PID.""" return _FAKE_PID def main(): # This is the original class and it works as expected app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) - assert app_server.base_url == "http://localhost:8000" + assert app_server.endpoint == "http://localhost:8000" assert app_server.start_server() == "Started server: http://localhost:8000" assert app_server.started is True @@ -71,19 +73,19 @@ def main(): assert isinstance(mock_server, MagicMock) assert isinstance(mock_server.start_server(), MagicMock) mock_server.start_server.assert_called() - mock_server.base_url.assert_not_called() + mock_server.endpoint.assert_not_called() # Approach 2: Patch a method in the original class - with patch.object(AppServer, "base_url", PropertyMock(return_value=_FAKE_BASE_URL)): + with patch.object(AppServer, "endpoint", PropertyMock(return_value=_FAKE_BASE_URL)): patch_server = AppServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(patch_server, AppServer) - assert patch_server.base_url == _FAKE_BASE_URL + assert patch_server.endpoint == _FAKE_BASE_URL assert patch_server.started is False # Approach 3: Create a new class that inherits the original class fake_server = FakeServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(fake_server, AppServer) - assert fake_server.base_url == _FAKE_BASE_URL + assert fake_server.endpoint == _FAKE_BASE_URL assert fake_server.started is True From eaedfde44e037e80fb1b4eef7bd80dd53a78178d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 19:55:18 -0700 Subject: [PATCH 410/553] Change start_server output in mocking lesson --- ultimatepython/advanced/mocking.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 82b962e6..6497dced 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -3,6 +3,8 @@ # Module-level constants _COUNTER = Counter(pid=1) +_START_SUCCESS = "success" +_START_FAILURE = "failure" _PROTOCOL_HTTP = "http" _PROTOCOL_HTTPS = "https" _FAKE_BASE_URL = f"{_PROTOCOL_HTTPS}://www.google.com:443" @@ -37,9 +39,11 @@ def started(self): return self.pid > 0 def start_server(self): + if self.started: + return _START_FAILURE self._pid = _COUNTER["pid"] _COUNTER["pid"] += 1 - return f"Started server: {self.endpoint}" + return _START_SUCCESS class FakeServer(AppServer): @@ -60,8 +64,9 @@ def main(): # This is the original class and it works as expected app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) assert app_server.endpoint == "http://localhost:8000" - assert app_server.start_server() == "Started server: http://localhost:8000" + assert app_server.start_server() == _START_SUCCESS assert app_server.started is True + assert app_server.start_server() == _START_FAILURE # But sometimes you cannot test the finer details of a class because # its methods depend on the availability of external resources. This @@ -81,12 +86,14 @@ def main(): assert isinstance(patch_server, AppServer) assert patch_server.endpoint == _FAKE_BASE_URL assert patch_server.started is False + assert patch_server.start_server() == _START_SUCCESS # Approach 3: Create a new class that inherits the original class fake_server = FakeServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(fake_server, AppServer) assert fake_server.endpoint == _FAKE_BASE_URL assert fake_server.started is True + assert patch_server.start_server() == _START_FAILURE if __name__ == "__main__": From 75fe9035ac3d07875d72923ec699363791a8fa03 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 20:04:36 -0700 Subject: [PATCH 411/553] Add __repr__ to abstract_class --- ultimatepython/classes/abstract_class.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 5fe6caf7..af490d8b 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -17,6 +17,9 @@ def __init__(self, name, title): self.name = name self.title = title + def __repr__(self): + return f"<{type(self).__name__} name={self.name}>" + def __str__(self): return self.name From 28394f8bf9759cbe3e5747429e9d304480afd6e1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 20:06:17 -0700 Subject: [PATCH 412/553] Revert "Add __repr__ to abstract_class" This reverts commit 75fe9035ac3d07875d72923ec699363791a8fa03. --- ultimatepython/classes/abstract_class.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index af490d8b..5fe6caf7 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -17,9 +17,6 @@ def __init__(self, name, title): self.name = name self.title = title - def __repr__(self): - return f"<{type(self).__name__} name={self.name}>" - def __str__(self): return self.name From 1cfbf6466071144bec327b099d38d26c76e469e7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 20:10:12 -0700 Subject: [PATCH 413/553] Remove unused __repr__ routines --- ultimatepython/advanced/meta_class.py | 7 ------- ultimatepython/classes/iterator_class.py | 3 --- 2 files changed, 10 deletions(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 1a687066..f9011e33 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -80,17 +80,10 @@ 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.""" diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 8af055b7..0e55c22b 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -30,9 +30,6 @@ def __init__(self, name, title, direct_reports): self.title = title self.direct_reports = direct_reports - def __repr__(self): - return f"" - class IterationError(RuntimeError): """Any error that comes while iterating through objects. From d808a245352d8a6ad7546da66e5298097228d88b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 12 Sep 2020 23:51:45 -0700 Subject: [PATCH 414/553] Add style conventions to contrib docs --- CONTRIBUTING.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd24432c..59d2d8ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ Every Python module is a lesson which helps developers build their own intuition for core Python. The modules tend to avoid physical I/O operations as they may be run in a constrained environment like a web browser. -### Standard block +### Standard structure Every standalone Python module consists of the following: @@ -49,8 +49,17 @@ if __name__ == "__main__": ``` If the module involves additional functions and classes, they are placed -above the `main` function. Additionally, module-level constants are -defined with the `_UNDER_SCORE_FIRST` convention. +above the `main` function. + +### Style conventions + +Apart from [PEP 8](https://www.python.org/dev/peps/pep-0008/), here are +some additional conventions: + +- Module imports are arranged by [isort](https://github.com/timothycrosley/isort) +- Module constants follow a `_UNDER_SCORE_FIRST` convention +- Strings have "double-quotes" unless a `"` exists in the string +- Dynamic strings make use of [f-strings](https://www.python.org/dev/peps/pep-0498/) ### Code coverage From f92c174dcee2aa77ad2738eed03e45900ac544e1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 09:24:12 -0700 Subject: [PATCH 415/553] Update docs in mocking lesson --- ultimatepython/advanced/mocking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 6497dced..00b5f2c9 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -14,10 +14,10 @@ class AppServer: """Application server. - Normally this isn't mocked because it is the runtime environment for - business logic, database endpoints, network sockets and more. But - this server definition is rather lightweight, so it's acceptable - in this case. + Normally we don't mock an application server because it is the runtime + environment (AKA central nervous system) for business logic, database + endpoints, network sockets and more. However, this server definition + is lightweight, so it's acceptable to mock this. """ def __init__(self, host, port, proto): From 9c2849f44a0cefb34f65318fe4f003ae33e7bc88 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 09:30:22 -0700 Subject: [PATCH 416/553] Refine docs in mocking lesson --- ultimatepython/advanced/mocking.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 00b5f2c9..e00eee36 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -28,17 +28,21 @@ def __init__(self, host, port, proto): @property def endpoint(self): + """Get application server endpoint URL.""" return f"{self._proto}://{self._host}:{self._port}" @property def pid(self): + """Get application server process ID.""" return self._pid @property def started(self): + """Check if application server is started.""" return self.pid > 0 - def start_server(self): + def start(self): + """Start application server.""" if self.started: return _START_FAILURE self._pid = _COUNTER["pid"] @@ -51,12 +55,12 @@ class FakeServer(AppServer): @property def endpoint(self): - """Mock output of public URL.""" + """Mock output of public endpoint URL.""" return _FAKE_BASE_URL @property def pid(self): - """Mock output of public PID.""" + """Mock output of public process ID.""" return _FAKE_PID @@ -64,9 +68,9 @@ def main(): # This is the original class and it works as expected app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) assert app_server.endpoint == "http://localhost:8000" - assert app_server.start_server() == _START_SUCCESS + assert app_server.start() == _START_SUCCESS assert app_server.started is True - assert app_server.start_server() == _START_FAILURE + assert app_server.start() == _START_FAILURE # But sometimes you cannot test the finer details of a class because # its methods depend on the availability of external resources. This @@ -86,14 +90,14 @@ def main(): assert isinstance(patch_server, AppServer) assert patch_server.endpoint == _FAKE_BASE_URL assert patch_server.started is False - assert patch_server.start_server() == _START_SUCCESS + assert patch_server.start() == _START_SUCCESS # Approach 3: Create a new class that inherits the original class fake_server = FakeServer("localhost", 8080, _PROTOCOL_HTTP) assert isinstance(fake_server, AppServer) assert fake_server.endpoint == _FAKE_BASE_URL assert fake_server.started is True - assert patch_server.start_server() == _START_FAILURE + assert patch_server.start() == _START_FAILURE if __name__ == "__main__": From bcf93edccf0b8f4dabce12de42adf45cfb724349 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 09:33:44 -0700 Subject: [PATCH 417/553] Use f-string in mocking lesson --- ultimatepython/advanced/mocking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index e00eee36..a9ed2f74 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -67,7 +67,7 @@ def pid(self): def main(): # This is the original class and it works as expected app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) - assert app_server.endpoint == "http://localhost:8000" + assert app_server.endpoint == f"{_PROTOCOL_HTTP}://localhost:8000" assert app_server.start() == _START_SUCCESS assert app_server.started is True assert app_server.start() == _START_FAILURE From 7bc4b4b517e2c35d6c689e2b99f7a2c78776efa4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 09:41:49 -0700 Subject: [PATCH 418/553] Refine comment in mocking lesson --- ultimatepython/advanced/mocking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index a9ed2f74..6442af9b 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -65,7 +65,7 @@ def pid(self): def main(): - # This is the original class and it works as expected + # This is the original class instance and it works as expected app_server = AppServer("localhost", 8000, _PROTOCOL_HTTP) assert app_server.endpoint == f"{_PROTOCOL_HTTP}://localhost:8000" assert app_server.start() == _START_SUCCESS From feaf7bd34abc4446996dea4c53fc7afcbb993e45 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 20:39:42 -0700 Subject: [PATCH 419/553] Create new regex lesson --- README.md | 1 + ultimatepython/advanced/regex.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ultimatepython/advanced/regex.py diff --git a/README.md b/README.md index 5c6ab8a2..bff65268 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ inspiring and highly encouraged if your goal is to become a true - Benchmark: [cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) + - Regular Expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py new file mode 100644 index 00000000..4faade9e --- /dev/null +++ b/ultimatepython/advanced/regex.py @@ -0,0 +1,41 @@ +import re + +# Module-level constants +_TEXT_HELLO = "World Hello Hello" +_TEXT_NAMES = "John, Jane" +_TEXT_ABC123 = "abc123" +_TEXT_BYE = "Bye for now" + + +def main(): + # Running `search` with "Hello" returns a `Match` object for first Hello + assert re.search(r"Hello", _TEXT_HELLO).start() == 6 + + # Running `search` with "Hello$" returns a `Match` object for last Hello + assert re.search(r"Hello$", _TEXT_HELLO).start() == 12 + + # Running `findall` with "Hi \w+" returns a list of strings + assert re.findall(r"\w+", _TEXT_NAMES) == ["John", "Jane"] + + # Running `match` with "[123]+" returns a `None` + assert re.match(r"[123]+", _TEXT_ABC123) is None + + # Running `match` with "[abc]+" returns a `Match` object + assert re.match(r"[abc]+", _TEXT_ABC123).group(0) == "abc" + + # Running `fullmatch` with "[\w]+" returns a `None` + assert re.fullmatch(r"[\w]+", _TEXT_BYE) is None + + # Running `fullmatch` with "[\w ]+" returns a `Match` object + assert re.fullmatch(r"[\w ]+", _TEXT_BYE).group(0) == _TEXT_BYE + + # There are many more ways to leverage regular expressions than what + # is shown here. To learn more about this topic, please consult the + # following resources: + # https://en.wikipedia.org/wiki/Regular_expression + # https://github.com/ziishaned/learn-regex + # https://github.com/learnbyexample/py_regular_expressions + + +if __name__ == "__main__": + main() From ae030dec2f22fd4a440663ab2b461439cdee48a0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 13 Sep 2020 20:48:07 -0700 Subject: [PATCH 420/553] Fix ToC title for regex lesson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bff65268..5e23074f 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ inspiring and highly encouraged if your goal is to become a true - Benchmark: [cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) - - Regular Expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) + - Regular expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) ## Additional resources From 26f70bbad6b2119483432a7e04eb735fb0ac4f3c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 09:11:18 -0700 Subject: [PATCH 421/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59d2d8ed..b6b6bba3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,9 +27,17 @@ must be legitimate and meaningful. Specifically: ## Python modules -Every Python module is a lesson which helps developers build their own -intuition for core Python. The modules tend to avoid physical I/O operations -as they may be run in a constrained environment like a web browser. +Every Python module is a standalone lesson which helps developers build their +own intuition for core Python. The primary way to teach these concepts is +through `assert` statements and not `print` statements. + +Certain Python concepts are skipped in this study guide in pursuit of the +statement above. However, it does open the doors for Python to run on any +computing environment - whether it be a IDE, browser, terminal or +standalone application. + +When creating or updating Python modules, please respect the guidelines in +the sub-sections below. ### Standard structure From 2779c3784687ddffe323f19958e349246739ffff Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 19:23:10 -0700 Subject: [PATCH 422/553] Revise style conventions in contrib docs --- CONTRIBUTING.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6b6bba3..bd4e887a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,8 +61,12 @@ above the `main` function. ### Style conventions -Apart from [PEP 8](https://www.python.org/dev/peps/pep-0008/), here are -some additional conventions: +The project follows conventions from these PEP proposals: + +- [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) +- [PEP 257 -- Docstring Conventions](https://www.python.org/dev/peps/pep-0257/) + +The project has additional conventions: - Module imports are arranged by [isort](https://github.com/timothycrosley/isort) - Module constants follow a `_UNDER_SCORE_FIRST` convention From c85aa76ec5f705e0192c7394cb7967d7a2ddaade Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 20:14:24 -0700 Subject: [PATCH 423/553] Make regex lesson more precise --- ultimatepython/advanced/regex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index 4faade9e..8b1ba5b0 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -17,13 +17,13 @@ def main(): # Running `findall` with "Hi \w+" returns a list of strings assert re.findall(r"\w+", _TEXT_NAMES) == ["John", "Jane"] - # Running `match` with "[123]+" returns a `None` + # Running `match` with "[123]+" returns a `None` object assert re.match(r"[123]+", _TEXT_ABC123) is None # Running `match` with "[abc]+" returns a `Match` object assert re.match(r"[abc]+", _TEXT_ABC123).group(0) == "abc" - # Running `fullmatch` with "[\w]+" returns a `None` + # Running `fullmatch` with "[\w]+" returns a `None` object assert re.fullmatch(r"[\w]+", _TEXT_BYE) is None # Running `fullmatch` with "[\w ]+" returns a `Match` object From 8ca1a71646142dffe7901e924f48af903634956e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 20:38:56 -0700 Subject: [PATCH 424/553] Remove public refs in mocking --- ultimatepython/advanced/mocking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 6442af9b..1cd340ce 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -55,12 +55,12 @@ class FakeServer(AppServer): @property def endpoint(self): - """Mock output of public endpoint URL.""" + """Mock output of endpoint URL.""" return _FAKE_BASE_URL @property def pid(self): - """Mock output of public process ID.""" + """Mock output of process ID.""" return _FAKE_PID From 5ea18fd13424299aa994d1fbcff72b56bd3ae182 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 21:52:32 -0700 Subject: [PATCH 425/553] Add docs to weak_ref lesson --- ultimatepython/advanced/weak_ref.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 2a046aee..3add913b 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -12,6 +12,7 @@ class Server: @classmethod def create(cls, role, provider=_CLOUD_PROVIDER): + """Create server with autogenerated SSID.""" return cls(uuid4().hex, role, provider) def __init__(self, ssid, role, provider): @@ -28,13 +29,16 @@ def __init__(self): @property def servers(self): + """Get set of added servers.""" return {s for s in self._servers} @property def server_count(self): + """Get count of added servers.""" return len(self.servers) def add(self, server): + """Add server to registry.""" self._servers.add(server) From 1befa772c299440dd3da2bb2837d8d084e670823 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 22:08:07 -0700 Subject: [PATCH 426/553] Refine docs in abstract_class lesson --- ultimatepython/classes/abstract_class.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 5fe6caf7..4e0e23f9 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -4,9 +4,10 @@ class Employee(ABC): """Abstract definition of an employee. - The Employee class is abstract because it inherits the `ABC` class + The `Employee` class is abstract because it inherits the `ABC` class and has at least one `abstractmethod`. That means we cannot create - an instance directly from its constructor. + an instance directly from its constructor. Also, all subclasses + needs to implement every `abstractmethod` in this class. For more about abstract classes, click the link below: From ce0ce7bff2be74f827c22e1739bdd43baf39b605 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 22:08:30 -0700 Subject: [PATCH 427/553] Fix grammar in doc of abstract_class lesson --- 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 4e0e23f9..d53630db 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -7,7 +7,7 @@ class Employee(ABC): The `Employee` class is abstract because it inherits the `ABC` class and has at least one `abstractmethod`. That means we cannot create an instance directly from its constructor. Also, all subclasses - needs to implement every `abstractmethod` in this class. + need to implement every `abstractmethod` in this class. For more about abstract classes, click the link below: From a05baf36155b6dbd73372032285bbe411bf36c25 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 14 Sep 2020 22:17:47 -0700 Subject: [PATCH 428/553] Add docs to abstract_class lesson --- ultimatepython/classes/abstract_class.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index d53630db..4014e762 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -23,10 +23,12 @@ def __str__(self): @abstractmethod def do_work(self): + """Do something for work.""" raise NotImplementedError @abstractmethod def do_relax(self): + """Do something to relax.""" raise NotImplementedError From e698d9e8956a50aadd63c2cdb525ec5a1e8f4d8e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 08:43:23 -0700 Subject: [PATCH 429/553] Update all comments in regex lesson --- ultimatepython/advanced/regex.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index 8b1ba5b0..54847a10 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -8,25 +8,28 @@ def main(): - # Running `search` with "Hello" returns a `Match` object for first Hello + # Running `search` with "Hello" has a match for first Hello assert re.search(r"Hello", _TEXT_HELLO).start() == 6 - # Running `search` with "Hello$" returns a `Match` object for last Hello + # Running `search` with "Hello$" has a match for last Hello assert re.search(r"Hello$", _TEXT_HELLO).start() == 12 - # Running `findall` with "Hi \w+" returns a list of strings + # Running `search` with "(Hello) (Hello)" has matches for Hello + assert re.search(r"(Hello) (Hello)", _TEXT_HELLO).groups() == ("Hello", "Hello") + + # Running `findall` with "Hi \w+" has list of strings assert re.findall(r"\w+", _TEXT_NAMES) == ["John", "Jane"] - # Running `match` with "[123]+" returns a `None` object + # Running `match` with "[123]+" has nothing assert re.match(r"[123]+", _TEXT_ABC123) is None - # Running `match` with "[abc]+" returns a `Match` object + # Running `match` with "[abc]+" has a match for abc assert re.match(r"[abc]+", _TEXT_ABC123).group(0) == "abc" - # Running `fullmatch` with "[\w]+" returns a `None` object + # Running `fullmatch` with "[\w]+" has nothing assert re.fullmatch(r"[\w]+", _TEXT_BYE) is None - # Running `fullmatch` with "[\w ]+" returns a `Match` object + # Running `fullmatch` with "[\w ]+" has a full match assert re.fullmatch(r"[\w ]+", _TEXT_BYE).group(0) == _TEXT_BYE # There are many more ways to leverage regular expressions than what From ed4cdcce492abce3c5afc961785dfc52ae8a0d72 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 08:45:08 -0700 Subject: [PATCH 430/553] Fix grammar with comment in regex lesson --- ultimatepython/advanced/regex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index 54847a10..fa99ad65 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -17,7 +17,7 @@ def main(): # Running `search` with "(Hello) (Hello)" has matches for Hello assert re.search(r"(Hello) (Hello)", _TEXT_HELLO).groups() == ("Hello", "Hello") - # Running `findall` with "Hi \w+" has list of strings + # Running `findall` with "Hi \w+" has a list of strings assert re.findall(r"\w+", _TEXT_NAMES) == ["John", "Jane"] # Running `match` with "[123]+" has nothing From cccfc0c349db9a557d2935d21c85fe79aa94fcd1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 10:16:17 -0700 Subject: [PATCH 431/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd4e887a..a40d022b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,11 +19,11 @@ The [README](README.md) is important because it is the most frequently viewed page in this repository. As such, any changes that are made to this page must be legitimate and meaningful. Specifically: -- All Python modules must be referenced in the ToC -- All links must point to HTTPS resources that return a `2xx` status -- External Python documentation must be useful for newcomers and professionals -- External GitHub repositories should have at least 1k stars -- External practice resources must have Python exercises +- All Python modules are referenced in the ToC +- All links point to HTTPS resources that return a `2xx` status +- External Python documentation are useful for newcomers and professionals +- External GitHub repositories have at least 1k stars +- External practice resources have Python exercises ## Python modules From 8e871abdd39f09df7eb79c3553f5fa47a68484b2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 10:29:18 -0700 Subject: [PATCH 432/553] Update regex.py --- ultimatepython/advanced/regex.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index fa99ad65..88c04e49 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -32,12 +32,12 @@ def main(): # Running `fullmatch` with "[\w ]+" has a full match assert re.fullmatch(r"[\w ]+", _TEXT_BYE).group(0) == _TEXT_BYE - # There are many more ways to leverage regular expressions than what - # is shown here. To learn more about this topic, please consult the - # following resources: + # To learn more about regular expressions: # https://en.wikipedia.org/wiki/Regular_expression # https://github.com/ziishaned/learn-regex - # https://github.com/learnbyexample/py_regular_expressions + + # To play around with regular expressions in the browser: + # https://regex101.com if __name__ == "__main__": From 5aac2b38b28c229c16ad48c5955304aad8a293ae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 14:39:58 -0700 Subject: [PATCH 433/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a40d022b..75573d9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ proceeding further. --- -The repository consists of documentation and Python modules. If you intend +The repository consists of documentation and Python modules. If you want to contribute by creating or updating resources of either type, please review the standards defined in the upcoming sections to learn how you can make a big impact for the developers consuming this content. From 65ee52b89fb8779546c9e8061030944e8182c42d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 17:30:31 -0700 Subject: [PATCH 434/553] Update basic_class.py --- ultimatepython/classes/basic_class.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 0c3ee214..f164843e 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -34,8 +34,10 @@ 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 + # Formal representation is used for debugging issues assert repr(car) == "" + + # Informal representation is used for displaying data assert str(car) == "Bumble Bee (2000)" # Call a method on the class constructor From 35e3b4c907d3ba28cb0e829e2ebdd41a73ec8258 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 17:31:54 -0700 Subject: [PATCH 435/553] Update basic_class.py --- ultimatepython/classes/basic_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index f164843e..8fc4592e 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -34,10 +34,10 @@ def main(): # Create a car with the provided class constructor car = Car("Bumble", "Bee", 2000, 200000.0) - # Formal representation is used for debugging issues + # Formal representation is good for debugging issues assert repr(car) == "" - # Informal representation is used for displaying data + # Informal representation is good for user output assert str(car) == "Bumble Bee (2000)" # Call a method on the class constructor From 463636dc1a08466b2d47e8c460746b3e17fcc7cd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 18:06:51 -0700 Subject: [PATCH 436/553] Add more slices to tuple --- ultimatepython/data_structures/tuple.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 5d96ebd9..e8e24434 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -9,6 +9,8 @@ def main(): # It can be sliced like a list assert immutable[1:3] == (2, 3) assert immutable[3:4] == (4,) + assert immutable[1::2] == (2, 4) + assert immutable[::-1] == (4, 3, 2, 1) # It can be iterated over like a list for ix, number in enumerate(immutable): From 36e3eca955dfc534c16731f6ff3d14c305fa385c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 18:22:35 -0700 Subject: [PATCH 437/553] Add real example for tuple lesson --- ultimatepython/data_structures/tuple.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index e8e24434..0fdf3303 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -23,6 +23,18 @@ def main(): smaller_immutable = immutable[0:2] assert smaller_immutable == (1, 2) + # We use tuples when the number of items is consistent. An example + # where this can help is a 2D game with X and Y coordinates. Using a + # tuple with two numbers can ensure that the number of coordinates + # doesn't change to one or three + moved_count = 0 + pos_x, pos_y = (0, 0) + for i in range(1, 5, 2): + moved_count += 1 + pos_x, pos_y = (pos_x + 10 * i, pos_y + 15 * i) + assert moved_count == 2 + assert pos_x == 40 and pos_y == 60 + if __name__ == "__main__": main() From 1be062bf8cb18462c3295aea8b32879a47ea92ba Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 18:28:06 -0700 Subject: [PATCH 438/553] Fix comment in 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 0fdf3303..6f42cad9 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -26,7 +26,7 @@ def main(): # We use tuples when the number of items is consistent. An example # where this can help is a 2D game with X and Y coordinates. Using a # tuple with two numbers can ensure that the number of coordinates - # doesn't change to one or three + # doesn't change to one, three, four, etc. moved_count = 0 pos_x, pos_y = (0, 0) for i in range(1, 5, 2): From e3aef25268fb71321c592fddf221d90b83802433 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 15 Sep 2020 22:33:37 -0700 Subject: [PATCH 439/553] Update paragraph in contrib docs --- CONTRIBUTING.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75573d9e..c0869e96 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,10 +8,11 @@ proceeding further. --- -The repository consists of documentation and Python modules. If you want -to contribute by creating or updating resources of either type, please -review the standards defined in the upcoming sections to learn how you -can make a big impact for the developers consuming this content. +The repository consists of documentation and Python modules. Before you +contribute to the repository with a pull request, review all the standards +listed in upcoming sections. That way, you can uphold the craftsmanship of +this project and still make an impact on the developers using this project +for learning purposes. ## README documentation From 8236cb6f63bf14ef0dd25a1463ff0ea5037a3f1e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 07:30:18 -0700 Subject: [PATCH 440/553] Create new data_format lesson --- README.md | 1 + ultimatepython/advanced/data_format.py | 85 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 ultimatepython/advanced/data_format.py diff --git a/README.md b/README.md index 5e23074f..03f50d86 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ inspiring and highly encouraged if your goal is to become a true - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) - Regular expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) + - Data format: [json | ElementTree](ultimatepython/advanced/data_format.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py new file mode 100644 index 00000000..b67ea3a5 --- /dev/null +++ b/ultimatepython/advanced/data_format.py @@ -0,0 +1,85 @@ +import json +from dataclasses import dataclass +from io import StringIO +from xml.etree import ElementTree as ETree + +# Module-level constants +_JSON_FILE = StringIO(""" +[ + { + "author": "John", + "title": "Summer", + "body": "Summer time is hot" + }, + { + "author": "Jane", + "title": "Winter", + "body": "Winter time is cold" + } +] +""") +_XML_FILE = StringIO(""" + + + John + Summer + Summer time is hot + + + Jane + Winter + Winter time is cold + + +""") + + +@dataclass +class Note: + """Note model.""" + author: str + title: str + body: str + + @classmethod + def from_data(cls, data): + """Create note from dictionary data.""" + return cls(**data) + + +def main(): + # Let's use `json.load` to parse note data from the JSON file + json_content = json.load(_JSON_FILE) + assert isinstance(json_content, list) + assert all(isinstance(item, dict) for item in json_content) + + # It's simple to convert note data to class instances + json_notes = [Note.from_data(data) for data in json_content] + assert all(isinstance(note, Note) for note in json_notes) + + # Let's use `Etree.parse` to parse note data from the XML file + tree = ETree.parse(_XML_FILE) + assert isinstance(tree, ETree.ElementTree) + + # XML tree hierarchies start with a root element + root_el = tree.getroot() + assert ETree.iselement(root_el) + assert root_el.tag == "notepad" + + # It's not simple to convert note data to class instances + xml_notes = [] + for note_el in root_el: + assert isinstance(note_el, ETree.Element) + assert note_el.tag == "note" + xml_notes.append(Note.from_data({ + attr_name: note_el.findtext(attr_name) + for attr_name in ("author", "title", "body") + })) + + # Note data is still the same between JSON and XML formats + for json_note, xml_note in zip(json_notes, xml_notes): + assert json_note == xml_note + + +if __name__ == "__main__": + main() From 6b543e37b8a1005537e1d1d69a59e29f416d2344 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:15:41 -0700 Subject: [PATCH 441/553] Parse CSV in data_format lesson --- ultimatepython/advanced/data_format.py | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index b67ea3a5..4cd3c3b6 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -1,5 +1,6 @@ import json -from dataclasses import dataclass +from csv import DictReader +from dataclasses import dataclass, fields from io import StringIO from xml.etree import ElementTree as ETree @@ -32,6 +33,10 @@ """) +_CSV_FILE = StringIO(""" +John,Summer,Summer time is hot +Jane,Winter,Winter time is cold +""") @dataclass @@ -46,39 +51,36 @@ def from_data(cls, data): """Create note from dictionary data.""" return cls(**data) + @classmethod + def fields(cls): + """Get field names to simplify parsing logic.""" + return tuple(field.name for field in fields(cls)) + def main(): # Let's use `json.load` to parse note data from the JSON file json_content = json.load(_JSON_FILE) - assert isinstance(json_content, list) - assert all(isinstance(item, dict) for item in json_content) - - # It's simple to convert note data to class instances json_notes = [Note.from_data(data) for data in json_content] assert all(isinstance(note, Note) for note in json_notes) # Let's use `Etree.parse` to parse note data from the XML file tree = ETree.parse(_XML_FILE) - assert isinstance(tree, ETree.ElementTree) - - # XML tree hierarchies start with a root element root_el = tree.getroot() - assert ETree.iselement(root_el) - assert root_el.tag == "notepad" - - # It's not simple to convert note data to class instances xml_notes = [] for note_el in root_el: - assert isinstance(note_el, ETree.Element) - assert note_el.tag == "note" xml_notes.append(Note.from_data({ attr_name: note_el.findtext(attr_name) - for attr_name in ("author", "title", "body") + for attr_name in Note.fields() })) - # Note data is still the same between JSON and XML formats - for json_note, xml_note in zip(json_notes, xml_notes): - assert json_note == xml_note + # Let's use `csv.DictReader` to parse note data from the CSV file + csv_reader = DictReader(_CSV_FILE, fieldnames=Note.fields()) + csv_notes = [Note.from_data(row) for row in csv_reader] + assert all(isinstance(note, Note) for note in csv_notes) + + # Note data is still the same between all three formats + for json_note, xml_note, csv_note in zip(json_notes, xml_notes, csv_notes): + assert json_note == xml_note == csv_note, f"{json_note} | {xml_note} | {csv_note}" if __name__ == "__main__": From 81a91d9c6b07e70622628669183b53deb0b1c598 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:18:56 -0700 Subject: [PATCH 442/553] Optimize XML parsing in data_format lesson --- ultimatepython/advanced/data_format.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 4cd3c3b6..f249e27d 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -65,13 +65,12 @@ def main(): # Let's use `Etree.parse` to parse note data from the XML file tree = ETree.parse(_XML_FILE) - root_el = tree.getroot() - xml_notes = [] - for note_el in root_el: - xml_notes.append(Note.from_data({ - attr_name: note_el.findtext(attr_name) - for attr_name in Note.fields() - })) + xml_notes = [ + Note.from_data({ + field: note_el.findtext(field) + for field in Note.fields() + }) for note_el in tree.getroot() + ] # Let's use `csv.DictReader` to parse note data from the CSV file csv_reader = DictReader(_CSV_FILE, fieldnames=Note.fields()) From 14bb4afb3c522bb20d0ad48a596514081566e28b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:20:49 -0700 Subject: [PATCH 443/553] Add assertion to data_format lesson --- ultimatepython/advanced/data_format.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index f249e27d..9434fc3b 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -71,6 +71,7 @@ def main(): for field in Note.fields() }) for note_el in tree.getroot() ] + assert all(isinstance(note, Note) for note in xml_notes) # Let's use `csv.DictReader` to parse note data from the CSV file csv_reader = DictReader(_CSV_FILE, fieldnames=Note.fields()) From a5d8b4291cc3c64cf335d78425f0220c0a772e04 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:21:32 -0700 Subject: [PATCH 444/553] Remove f-string in data_format lesson --- ultimatepython/advanced/data_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 9434fc3b..85ce3e19 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -80,7 +80,7 @@ def main(): # Note data is still the same between all three formats for json_note, xml_note, csv_note in zip(json_notes, xml_notes, csv_notes): - assert json_note == xml_note == csv_note, f"{json_note} | {xml_note} | {csv_note}" + assert json_note == xml_note == csv_note if __name__ == "__main__": From 1f8a3ae74b5451c13e6fe245c4f1d2300ab92272 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:23:33 -0700 Subject: [PATCH 445/553] Fixup title for data_format in ToC --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03f50d86..f6a51379 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ inspiring and highly encouraged if your goal is to become a true - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) - Regular expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) - - Data format: [json | ElementTree](ultimatepython/advanced/data_format.py) (:exploding_head:) + - Data format: [json | xml | csv](ultimatepython/advanced/data_format.py) (:exploding_head:) ## Additional resources From fbf6fdf579443a3c6a80c131300fa9d03d334d21 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 08:52:10 -0700 Subject: [PATCH 446/553] Change up constant comments in data_format lesson --- ultimatepython/advanced/data_format.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 85ce3e19..92333029 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -4,7 +4,8 @@ from io import StringIO from xml.etree import ElementTree as ETree -# Module-level constants +# JSON file with notes. For more information: +# https://fileinfo.com/extension/json _JSON_FILE = StringIO(""" [ { @@ -19,6 +20,9 @@ } ] """) + +# XML file with notes. For more information: +# https://fileinfo.com/extension/xml _XML_FILE = StringIO(""" @@ -33,6 +37,9 @@ """) + +# CSV file with notes. For more information: +# https://fileinfo.com/extension/csv _CSV_FILE = StringIO(""" John,Summer,Summer time is hot Jane,Winter,Winter time is cold From 0ecb40d037afcd8bc723afd9bdd71eb7fcb9a76e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 11:08:05 -0700 Subject: [PATCH 447/553] Update data_format.py --- ultimatepython/advanced/data_format.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 92333029..873824d3 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -4,7 +4,7 @@ from io import StringIO from xml.etree import ElementTree as ETree -# JSON file with notes. For more information: +# JSON file with notes. For info on this file format: # https://fileinfo.com/extension/json _JSON_FILE = StringIO(""" [ @@ -21,7 +21,7 @@ ] """) -# XML file with notes. For more information: +# XML file with notes. For info on this file format: # https://fileinfo.com/extension/xml _XML_FILE = StringIO(""" @@ -38,7 +38,7 @@ """) -# CSV file with notes. For more information: +# CSV file with notes. For info on this file format: # https://fileinfo.com/extension/csv _CSV_FILE = StringIO(""" John,Summer,Summer time is hot From fcce64d64497c1c2087fcb2a9e89ec4b9deb7ac9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 15:27:40 -0700 Subject: [PATCH 448/553] Use full package in data_format lesson --- ultimatepython/advanced/data_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 873824d3..8faa25d1 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -70,7 +70,7 @@ def main(): json_notes = [Note.from_data(data) for data in json_content] assert all(isinstance(note, Note) for note in json_notes) - # Let's use `Etree.parse` to parse note data from the XML file + # Let's use `ElementTree.parse` to parse note data from the XML file tree = ETree.parse(_XML_FILE) xml_notes = [ Note.from_data({ From cda8ca7dc1d5a05a417479c560810d2b3889e978 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 15:35:05 -0700 Subject: [PATCH 449/553] Add standard library reference in ToC --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f6a51379..96fa1bb5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ inspiring and highly encouraged if your goal is to become a true - 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:) + - Standard library: [The Python Standard Library](https://docs.python.org/3/library/) (:books:, :exploding_head:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) From 66d4e5a37dc78d18f1e84507781ed68a27bfeae4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 17:10:02 -0700 Subject: [PATCH 450/553] Update data_format.py --- ultimatepython/advanced/data_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 8faa25d1..0ed784a3 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -85,7 +85,7 @@ def main(): csv_notes = [Note.from_data(row) for row in csv_reader] assert all(isinstance(note, Note) for note in csv_notes) - # Note data is still the same between all three formats + # All three formats have similar `Note` objects for json_note, xml_note, csv_note in zip(json_notes, xml_notes, csv_notes): assert json_note == xml_note == csv_note From 8c370fe042e50391961d63b90f43491378c79355 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:16:44 -0700 Subject: [PATCH 451/553] Experiment with this format --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d7054f30..f61092ef 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ -Please read the [contributing guidelines](https://github.com/huangsam/ultimate-python/blob/master/CONTRIBUTING.md) before submitting a pull request. - --- +Please read the [contributing guidelines](https://github.com/huangsam/ultimate-python/blob/master/CONTRIBUTING.md) before submitting a pull request. + **Describe the change** A clear and concise description of what the change is. From 8d82c1228e23ab581d0af943cad9c992a9e56912 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:24:01 -0700 Subject: [PATCH 452/553] Use poetry as package manager (#9) Trying to make the repo compatible with Repl.it - Add setup step - Try shell instead - Use poetry run instead - Make usable for repl.it - Enable caching - Change it up with --no-dev - Use branch in cache key --- .circleci/config.yml | 21 +- poetry.lock | 353 +++++++++++++++++++++++++ pyproject.toml | 19 ++ requirements.txt | 3 - runner.py | 17 +- ultimatepython/advanced/data_format.py | 30 +-- 6 files changed, 402 insertions(+), 41 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index a65f7f45..4015e961 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,24 +1,31 @@ version: 2.1 orbs: - python: circleci/python@0.3.2 + python: circleci/python@1.0.0 jobs: build-and-test: executor: python/default steps: - checkout - - python/load-cache - - python/install-deps - - python/save-cache + - restore_cache: + keys: + - deps-{{ .Branch }}-{{ checksum "poetry.lock" }} - run: - command: flake8 + command: poetry install --no-dev + name: Install dependencies + - save_cache: + key: deps-{{ .Branch }}-{{ checksum "poetry.lock" }} + paths: + - /home/circleci/.cache/pypoetry/virtualenvs + - run: + command: poetry run flake8 name: Lint - run: - command: coverage run -m runner + command: poetry run coverage run -m runner name: Test - run: - command: coverage report + command: poetry run coverage report name: Report workflows: diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..c1272794 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,353 @@ +[[package]] +category = "dev" +description = "Disable App Nap on OS X 10.9" +marker = "sys_platform == \"darwin\"" +name = "appnope" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "dev" +description = "Specifications for callback functions passed in to an API" +name = "backcall" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "main" +description = "Code coverage measurement for Python" +name = "coverage" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.3" + +[package.extras] +toml = ["toml"] + +[[package]] +category = "dev" +description = "Decorators for Humans" +name = "decorator" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" + +[[package]] +category = "main" +description = "the modular source code checker: pep8 pyflakes and co" +name = "flake8" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.3" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +category = "dev" +description = "IPython: Productive Interactive Computing" +name = "ipython" +optional = false +python-versions = ">=3.7" +version = "7.18.1" + +[package.dependencies] +appnope = "*" +backcall = "*" +colorama = "*" +decorator = "*" +jedi = ">=0.10" +pexpect = ">4.3" +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] + +[[package]] +category = "dev" +description = "Vestigial utilities from IPython" +name = "ipython-genutils" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "main" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=3.6,<4.0" +version = "5.5.2" + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] + +[[package]] +category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +name = "jedi" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.17.2" + +[package.dependencies] +parso = ">=0.7.0,<0.8.0" + +[package.extras] +qa = ["flake8 (3.7.9)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] + +[[package]] +category = "main" +description = "McCabe checker, plugin for flake8" +name = "mccabe" +optional = false +python-versions = "*" +version = "0.6.1" + +[[package]] +category = "dev" +description = "A Python Parser" +name = "parso" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.7.1" + +[package.extras] +testing = ["docopt", "pytest (>=3.0.7)"] + +[[package]] +category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "sys_platform != \"win32\"" +name = "pexpect" +optional = false +python-versions = "*" +version = "4.8.0" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +name = "pickleshare" +optional = false +python-versions = "*" +version = "0.7.5" + +[[package]] +category = "dev" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" +optional = false +python-versions = ">=3.6.1" +version = "3.0.7" + +[package.dependencies] +wcwidth = "*" + +[[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "sys_platform != \"win32\"" +name = "ptyprocess" +optional = false +python-versions = "*" +version = "0.6.0" + +[[package]] +category = "main" +description = "Python style guide checker" +name = "pycodestyle" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" + +[[package]] +category = "main" +description = "passive checker of Python programs" +name = "pyflakes" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.2.0" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=3.5" +version = "2.7.0" + +[[package]] +category = "dev" +description = "Traitlets Python configuration system" +name = "traitlets" +optional = false +python-versions = ">=3.7" +version = "5.0.4" + +[package.dependencies] +ipython-genutils = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.2.5" + +[metadata] +content-hash = "ed1cad59b750ac71dd455b17071b813e2e2cf6625a4652108ed399b0d69d60bf" +lock-version = "1.0" +python-versions = "^3.8" + +[metadata.files] +appnope = [ + {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, + {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +coverage = [ + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] +flake8 = [ + {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, + {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, +] +ipython = [ + {file = "ipython-7.18.1-py3-none-any.whl", hash = "sha256:2e22c1f74477b5106a6fb301c342ab8c64bb75d702e350f05a649e8cb40a0fb8"}, + {file = "ipython-7.18.1.tar.gz", hash = "sha256:a331e78086001931de9424940699691ad49dfb457cea31f5471eae7b78222d5e"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +isort = [ + {file = "isort-5.5.2-py3-none-any.whl", hash = "sha256:ba91218eee31f1e300ecc079ef0c524cea3fc41bfbb979cbdf5fd3a889e3cfed"}, + {file = "isort-5.5.2.tar.gz", hash = "sha256:171c5f365791073426b5ed3a156c2081a47f88c329161fd28228ff2da4c97ddb"}, +] +jedi = [ + {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, + {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +parso = [ + {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, + {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.7-py3-none-any.whl", hash = "sha256:83074ee28ad4ba6af190593d4d4c607ff525272a504eb159199b6dd9f950c950"}, + {file = "prompt_toolkit-3.0.7.tar.gz", hash = "sha256:822f4605f28f7d2ba6b0b09a31e25e140871e96364d1d377667b547bb3bf4489"}, +] +ptyprocess = [ + {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, + {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pygments = [ + {file = "Pygments-2.7.0-py3-none-any.whl", hash = "sha256:2df50d16b45b977217e02cba6c8422aaddb859f3d0570a88e09b00eafae89c6e"}, + {file = "Pygments-2.7.0.tar.gz", hash = "sha256:2594e8fdb06fef91552f86f4fd3a244d148ab24b66042036e64f29a291515048"}, +] +traitlets = [ + {file = "traitlets-5.0.4-py3-none-any.whl", hash = "sha256:9664ec0c526e48e7b47b7d14cd6b252efa03e0129011de0a9c1d70315d4309c3"}, + {file = "traitlets-5.0.4.tar.gz", hash = "sha256:86c9351f94f95de9db8a04ad8e892da299a088a64fd283f9f6f18770ae5eae1b"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..56780eda --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "ultimate-python" +version = "0.1.0" +description = "Ultimate Python study guide" +authors = ["Samuel Huang "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +coverage = "^5.3" +flake8 = "^3.8.3" +isort = "^5.5.2" + +[tool.poetry.dev-dependencies] +ipython = "^7.18.1" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index afa7d5ca..00000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -coverage -flake8 -isort diff --git a/runner.py b/runner.py index e6fc254c..e19fd025 100644 --- a/runner.py +++ b/runner.py @@ -1,9 +1,5 @@ -import io -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 from ultimatepython import __name__ as root_name @@ -17,16 +13,6 @@ _MODULE_MAIN = "main" -@contextmanager -def no_stdout(): - """Silence standard output with /dev/null.""" - save_stdout = sys.stdout - with io.open(devnull, "w") as dev_null: - sys.stdout = dev_null - yield - sys.stdout = save_stdout - - def success_text(text): """Get success text.""" return f"{_STYLE_SUCCESS}{bold_text(text)}{_STYLE_END}" @@ -58,8 +44,7 @@ def main(): # The main function should not throw any errors print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_MODULE_MAIN}", end="") - with no_stdout(): - main_func() + main_func() print(" [PASS]") print(success_text(f"Finish {root_name} runner")) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 0ed784a3..7d603578 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -4,9 +4,9 @@ from io import StringIO from xml.etree import ElementTree as ETree -# JSON file with notes. For info on this file format: +# Data in XML format. For more info on this format: # https://fileinfo.com/extension/json -_JSON_FILE = StringIO(""" +_JSON_DATA = """ [ { "author": "John", @@ -19,11 +19,11 @@ "body": "Winter time is cold" } ] -""") +""" -# XML file with notes. For info on this file format: +# Data in XML format. For more info on this format: # https://fileinfo.com/extension/xml -_XML_FILE = StringIO(""" +_XML_DATA = """ John @@ -36,14 +36,14 @@ Winter time is cold -""") +""" -# CSV file with notes. For info on this file format: +# Data in CSV format. For more info on this format: # https://fileinfo.com/extension/csv -_CSV_FILE = StringIO(""" +_CSV_DATA = """ John,Summer,Summer time is hot Jane,Winter,Winter time is cold -""") +""" @dataclass @@ -65,13 +65,13 @@ def fields(cls): def main(): - # Let's use `json.load` to parse note data from the JSON file - json_content = json.load(_JSON_FILE) + # Let's use `json.load` to parse note data from a JSON file + json_content = json.load(StringIO(_JSON_DATA)) json_notes = [Note.from_data(data) for data in json_content] assert all(isinstance(note, Note) for note in json_notes) - # Let's use `ElementTree.parse` to parse note data from the XML file - tree = ETree.parse(_XML_FILE) + # Let's use `ElementTree.parse` to parse note data from a XML file + tree = ETree.parse(StringIO(_XML_DATA)) xml_notes = [ Note.from_data({ field: note_el.findtext(field) @@ -80,8 +80,8 @@ def main(): ] assert all(isinstance(note, Note) for note in xml_notes) - # Let's use `csv.DictReader` to parse note data from the CSV file - csv_reader = DictReader(_CSV_FILE, fieldnames=Note.fields()) + # Let's use `csv.DictReader` to parse note data from a CSV file + csv_reader = DictReader(StringIO(_CSV_DATA), fieldnames=Note.fields()) csv_notes = [Note.from_data(row) for row in csv_reader] assert all(isinstance(note, Note) for note in csv_notes) From 4d1aca76a886346e13a7ff297de1cab987a1dedd Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:34:53 -0700 Subject: [PATCH 453/553] Remove ipython from dev dependencies --- poetry.lock | 232 +------------------------------------------------ pyproject.toml | 1 - 2 files changed, 1 insertion(+), 232 deletions(-) diff --git a/poetry.lock b/poetry.lock index c1272794..ed2d4015 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,29 +1,3 @@ -[[package]] -category = "dev" -description = "Disable App Nap on OS X 10.9" -marker = "sys_platform == \"darwin\"" -name = "appnope" -optional = false -python-versions = "*" -version = "0.1.0" - -[[package]] -category = "dev" -description = "Specifications for callback functions passed in to an API" -name = "backcall" -optional = false -python-versions = "*" -version = "0.2.0" - -[[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" - [[package]] category = "main" description = "Code coverage measurement for Python" @@ -35,14 +9,6 @@ version = "5.3" [package.extras] toml = ["toml"] -[[package]] -category = "dev" -description = "Decorators for Humans" -name = "decorator" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.4.2" - [[package]] category = "main" description = "the modular source code checker: pep8 pyflakes and co" @@ -56,46 +22,6 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" -[[package]] -category = "dev" -description = "IPython: Productive Interactive Computing" -name = "ipython" -optional = false -python-versions = ">=3.7" -version = "7.18.1" - -[package.dependencies] -appnope = "*" -backcall = "*" -colorama = "*" -decorator = "*" -jedi = ">=0.10" -pexpect = ">4.3" -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = "*" -setuptools = ">=18.5" -traitlets = ">=4.2" - -[package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] -doc = ["Sphinx (>=1.3)"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["notebook", "ipywidgets"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] - -[[package]] -category = "dev" -description = "Vestigial utilities from IPython" -name = "ipython-genutils" -optional = false -python-versions = "*" -version = "0.2.0" - [[package]] category = "main" description = "A Python utility / library to sort Python imports." @@ -109,21 +35,6 @@ colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] -[[package]] -category = "dev" -description = "An autocompletion tool for Python that can be used for text editors." -name = "jedi" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.17.2" - -[package.dependencies] -parso = ">=0.7.0,<0.8.0" - -[package.extras] -qa = ["flake8 (3.7.9)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] - [[package]] category = "main" description = "McCabe checker, plugin for flake8" @@ -132,57 +43,6 @@ optional = false python-versions = "*" version = "0.6.1" -[[package]] -category = "dev" -description = "A Python Parser" -name = "parso" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.7.1" - -[package.extras] -testing = ["docopt", "pytest (>=3.0.7)"] - -[[package]] -category = "dev" -description = "Pexpect allows easy control of interactive console applications." -marker = "sys_platform != \"win32\"" -name = "pexpect" -optional = false -python-versions = "*" -version = "4.8.0" - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -category = "dev" -description = "Tiny 'shelve'-like database with concurrency support" -name = "pickleshare" -optional = false -python-versions = "*" -version = "0.7.5" - -[[package]] -category = "dev" -description = "Library for building powerful interactive command lines in Python" -name = "prompt-toolkit" -optional = false -python-versions = ">=3.6.1" -version = "3.0.7" - -[package.dependencies] -wcwidth = "*" - -[[package]] -category = "dev" -description = "Run a subprocess in a pseudo terminal" -marker = "sys_platform != \"win32\"" -name = "ptyprocess" -optional = false -python-versions = "*" -version = "0.6.0" - [[package]] category = "main" description = "Python style guide checker" @@ -199,54 +59,12 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.2.0" -[[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." -name = "pygments" -optional = false -python-versions = ">=3.5" -version = "2.7.0" - -[[package]] -category = "dev" -description = "Traitlets Python configuration system" -name = "traitlets" -optional = false -python-versions = ">=3.7" -version = "5.0.4" - -[package.dependencies] -ipython-genutils = "*" - -[package.extras] -test = ["pytest"] - -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - [metadata] -content-hash = "ed1cad59b750ac71dd455b17071b813e2e2cf6625a4652108ed399b0d69d60bf" +content-hash = "7f7548ae5cf1510fdcf2450b8a713bd869e075e7fe9f1a8eef0292bf2257115d" lock-version = "1.0" python-versions = "^3.8" [metadata.files] -appnope = [ - {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, - {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, -] -backcall = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] coverage = [ {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, @@ -283,54 +101,18 @@ coverage = [ {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] -decorator = [ - {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, - {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, -] flake8 = [ {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, ] -ipython = [ - {file = "ipython-7.18.1-py3-none-any.whl", hash = "sha256:2e22c1f74477b5106a6fb301c342ab8c64bb75d702e350f05a649e8cb40a0fb8"}, - {file = "ipython-7.18.1.tar.gz", hash = "sha256:a331e78086001931de9424940699691ad49dfb457cea31f5471eae7b78222d5e"}, -] -ipython-genutils = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, -] isort = [ {file = "isort-5.5.2-py3-none-any.whl", hash = "sha256:ba91218eee31f1e300ecc079ef0c524cea3fc41bfbb979cbdf5fd3a889e3cfed"}, {file = "isort-5.5.2.tar.gz", hash = "sha256:171c5f365791073426b5ed3a156c2081a47f88c329161fd28228ff2da4c97ddb"}, ] -jedi = [ - {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, - {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, -] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -parso = [ - {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, - {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, -] -pexpect = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] -pickleshare = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] -prompt-toolkit = [ - {file = "prompt_toolkit-3.0.7-py3-none-any.whl", hash = "sha256:83074ee28ad4ba6af190593d4d4c607ff525272a504eb159199b6dd9f950c950"}, - {file = "prompt_toolkit-3.0.7.tar.gz", hash = "sha256:822f4605f28f7d2ba6b0b09a31e25e140871e96364d1d377667b547bb3bf4489"}, -] -ptyprocess = [ - {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, - {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, -] pycodestyle = [ {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, @@ -339,15 +121,3 @@ pyflakes = [ {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] -pygments = [ - {file = "Pygments-2.7.0-py3-none-any.whl", hash = "sha256:2df50d16b45b977217e02cba6c8422aaddb859f3d0570a88e09b00eafae89c6e"}, - {file = "Pygments-2.7.0.tar.gz", hash = "sha256:2594e8fdb06fef91552f86f4fd3a244d148ab24b66042036e64f29a291515048"}, -] -traitlets = [ - {file = "traitlets-5.0.4-py3-none-any.whl", hash = "sha256:9664ec0c526e48e7b47b7d14cd6b252efa03e0129011de0a9c1d70315d4309c3"}, - {file = "traitlets-5.0.4.tar.gz", hash = "sha256:86c9351f94f95de9db8a04ad8e892da299a088a64fd283f9f6f18770ae5eae1b"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] diff --git a/pyproject.toml b/pyproject.toml index 56780eda..89c1065c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ flake8 = "^3.8.3" isort = "^5.5.2" [tool.poetry.dev-dependencies] -ipython = "^7.18.1" [build-system] requires = ["poetry>=0.12"] From 8f7d0b745e8e92afa5adbe4ace032a826cb6eb6c Mon Sep 17 00:00:00 2001 From: Add1solver <71240297+Add1solver@users.noreply.github.com> Date: Thu, 17 Sep 2020 01:36:22 -0500 Subject: [PATCH 454/553] Add run on repl.it badge to README (#8) This pull request adds a badge to the . This will allow users to easily run this repository in their browser, without having to set up an environment. You can learn more about Repl.it [here](https://repl.it). --- .replit | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 .replit diff --git a/.replit b/.replit new file mode 100644 index 00000000..8ac7b5ad --- /dev/null +++ b/.replit @@ -0,0 +1,2 @@ +language = "python3" +run = "/ultimatepython" \ No newline at end of file diff --git a/README.md b/README.md index 96fa1bb5..d42b888d 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,5 @@ Keep practicing so that your coding skills don't get rusty. - [kaggle.com](https://www.kaggle.com/) (:brain:) - [exercism.io](https://exercism.io/) - [projecteuler.net](https://projecteuler.net/) + +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) \ No newline at end of file From b1f1b273d53604e8baa36ee89cdf5378f4b830f4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:37:50 -0700 Subject: [PATCH 455/553] Fixup .replit file --- .replit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.replit b/.replit index 8ac7b5ad..66d6a7a4 100644 --- a/.replit +++ b/.replit @@ -1,2 +1,2 @@ language = "python3" -run = "/ultimatepython" \ No newline at end of file +run = "python runner.py" From 9733df2bb00aacc10357f905fbb07ed60fdefc1e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:40:41 -0700 Subject: [PATCH 456/553] Move repl.it badge to top of README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d42b888d..1b2c33b9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From c47171eee758818c1d6ce818d843be7bc066fc5a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:42:21 -0700 Subject: [PATCH 457/553] Add sonarcloud badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1b2c33b9..6ab40e78 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) ![](https://img.shields.io/github/license/huangsam/ultimate-python) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From 20ec495c3f77ca00be6e8eca39aee0a10c6ba9b7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 16 Sep 2020 23:43:46 -0700 Subject: [PATCH 458/553] Remove repl.it badge at the bottom of README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 6ab40e78..018d5d9a 100644 --- a/README.md +++ b/README.md @@ -115,5 +115,3 @@ Keep practicing so that your coding skills don't get rusty. - [kaggle.com](https://www.kaggle.com/) (:brain:) - [exercism.io](https://exercism.io/) - [projecteuler.net](https://projecteuler.net/) - -[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) \ No newline at end of file From a4039b6dea61127fde376c2e6202ed8502d55ca3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 17 Sep 2020 07:55:06 -0700 Subject: [PATCH 459/553] Add links to badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 018d5d9a..7fbb9ad0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Ultimate Python study guide -![](https://img.shields.io/circleci/build/github/huangsam/ultimate-python) -![](https://img.shields.io/github/license/huangsam/ultimate-python) +[![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](README.md) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) From 4509f8a44d4a4276f331acb7f34b3cf8c816b2ce Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 17 Sep 2020 07:55:27 -0700 Subject: [PATCH 460/553] Fix license linkage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fbb9ad0..aab80c8e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Ultimate Python study guide [![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) -[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](README.md) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) From 4464a407af46ba982f825e3590a570d635513602 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 17 Sep 2020 08:11:02 -0700 Subject: [PATCH 461/553] Add subreddit badge to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aab80c8e..d79fbf9f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) +[![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From ec5d51603848d58d18a59217d9f3fb54d23beeb8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 17 Sep 2020 12:07:54 -0700 Subject: [PATCH 462/553] Update data_format.py --- ultimatepython/advanced/data_format.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 7d603578..5fd9d2a2 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -4,7 +4,7 @@ from io import StringIO from xml.etree import ElementTree as ETree -# Data in XML format. For more info on this format: +# Data in JSON format. For more info on this format: # https://fileinfo.com/extension/json _JSON_DATA = """ [ From 4265c8e8766a5bc362b90b588a091a7064d2d8e7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 11:02:15 -0700 Subject: [PATCH 463/553] Update pull_request_template --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f61092ef..000a8182 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ---- +*Please read the [contributing guidelines](https://github.com/huangsam/ultimate-python/blob/master/CONTRIBUTING.md) before submitting a pull request.* -Please read the [contributing guidelines](https://github.com/huangsam/ultimate-python/blob/master/CONTRIBUTING.md) before submitting a pull request. +--- **Describe the change** A clear and concise description of what the change is. From 9259ba988fd4c2ee45a0743241e28912bea10141 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 11:06:33 -0700 Subject: [PATCH 464/553] Add codecov.io integration (#10) --- .circleci/config.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4015e961..962c8733 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,6 +2,7 @@ version: 2.1 orbs: python: circleci/python@1.0.0 + codecov: codecov/codecov@1.1.1 jobs: build-and-test: @@ -27,6 +28,16 @@ jobs: - run: command: poetry run coverage report name: Report + - run: + command: | + mkdir -p /tmp/coverage/custom + poetry run coverage xml -o /tmp/coverage/results.xml + name: Prepare coverage results + - store_artifacts: + path: /tmp/coverage/results.xml + - codecov/upload: + file: /tmp/coverage/results.xml + workflows: main: From 84fbc671ca54258afa54ed33c862df185e849a0a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 11:17:27 -0700 Subject: [PATCH 465/553] Add codecov.io badge and reorder others --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d79fbf9f..65a7dd71 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Ultimate Python study guide -[![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) -[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) +[![CircleCI](https://circleci.com/gh/huangsam/ultimate-python.svg?style=shield)](https://circleci.com/gh/huangsam/ultimate-python) +[![Code Coverage](https://codecov.io/gh/huangsam/ultimate-python/branch/master/graph/badge.svg)](https://codecov.io/gh/huangsam/ultimate-python) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) -[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) [![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) + Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: ```python From 77bb1bfa2958360d856b8629e36c92693a9e8195 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 11:54:39 -0700 Subject: [PATCH 466/553] Add getting started content (#11) --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65a7dd71..9a3cbf9f 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ [![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) [![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) -[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) - Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: ```python @@ -45,6 +43,31 @@ such, popular open-source libraries and frameworks (i.e. `sqlalchemy`, `requests inspiring and highly encouraged if your goal is to become a true [Pythonista](https://www.urbandictionary.com/define.php?term=pythonista). +## Getting started + +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) + +Click the badge above to spin up a working environment in the browser without +needing Git and Python installed on a local machine. If those requirements +are already met, feel free to proceed using the manual steps below. + +### Manual steps + +Start by cloning the repository: + + git clone https://github.com/huangsam/ultimate-python.git + +Once the repository is cloned, we are ready to learn. There are two ways of +running the modules. + +One way is to run a single module: + + python ultimatepython/syntax/variable.py + +Another way is to run all of the modules: + + python runner.py + ## Table of contents :books: = External resource, From dd67994a3afa183630867d8b7c49bea59a3ad1fb Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 11:59:12 -0700 Subject: [PATCH 467/553] Shorten manual steps in README --- README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9a3cbf9f..2062a007 100644 --- a/README.md +++ b/README.md @@ -53,20 +53,13 @@ are already met, feel free to proceed using the manual steps below. ### Manual steps -Start by cloning the repository: - - git clone https://github.com/huangsam/ultimate-python.git +Start by cloning the repository: `git clone https://github.com/huangsam/ultimate-python.git` Once the repository is cloned, we are ready to learn. There are two ways of running the modules. -One way is to run a single module: - - python ultimatepython/syntax/variable.py - -Another way is to run all of the modules: - - python runner.py +1. Run a single module: `python ultimatepython/syntax/variable.py` +2. Run all of the modules: `python runner.py` ## Table of contents From 39a04333e7e202a1206714c68833dd1d14037f8c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 13:42:34 -0700 Subject: [PATCH 468/553] Use shields.io for top badges --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2062a007..aabc4432 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Ultimate Python study guide -[![CircleCI](https://circleci.com/gh/huangsam/ultimate-python.svg?style=shield)](https://circleci.com/gh/huangsam/ultimate-python) -[![Code Coverage](https://codecov.io/gh/huangsam/ultimate-python/branch/master/graph/badge.svg)](https://codecov.io/gh/huangsam/ultimate-python) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=huangsam_ultimate-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) +[![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) +[![Code Coverage](https://img.shields.io/codecov/c/github/huangsam/ultimate-python)](https://codecov.io/gh/huangsam/ultimate-python) +[![Quality Gate Status](https://img.shields.io/sonar/quality_gate/huangsam_ultimate-python?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) [![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) [![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) From da3f00f381658571a94b4be5691f196b82f56eef Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 14:59:01 -0700 Subject: [PATCH 469/553] Update README.md --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aabc4432..d7370a65 100644 --- a/README.md +++ b/README.md @@ -48,15 +48,12 @@ inspiring and highly encouraged if your goal is to become a true [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) Click the badge above to spin up a working environment in the browser without -needing Git and Python installed on a local machine. If those requirements -are already met, feel free to proceed using the manual steps below. +needing Git and Python installed on a local machine. If these requirements +are already met, feel free to clone the repository directly. -### Manual steps - -Start by cloning the repository: `git clone https://github.com/huangsam/ultimate-python.git` - -Once the repository is cloned, we are ready to learn. There are two ways of -running the modules. +Once the repository is accessible, we are ready to learn from the modules. +To get the most out of each module, read the module code and run it. There +are two ways of running the modules: 1. Run a single module: `python ultimatepython/syntax/variable.py` 2. Run all of the modules: `python runner.py` @@ -73,6 +70,7 @@ running the modules. - 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:) - Standard library: [The Python Standard Library](https://docs.python.org/3/library/) (:books:, :exploding_head:) + - Built-in functions: [Built-in Functions](https://docs.python.org/3/library/functions.html) (:books:) 2. **Syntax** - Variable: [Built-in literals](ultimatepython/syntax/variable.py) (:cake:) - Expression: [Numeric operations](ultimatepython/syntax/expression.py) (:cake:) From 3a0a96c7bda9e182d4e205c39b462296627b23b5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 18 Sep 2020 18:36:47 -0700 Subject: [PATCH 470/553] Refine getting started section again --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d7370a65..fe8cf854 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ Click the badge above to spin up a working environment in the browser without needing Git and Python installed on a local machine. If these requirements are already met, feel free to clone the repository directly. -Once the repository is accessible, we are ready to learn from the modules. -To get the most out of each module, read the module code and run it. There -are two ways of running the modules: +Once the repository is accessible, we are ready to learn from the standalone +modules. To get the most out of each module, read the module code and run it. +There are two ways of running the modules: 1. Run a single module: `python ultimatepython/syntax/variable.py` 2. Run all of the modules: `python runner.py` From b2f08fbffcc5ddeb549e3a929f447f151ab1c10b Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 19 Sep 2020 09:52:55 -0700 Subject: [PATCH 471/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0869e96..386bf73d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ through `assert` statements and not `print` statements. Certain Python concepts are skipped in this study guide in pursuit of the statement above. However, it does open the doors for Python to run on any -computing environment - whether it be a IDE, browser, terminal or +computing environment - whether it be an IDE, browser, terminal or standalone application. When creating or updating Python modules, please respect the guidelines in From aeb5ea06b28213c0b030096c6276708478f1ffbf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 19 Sep 2020 19:01:42 -0700 Subject: [PATCH 472/553] Normalize ultimate-python metadata --- pyproject.toml | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 89c1065c..e88deb58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ultimate-python" -version = "0.1.0" +version = "1.0.0" description = "Ultimate Python study guide" authors = ["Samuel Huang "] license = "MIT" diff --git a/setup.py b/setup.py index 5cceefbf..af8dbbe1 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ from setuptools import find_packages, setup setup( - name="ultimatepython", + name="ultimate-python", + version="1.0.0", packages=find_packages(), description="Ultimate Python study guide", classifiers=[ From ff99225761e62a80569203078af5d4bbb601c046 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 20 Sep 2020 14:23:59 -0700 Subject: [PATCH 473/553] Use we instead of you in advanced modules --- ultimatepython/advanced/context_manager.py | 2 +- ultimatepython/advanced/mocking.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index ebf310b6..f7909c2c 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -30,7 +30,7 @@ class FileHandler: """File handler context manager. This is the class variant of the context manager. Just like the iterator - lesson, it depends on context and preference that you choose one style + lesson, it depends on context and preference that we choose one style over the other. """ diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index 1cd340ce..b01379b6 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -72,7 +72,7 @@ def main(): assert app_server.started is True assert app_server.start() == _START_FAILURE - # But sometimes you cannot test the finer details of a class because + # But sometimes we cannot test the finer details of a class because # its methods depend on the availability of external resources. This # is where mocking comes to the rescue. There are a couple approaches # that developers use when it comes to mocking From b96bc46d91f5a7b9a87bde082107ad97c06e52c8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 20 Sep 2020 14:26:17 -0700 Subject: [PATCH 474/553] Change we to you in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe8cf854..9d63aa30 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Click the badge above to spin up a working environment in the browser without needing Git and Python installed on a local machine. If these requirements are already met, feel free to clone the repository directly. -Once the repository is accessible, we are ready to learn from the standalone +Once the repository is accessible, you are ready to learn from the standalone modules. To get the most out of each module, read the module code and run it. There are two ways of running the modules: From 19e4c75048b365fef1f6d6d0b5e6d4fe7e3a4089 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 21 Sep 2020 13:13:44 -0700 Subject: [PATCH 475/553] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..50d26646 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: huangsam From 335db1061d290828f0d82d757ab08dc79890a2c2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 21 Sep 2020 13:14:19 -0700 Subject: [PATCH 476/553] Rename pull_request_template.md to PULL_REQUEST_TEMPLATE.md --- .github/{pull_request_template.md => PULL_REQUEST_TEMPLATE.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{pull_request_template.md => PULL_REQUEST_TEMPLATE.md} (100%) diff --git a/.github/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE.md From 982a51d1d6eaf05dbc9b2bbec51a721fa0d6b2df Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 21 Sep 2020 13:48:34 -0700 Subject: [PATCH 477/553] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d63aa30..5df2917d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) [![Code Coverage](https://img.shields.io/codecov/c/github/huangsam/ultimate-python)](https://codecov.io/gh/huangsam/ultimate-python) [![Quality Gate Status](https://img.shields.io/sonar/quality_gate/huangsam_ultimate-python?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) -[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](LICENSE) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](https://github.com/huangsam/ultimate-python/blob/master/LICENSE) [![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) Ultimate Python study guide for newcomers and professionals alike. :snake: :snake: :snake: From 1d37fbd05068e0590bd86a3d37d040087631b72c Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 22 Sep 2020 09:16:40 -0700 Subject: [PATCH 478/553] Add content to dict lesson --- ultimatepython/data_structures/dict.py | 33 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index d5944510..b9fb130c 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,7 +1,12 @@ +# Module-level constants +_GPA_MAX = 4.0 +_GPA_MIN = 0.0 + + def main(): # Let's create a dictionary with student keys and GPA values student_gpa = {"john": 3.5, - "jane": 4.0, + "jane": _GPA_MAX, "bob": 2.8, "mary": 3.2} @@ -11,20 +16,40 @@ def main(): # Each student has a name key and a GPA value assert len(student_gpa.keys()) == len(student_gpa.values()) - # We can get the names in isolation + # We can get the names in isolation. Note that in Python 3.7 and + # above dictionaries are sorted in the order that they were defined + # or inserted + student_names = [] for student in student_gpa.keys(): - assert len(student) > 2 + student_names.append(student) + assert student_names == ["john", "jane", "bob", "mary"] # We can get the GPAs in isolation + gpa_values = [] for gpa in student_gpa.values(): - assert gpa > 2.0 + gpa_values.append(gpa) + assert gpa_values == [3.5, _GPA_MAX, 2.8, 3.2] # We can get the GPA for a specific student assert student_gpa["john"] == 3.5 + # We can update the GPA for a specific student + student_gpa["john"] = _GPA_MAX + + # Or update the GPA for multiple students + student_gpa.update(bob=_GPA_MIN, mary=_GPA_MIN) + # We can access the student and GPA simultaneously + gpa_binary = [] for student, gpa in student_gpa.items(): assert student_gpa[student] == gpa + gpa_binary.append(gpa) + assert gpa_binary == [_GPA_MAX, _GPA_MAX, _GPA_MIN, _GPA_MIN] + + # To end this lesson, let's remove all the students + for student_name in student_names: + student_gpa.pop(student_name) + assert len(student_gpa) == 0 if __name__ == "__main__": From 37e92a474220c46005319f3f2a66a5e3483d45ed Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 22 Sep 2020 09:20:28 -0700 Subject: [PATCH 479/553] Fix clarification 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 b9fb130c..bdfd5b82 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -17,8 +17,8 @@ def main(): assert len(student_gpa.keys()) == len(student_gpa.values()) # We can get the names in isolation. Note that in Python 3.7 and - # above dictionaries are sorted in the order that they were defined - # or inserted + # above, dictionary entries are sorted in the order that they were + # defined or inserted student_names = [] for student in student_gpa.keys(): student_names.append(student) From 2a831e178be65c41430e8430ae3c458a0d21ccd1 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 22 Sep 2020 15:38:25 -0700 Subject: [PATCH 480/553] Update dict.py --- ultimatepython/data_structures/dict.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index bdfd5b82..51e40f06 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -46,11 +46,16 @@ def main(): gpa_binary.append(gpa) assert gpa_binary == [_GPA_MAX, _GPA_MAX, _GPA_MIN, _GPA_MIN] - # To end this lesson, let's remove all the students - for student_name in student_names: - student_gpa.pop(student_name) + # Let's remove all the students + for student in student_names: + student_gpa.pop(student) assert len(student_gpa) == 0 + # Let's add all the students back in + for student, gpa in zip(student_names, gpa_binary): + student_gpa[student] = gpa + assert len(student_gpa) == len(student_names) + if __name__ == "__main__": main() From a9248b81206477e442c78683f87579aa9ee76fbe Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 23 Sep 2020 10:24:26 -0700 Subject: [PATCH 481/553] Update exception_class.py --- ultimatepython/classes/exception_class.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 55e317ea..5c24d715 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -6,9 +6,9 @@ class CustomError(Exception): consumption by downstream services and command-line tools. 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: + it makes little sense to define a custom hierarchy of exceptions. In that + case, we should use the existing hierarchy of builtin exception + classes, which are listed in the Python docs: https://docs.python.org/3/library/exceptions.html """ From 7165e02cd29538e1791259beaec5e346506ad207 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 23 Sep 2020 10:24:59 -0700 Subject: [PATCH 482/553] Update exception_class.py --- ultimatepython/classes/exception_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 5c24d715..f658f0b0 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -8,7 +8,7 @@ class CustomError(Exception): If we designed a standalone application with no downstream consumers, then it makes little sense to define a custom hierarchy of exceptions. In that case, we should use the existing hierarchy of builtin exception - classes, which are listed in the Python docs: + classes which are listed in the Python docs: https://docs.python.org/3/library/exceptions.html """ From b84e6b20a59dd53e8b838734de4ee5500cb9159a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 23 Sep 2020 18:00:37 -0700 Subject: [PATCH 483/553] Update weak_ref.py --- ultimatepython/advanced/weak_ref.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index 3add913b..ccd97371 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -46,8 +46,7 @@ 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 + # Let's create all of the servers and store them properly for app in _CLOUD_APPS: app_servers[app] = set() for component in _CLOUD_APP_COMPONENTS: From 6e55772d62e0a6de8c65d829eb6c98096dbb8edf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 24 Sep 2020 09:25:41 -0700 Subject: [PATCH 484/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 386bf73d..0fd35f9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ proceeding further. The repository consists of documentation and Python modules. Before you contribute to the repository with a pull request, review all the standards -listed in upcoming sections. That way, you can uphold the craftsmanship of +listed in upcoming sections. That way, you can maintain the craftsmanship of this project and still make an impact on the developers using this project for learning purposes. @@ -28,9 +28,11 @@ must be legitimate and meaningful. Specifically: ## Python modules -Every Python module is a standalone lesson which helps developers build their -own intuition for core Python. The primary way to teach these concepts is -through `assert` statements and not `print` statements. +Every Python module is a standalone lesson which helps developers build +their own intuition for core Python. Each module has a name that corresponds +to a topic and explores concepts with `assert` statements. This approach +encourages test-driven development and makes it simple for developers to +discern what the expected output from the code is. Certain Python concepts are skipped in this study guide in pursuit of the statement above. However, it does open the doors for Python to run on any From bc6da8a408b993c4d7b982dac82ed0686d57edb8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 24 Sep 2020 21:53:43 -0700 Subject: [PATCH 485/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fd35f9c..dc47bc2e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,11 +20,11 @@ The [README](README.md) is important because it is the most frequently viewed page in this repository. As such, any changes that are made to this page must be legitimate and meaningful. Specifically: -- All Python modules are referenced in the ToC -- All links point to HTTPS resources that return a `2xx` status -- External Python documentation are useful for newcomers and professionals -- External GitHub repositories have at least 1k stars -- External practice resources have Python exercises +- Python modules are referenced in the ToC +- External links point to HTTPS resources that return a `2xx` status +- Python documentation is useful for newcomers and professionals +- GitHub repositories have at least 1k stars +- Practice resources have Python exercises ## Python modules From 36030cf9dac0ba35383b34629ffce03db4f7e3f9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 24 Sep 2020 22:01:39 -0700 Subject: [PATCH 486/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc47bc2e..24ea4426 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ to a topic and explores concepts with `assert` statements. This approach encourages test-driven development and makes it simple for developers to discern what the expected output from the code is. -Certain Python concepts are skipped in this study guide in pursuit of the -statement above. However, it does open the doors for Python to run on any -computing environment - whether it be an IDE, browser, terminal or -standalone application. +Certain Python concepts are skipped in this study guide because the modules +do not reference each other and make small use of I/O operations. But this +limitation also allows the lessons to be pasted freely to any computing +environment such as an IDE, a browser window or a standalone application. When creating or updating Python modules, please respect the guidelines in the sub-sections below. From 9531b575eda129f24b2727ee229cefbec39553ac Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 24 Sep 2020 22:04:18 -0700 Subject: [PATCH 487/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24ea4426..2efd05ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ The project follows conventions from these PEP proposals: The project has additional conventions: - Module imports are arranged by [isort](https://github.com/timothycrosley/isort) -- Module constants follow a `_UNDER_SCORE_FIRST` convention +- Module constants follow an `_UNDER_SCORE_FIRST` convention - Strings have "double-quotes" unless a `"` exists in the string - Dynamic strings make use of [f-strings](https://www.python.org/dev/peps/pep-0498/) From 02de8c849c1a924f0ce4f1b9c074e95cb97eb72f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 24 Sep 2020 22:09:02 -0700 Subject: [PATCH 488/553] Update dict.py --- ultimatepython/data_structures/dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 51e40f06..0db7911a 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,6 +1,6 @@ # Module-level constants -_GPA_MAX = 4.0 _GPA_MIN = 0.0 +_GPA_MAX = 4.0 def main(): From 4142033a538857574c522efd6d92851c7d9aef4f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 25 Sep 2020 11:10:45 -0700 Subject: [PATCH 489/553] Update isort to 5.5.3 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index ed2d4015..80c3f71c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,7 +28,7 @@ description = "A Python utility / library to sort Python imports." name = "isort" optional = false python-versions = ">=3.6,<4.0" -version = "5.5.2" +version = "5.5.3" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] @@ -106,8 +106,8 @@ flake8 = [ {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, ] isort = [ - {file = "isort-5.5.2-py3-none-any.whl", hash = "sha256:ba91218eee31f1e300ecc079ef0c524cea3fc41bfbb979cbdf5fd3a889e3cfed"}, - {file = "isort-5.5.2.tar.gz", hash = "sha256:171c5f365791073426b5ed3a156c2081a47f88c329161fd28228ff2da4c97ddb"}, + {file = "isort-5.5.3-py3-none-any.whl", hash = "sha256:c16eaa7432a1c004c585d79b12ad080c6c421dd18fe27982ca11f95e6898e432"}, + {file = "isort-5.5.3.tar.gz", hash = "sha256:6187a9f1ce8784cbc6d1b88790a43e6083a6302f03e9ae482acc0f232a98c843"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, From abd1d009493c8f7279da320e043421af491331ca Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 25 Sep 2020 17:09:49 -0700 Subject: [PATCH 490/553] Update runner.py --- runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runner.py b/runner.py index e19fd025..28725116 100644 --- a/runner.py +++ b/runner.py @@ -34,17 +34,17 @@ def main(): continue # By this point, there is a main object in the module - main_func = getattr(mod, _MODULE_MAIN) + mod_main = getattr(mod, _MODULE_MAIN) # The main object is a function - assert isfunction(main_func) + assert isfunction(mod_main) # The main function has zero parameters - assert len(signature(main_func).parameters) == 0 + assert len(signature(mod_main).parameters) == 0 # The main function should not throw any errors print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_MODULE_MAIN}", end="") - main_func() + mod_main() print(" [PASS]") print(success_text(f"Finish {root_name} runner")) From 692eb151419b2a752bf750ad2d1a205edeca9aba Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 25 Sep 2020 17:12:52 -0700 Subject: [PATCH 491/553] Update runner.py --- runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runner.py b/runner.py index 28725116..4761a475 100644 --- a/runner.py +++ b/runner.py @@ -10,7 +10,7 @@ _STYLE_BOLD = "\033[1m" _STYLE_END = "\033[0m" _RUNNER_PROGRESS = "->" -_MODULE_MAIN = "main" +_RUNNER_MATCH = "main" def success_text(text): @@ -30,11 +30,11 @@ def main(): mod = import_module(item.name) # Skip modules without a main object - if not hasattr(mod, _MODULE_MAIN): + if not hasattr(mod, _RUNNER_MATCH): continue # By this point, there is a main object in the module - mod_main = getattr(mod, _MODULE_MAIN) + mod_main = getattr(mod, _RUNNER_MATCH) # The main object is a function assert isfunction(mod_main) @@ -43,7 +43,7 @@ def main(): assert len(signature(mod_main).parameters) == 0 # The main function should not throw any errors - print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_MODULE_MAIN}", end="") + print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_RUNNER_MATCH}", end="") mod_main() print(" [PASS]") From 4a5b2cc04348898d07c6e1931df556156279ad00 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 25 Sep 2020 17:16:06 -0700 Subject: [PATCH 492/553] Update runner.py --- runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runner.py b/runner.py index 4761a475..afcfabb1 100644 --- a/runner.py +++ b/runner.py @@ -10,7 +10,7 @@ _STYLE_BOLD = "\033[1m" _STYLE_END = "\033[0m" _RUNNER_PROGRESS = "->" -_RUNNER_MATCH = "main" +_RUNNER_MAIN = "main" def success_text(text): @@ -30,11 +30,11 @@ def main(): mod = import_module(item.name) # Skip modules without a main object - if not hasattr(mod, _RUNNER_MATCH): + if not hasattr(mod, _RUNNER_MAIN): continue # By this point, there is a main object in the module - mod_main = getattr(mod, _RUNNER_MATCH) + mod_main = getattr(mod, _RUNNER_MAIN) # The main object is a function assert isfunction(mod_main) @@ -43,7 +43,7 @@ def main(): assert len(signature(mod_main).parameters) == 0 # The main function should not throw any errors - print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_RUNNER_MATCH}", end="") + print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_RUNNER_MAIN}", end="") mod_main() print(" [PASS]") From a5ef7eb515b0ba32a7a9829086e38bfa09ad4a76 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 26 Sep 2020 15:46:44 -0700 Subject: [PATCH 493/553] Refine comments in string lesson --- ultimatepython/data_structures/string.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 1dc364f1..68b001c1 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -6,10 +6,10 @@ def main(): # Strings are some of the most robust data structures around content = "Ultimate Python study guide" - # We can compute a string's length just like all other data structures + # We can compute the length of a string just like all other data structures assert len(content) > 0 - # We can use range slices to get substrings from the content + # We can use range slices to get substrings from a string assert content[:8] == "Ultimate" assert content[9:15] == "Python" assert content[::-1] == "ediug yduts nohtyP etamitlU" From 0a92a25ffeb0dfcb5c265acb0af14543d126ec2f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 26 Sep 2020 15:58:47 -0700 Subject: [PATCH 494/553] Refine comments in weak_ref lesson --- ultimatepython/advanced/weak_ref.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index ccd97371..e88e36a3 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -54,10 +54,10 @@ def setup_and_teardown_servers(registry): 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 + # All of these counts are equivalent. This is no surprise since our + # for loop unconditionally creates a server for every permutation of + # apps and components. The loop also adds each server to the registry + # and dictionary unconditionally assert ( registry.server_count == len(_CLOUD_APPS) * len(_CLOUD_APP_COMPONENTS) From 0b8f2e4ff3c17d574af7ccc81fb8cb0ae8605e84 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 28 Sep 2020 11:05:43 -0700 Subject: [PATCH 495/553] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5df2917d..9d7bf540 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ inspiring and highly encouraged if your goal is to become a true [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) Click the badge above to spin up a working environment in the browser without -needing Git and Python installed on a local machine. If these requirements +needing Git and Python installed on your local machine. If these requirements are already met, feel free to clone the repository directly. Once the repository is accessible, you are ready to learn from the standalone From 51a11744df46dfb0b08531d4728a5db52e1ce6ae Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 29 Sep 2020 22:09:56 -0700 Subject: [PATCH 496/553] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2efd05ba..5e172da5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ proceeding further. --- The repository consists of documentation and Python modules. Before you -contribute to the repository with a pull request, review all the standards +contribute to the repository with a pull request, review all of the standards listed in upcoming sections. That way, you can maintain the craftsmanship of this project and still make an impact on the developers using this project for learning purposes. From 939212462e867265c9f3861e42c4410d0478a58d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 1 Oct 2020 08:57:09 -0700 Subject: [PATCH 497/553] Update conditional.py --- ultimatepython/syntax/conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index a6112962..5ef8f45b 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -15,8 +15,8 @@ def main(): assert ran_2 is True # 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 + # fails. Notice that one line gets skipped and this conditional does not + # help us make a conclusion on the variable's true value if x_add_two == 1: ran_3 = False # skip: if else: From 4901107ebdbcf1aba17730551757f662be22dfdb Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 1 Oct 2020 08:59:51 -0700 Subject: [PATCH 498/553] Update loop.py --- 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 fe24b916..a547427e 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -32,7 +32,7 @@ def main(): # The `while` loop terminated at this value assert i == 8 - # This is a `while` loop that is stopped with `break` and its counter is + # This is a `while` loop that stops with `break` and its counter is # 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 From 0a284f2d7cccd36ca2ca49762ebb7a043cf95d6e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 1 Oct 2020 18:41:56 -0700 Subject: [PATCH 499/553] Update list.py --- ultimatepython/data_structures/list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 9483df33..75a32efd 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -31,7 +31,8 @@ def main(): # Note that a list is ordered and mutable. If we want to reverse the order # of the `numbers` list, we can start at index 0 and end halfway. At each - # cycle, we swap values in the front with values in the back + # step of the `for` loop, we swap a value from the first half of the list + # with a value from the second half of the list for ix_front in range(len(numbers) // 2): ix_back = len(numbers) - ix_front - 1 numbers[ix_front], numbers[ix_back] = numbers[ix_back], numbers[ix_front] From b300f1f699636088c42795ab1cf855ce8c8f311e Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 2 Oct 2020 08:24:20 -0700 Subject: [PATCH 500/553] Update mocking.py --- ultimatepython/advanced/mocking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index b01379b6..a7a03183 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -17,7 +17,7 @@ class AppServer: Normally we don't mock an application server because it is the runtime environment (AKA central nervous system) for business logic, database endpoints, network sockets and more. However, this server definition - is lightweight, so it's acceptable to mock this. + is lightweight, so it's okay to mock this. """ def __init__(self, host, port, proto): From 6fffc54656403b85b5f0f7cab079f18292ecc232 Mon Sep 17 00:00:00 2001 From: miguelforsetti Date: Sat, 3 Oct 2020 22:59:58 +0700 Subject: [PATCH 501/553] Integer infos (#12) * Add info on integer bases and integer literals. * Words are hard. * Bad grammar. --- ultimatepython/syntax/variable.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index 1d95fe81..275cd4ed 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -28,6 +28,18 @@ def main(): assert isinstance(c, object) and isinstance(c_type, object) assert isinstance(d, object) and isinstance(d_type, object) + # We can represent integer literals in Python using 4 bases: decimal, + # hexadecimal, octal, and binary. + # Decimal literals do not require any prefix, while other bases require + # prefixes: `0x` for hexadecimal, `0o` for octal, and `0b` for binary. + assert 100 == 0x64 == 0o144 == 0b1100100 + + # We can use underscores (literal `_`) to separate digit groups in integer + # literals. + assert 10_000 == 10000 + assert 0x01_0f_2c == 69_420 + assert 3.456_290e-1 == 0.3_456_290 + if __name__ == "__main__": main() From 9980fb97e0dd12e963d34406ed911eeb5a77d1be Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 09:04:19 -0700 Subject: [PATCH 502/553] Fix code to match pep8 standards --- 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 275cd4ed..ba05f8c4 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -36,8 +36,8 @@ def main(): # We can use underscores (literal `_`) to separate digit groups in integer # literals. - assert 10_000 == 10000 - assert 0x01_0f_2c == 69_420 + assert 10_000 == 10000 + assert 0x01_0f_2c == 69_420 assert 3.456_290e-1 == 0.3_456_290 From 0675c93f14feec5fe6b2c6c817d878bd9aaa10fb Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 09:12:59 -0700 Subject: [PATCH 503/553] Apply formatting to variable lesson --- ultimatepython/syntax/variable.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index ba05f8c4..bb8ee2db 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -29,13 +29,15 @@ def main(): assert isinstance(d, object) and isinstance(d_type, object) # We can represent integer literals in Python using 4 bases: decimal, - # hexadecimal, octal, and binary. - # Decimal literals do not require any prefix, while other bases require - # prefixes: `0x` for hexadecimal, `0o` for octal, and `0b` for binary. + # hexadecimal, octal, and binary. Decimal literals do not require any + # prefix, while other bases require prefixes: + # - `0x` for hexadecimal + # - `0o` for octal + # - `0b` for binary assert 100 == 0x64 == 0o144 == 0b1100100 - # We can use underscores (literal `_`) to separate digit groups in integer - # literals. + # We can use underscores (literal `_`) to separate digit groups in + # integer literals. assert 10_000 == 10000 assert 0x01_0f_2c == 69_420 assert 3.456_290e-1 == 0.3_456_290 From 70b522d5ed769eebcb3a975f59f6c9148671784d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 09:14:03 -0700 Subject: [PATCH 504/553] Remove trailing period from variable lesson --- 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 bb8ee2db..280d9de3 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -37,7 +37,7 @@ def main(): assert 100 == 0x64 == 0o144 == 0b1100100 # We can use underscores (literal `_`) to separate digit groups in - # integer literals. + # integer literals assert 10_000 == 10000 assert 0x01_0f_2c == 69_420 assert 3.456_290e-1 == 0.3_456_290 From 5cc82e00b8ce25971c0b1ee5336d572ff8a69702 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 10:48:07 -0700 Subject: [PATCH 505/553] Update poetry dependencies --- poetry.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/poetry.lock b/poetry.lock index 80c3f71c..baa37cd0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,21 +1,21 @@ [[package]] -category = "main" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3" +description = "Code coverage measurement for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.3" [package.extras] toml = ["toml"] [[package]] -category = "main" -description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.3" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" @@ -23,46 +23,46 @@ pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" [[package]] -category = "main" -description = "A Python utility / library to sort Python imports." name = "isort" +version = "5.5.4" +description = "A Python utility / library to sort Python imports." +category = "main" optional = false python-versions = ">=3.6,<4.0" -version = "5.5.3" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] -category = "main" -description = "McCabe checker, plugin for flake8" name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "main" optional = false python-versions = "*" -version = "0.6.1" [[package]] -category = "main" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "main" -description = "passive checker of Python programs" name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" [metadata] -content-hash = "7f7548ae5cf1510fdcf2450b8a713bd869e075e7fe9f1a8eef0292bf2257115d" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.8" +content-hash = "7f7548ae5cf1510fdcf2450b8a713bd869e075e7fe9f1a8eef0292bf2257115d" [metadata.files] coverage = [ @@ -102,12 +102,12 @@ coverage = [ {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] flake8 = [ - {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, - {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] isort = [ - {file = "isort-5.5.3-py3-none-any.whl", hash = "sha256:c16eaa7432a1c004c585d79b12ad080c6c421dd18fe27982ca11f95e6898e432"}, - {file = "isort-5.5.3.tar.gz", hash = "sha256:6187a9f1ce8784cbc6d1b88790a43e6083a6302f03e9ae482acc0f232a98c843"}, + {file = "isort-5.5.4-py3-none-any.whl", hash = "sha256:36f0c6659b9000597e92618d05b72d4181104cf59472b1c6a039e3783f930c95"}, + {file = "isort-5.5.4.tar.gz", hash = "sha256:ba040c24d20aa302f78f4747df549573ae1eaf8e1084269199154da9c483f07f"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, From ee9935491a78b631f20a6f64c835085a30c85335 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 10:54:23 -0700 Subject: [PATCH 506/553] Update variable.py --- 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 280d9de3..dc005c57 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -30,7 +30,7 @@ def main(): # We can represent integer literals in Python using 4 bases: decimal, # hexadecimal, octal, and binary. Decimal literals do not require any - # prefix, while other bases require prefixes: + # prefix while other bases require prefixes: # - `0x` for hexadecimal # - `0o` for octal # - `0b` for binary From 904e6102dc0635437bd5ce34fff0efd4fbda0340 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 14:18:40 -0700 Subject: [PATCH 507/553] Update CircleCI Python orb (#13) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 962c8733..17436580 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: circleci/python@1.0.0 + python: circleci/python@1.1.0 codecov: codecov/codecov@1.1.1 jobs: From 084504953b045b252d66c15f9246d750dd0167f4 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 15:19:18 -0700 Subject: [PATCH 508/553] Revisit CircleCI config (#14) --- .circleci/config.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 17436580..de1b6aea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,25 +9,14 @@ jobs: executor: python/default steps: - checkout - - restore_cache: - keys: - - deps-{{ .Branch }}-{{ checksum "poetry.lock" }} - - run: - command: poetry install --no-dev - name: Install dependencies - - save_cache: - key: deps-{{ .Branch }}-{{ checksum "poetry.lock" }} - paths: - - /home/circleci/.cache/pypoetry/virtualenvs + - python/install-packages: + pkg-manager: poetry - run: command: poetry run flake8 name: Lint - run: command: poetry run coverage run -m runner name: Test - - run: - command: poetry run coverage report - name: Report - run: command: | mkdir -p /tmp/coverage/custom @@ -38,7 +27,6 @@ jobs: - codecov/upload: file: /tmp/coverage/results.xml - workflows: main: jobs: From bdb721b792f76bb0630288c9d708c41b72f2f4e3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 16:07:59 -0700 Subject: [PATCH 509/553] Shorten circleci config --- .circleci/config.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index de1b6aea..15ae3ad6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,14 +18,12 @@ jobs: command: poetry run coverage run -m runner name: Test - run: - command: | - mkdir -p /tmp/coverage/custom - poetry run coverage xml -o /tmp/coverage/results.xml + command: poetry run coverage xml -o /tmp/coverage.xml name: Prepare coverage results - store_artifacts: - path: /tmp/coverage/results.xml + path: /tmp/coverage.xml - codecov/upload: - file: /tmp/coverage/results.xml + file: /tmp/coverage.xml workflows: main: From fab6284372177be8e3f1a5b67d34eb9de6ce60b8 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 3 Oct 2020 16:14:01 -0700 Subject: [PATCH 510/553] Fix semantics 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 7847c548..c3fe49d8 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -37,7 +37,7 @@ async def start_job(delay, job_id): async def schedule_jobs(): """Schedule jobs concurrently.""" - # Create a job which also represents a coroutine + # Start a job which also represents a coroutine single_job = start_job(_MILLISECOND, uuid4().hex) assert asyncio.iscoroutine(single_job) From 8826ed892059e8ab240e4c7122b162b1f4d701af Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 6 Oct 2020 22:03:46 -0700 Subject: [PATCH 511/553] Simplify wording in contrib docs --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e172da5..50863cb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,8 @@ for learning purposes. ## README documentation The [README](README.md) is important because it is the most frequently viewed -page in this repository. As such, any changes that are made to this page -must be legitimate and meaningful. Specifically: +page in this repository. As such, any changes that are made to this page must +follow these guidelines: - Python modules are referenced in the ToC - External links point to HTTPS resources that return a `2xx` status From 258dc9dd3500800d411ff234785d84a5f9391cb9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 7 Oct 2020 09:12:24 -0700 Subject: [PATCH 512/553] Refine content in context_manager lesson --- ultimatepython/advanced/context_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index f7909c2c..fb71af06 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -1,11 +1,11 @@ from contextlib import contextmanager from io import StringIO -# Module-level constants +# Simple directory with file contents _FILESYSTEM = { "a.txt": "Hello World", "b.xml": "Hello World", - "c.out": "10101010" + "c.out": "10101010", } From 8c1c642160afc6aae252c6e10daf23aaf37b4ba7 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 7 Oct 2020 09:14:51 -0700 Subject: [PATCH 513/553] Avoid reserved names in context_manager lesson --- ultimatepython/advanced/context_manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index fb71af06..1db4a233 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -17,13 +17,13 @@ def file(filename): are useful for resources that need to be opened and closed such as files, database connections and sockets. """ - buffer = StringIO(_FILESYSTEM[filename]) + io_buffer = StringIO(_FILESYSTEM[filename]) try: # Pass the buffer to the context block - yield buffer + yield io_buffer finally: # Close the buffer unconditionally - buffer.close() + io_buffer.close() class FileHandler: @@ -35,15 +35,15 @@ class FileHandler: """ def __init__(self, filename): - self.buffer = StringIO(_FILESYSTEM[filename]) + self.io_buffer = StringIO(_FILESYSTEM[filename]) def __enter__(self): """Pass the buffer to the context block.""" - return self.buffer + return self.io_buffer def __exit__(self, *args): """Close the buffer unconditionally.""" - self.buffer.close() + self.io_buffer.close() def main(): From 6e01c5174aa95376ae339e8d89da6eb4aa5aa015 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 7 Oct 2020 09:16:22 -0700 Subject: [PATCH 514/553] Remove extra code in context_manager lesson --- ultimatepython/advanced/context_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index 1db4a233..12a0cd5c 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -62,7 +62,7 @@ def main(): # Whenever any error happens in the context block, the buffer # in the context manager gets closed automatically and the # error gets raised to the outer block - with obj("c.out") as _: + with obj("c.out"): raise RuntimeError("System crash. Abort!") except RuntimeError: call_failed = True From b9effddb777a90903809f18814cb961de517315d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 7 Oct 2020 09:22:48 -0700 Subject: [PATCH 515/553] Add assertion to dict lesson --- ultimatepython/data_structures/dict.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 0db7911a..666cb08b 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -24,6 +24,11 @@ def main(): student_names.append(student) assert student_names == ["john", "jane", "bob", "mary"] + # We can check that `student_gpa` has the names that were stored + # in `student_names` from the loop above + for student in student_names: + assert student in student_gpa + # We can get the GPAs in isolation gpa_values = [] for gpa in student_gpa.values(): From 7c561ca97d9ce22fd0b98b504a97c21d5b2f4aa3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 10 Oct 2020 16:28:54 -0700 Subject: [PATCH 516/553] Add threads lesson (#15) --- README.md | 1 + ultimatepython/advanced/threads.py | 59 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 ultimatepython/advanced/threads.py diff --git a/README.md b/README.md index 9d7bf540..2dd861ec 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ There are two ways of running the modules: - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) - Regular expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) - Data format: [json | xml | csv](ultimatepython/advanced/data_format.py) (:exploding_head:) + - Threads: [concurrent.futures](ultimatepython/advanced/threads.py) (:exploding_head:) ## Additional resources diff --git a/ultimatepython/advanced/threads.py b/ultimatepython/advanced/threads.py new file mode 100644 index 00000000..a5189e33 --- /dev/null +++ b/ultimatepython/advanced/threads.py @@ -0,0 +1,59 @@ +import time +from concurrent import futures +from datetime import datetime + +# Module-level constants +_MULTIPLY_DELAY = 0.001 + + +def multiply_by_two(item): + """This multiplication has a small delay.""" + time.sleep(_MULTIPLY_DELAY) + return item * 2 + + +def run_workers(work, data): + results = set() + + # We can use a with statement to ensure threads are cleaned up promptly + with futures.ThreadPoolExecutor() as executor: + future_iterable = (executor.submit(work, item) for item in data) + + # Note that future outcomes are returned out of order + for future in futures.as_completed(future_iterable): + results.add(future.result()) + + return results + + +def main(): + original_data = {1, 2, 3, 4} + expected_data = {(item * 2) for item in original_data} + + # Let's get the data using the simple approach + simple_start = datetime.now() + simple_data = {multiply_by_two(item) for item in original_data} + simple_duration = datetime.now() - simple_start + + # The simple approach has the expected data + assert simple_data == expected_data + + # Let's get the data using the threaded approach + thread_start = datetime.now() + thread_data = run_workers(multiply_by_two, original_data) + thread_duration = datetime.now() - thread_start + + # The threaded approach has the expected data + assert thread_data == expected_data + + # But the threaded approach is faster than the simple approach because + # threads are a form of cooperative multitasking where a blocking call + # like time.sleep forces the current thread to yield its control over + # to a waiting thread to start running. That means the threaded approach + # can run multiple blocking operations at a time whereas the simple + # approach can only run one blocking operation at a time + assert thread_duration < simple_duration + + +if __name__ == '__main__': + main() From 9e58ae11bb34b86ee1007f7b7cda61539304835d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 10 Oct 2020 16:46:44 -0700 Subject: [PATCH 517/553] Reorder advanced section of ToC (#16) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2dd861ec..554c2494 100644 --- a/README.md +++ b/README.md @@ -92,16 +92,16 @@ There are two ways of running the modules: - Iterator class: [Iterator definition | yield](ultimatepython/classes/iterator_class.py) (:exploding_head:) 5. **Advanced** - Decorator: [Decorator definition | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) - - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - Threads: [concurrent.futures](ultimatepython/advanced/threads.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:) - - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Mocking: [MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) - Regular expression: [search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) - Data format: [json | xml | csv](ultimatepython/advanced/data_format.py) (:exploding_head:) - - Threads: [concurrent.futures](ultimatepython/advanced/threads.py) (:exploding_head:) ## Additional resources From 027361f22fa4c92466bf47d57bb92091b42af1d5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 10 Oct 2020 22:57:15 -0700 Subject: [PATCH 518/553] Refactor threads lesson to explore parallelism (#17) --- README.md | 2 +- ultimatepython/advanced/threads.py | 37 +++++++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 554c2494..9589bcb0 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ There are two ways of running the modules: - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - - Threads: [concurrent.futures](ultimatepython/advanced/threads.py) (:exploding_head:) + - Threads: [ThreadPoolExecutor](ultimatepython/advanced/threads.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:) diff --git a/ultimatepython/advanced/threads.py b/ultimatepython/advanced/threads.py index a5189e33..77c34661 100644 --- a/ultimatepython/advanced/threads.py +++ b/ultimatepython/advanced/threads.py @@ -1,5 +1,5 @@ import time -from concurrent import futures +from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime # Module-level constants @@ -12,22 +12,22 @@ def multiply_by_two(item): return item * 2 -def run_workers(work, data): +def run_thread_workers(work, data): + """Run thread workers that invoke work on each data element.""" results = set() - # We can use a with statement to ensure threads are cleaned up promptly - with futures.ThreadPoolExecutor() as executor: - future_iterable = (executor.submit(work, item) for item in data) - - # Note that future outcomes are returned out of order - for future in futures.as_completed(future_iterable): + # We can use a with statement to ensure workers are cleaned up promptly + with ThreadPoolExecutor() as executor: + # Note that results are returned out of order + work_queue = (executor.submit(work, item) for item in data) + for future in as_completed(work_queue): results.add(future.result()) return results def main(): - original_data = {1, 2, 3, 4} + original_data = {num for num in range(100)} expected_data = {(item * 2) for item in original_data} # Let's get the data using the simple approach @@ -40,20 +40,25 @@ def main(): # Let's get the data using the threaded approach thread_start = datetime.now() - thread_data = run_workers(multiply_by_two, original_data) + thread_data = run_thread_workers(multiply_by_two, original_data) thread_duration = datetime.now() - thread_start # The threaded approach has the expected data assert thread_data == expected_data - # But the threaded approach is faster than the simple approach because - # threads are a form of cooperative multitasking where a blocking call - # like time.sleep forces the current thread to yield its control over - # to a waiting thread to start running. That means the threaded approach - # can run multiple blocking operations at a time whereas the simple - # approach can only run one blocking operation at a time + # The threaded approach is faster than the simple approach in this case + # because a blocking I/O call like time.sleep forces the current thread + # to yield its control over to a waiting thread to start running. That + # means the threaded approach can run blocking operations in parallel. + # The cost of creating threads is somewhat cheap which means we often + # create more than one thread to speed up I/O-heavy workloads assert thread_duration < simple_duration + # However, Python threads are not suitable for CPU-heavy tasks in the + # CPython interpreter due to the GIL. To address this, we typically + # resort to forking processes or running C externally. Both approaches + # have their own pros and cons + if __name__ == '__main__': main() From e0994db28cd1dc7aaf287ad7090231b6aeb911fe Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 06:31:04 -0700 Subject: [PATCH 519/553] Add mixin lesson (#18) --- README.md | 1 + ultimatepython/advanced/mixins.py | 95 +++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 ultimatepython/advanced/mixins.py diff --git a/README.md b/README.md index 9589bcb0..572a2aba 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ There are two ways of running the modules: - Decorator: [Decorator definition | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Mixins: [Mixin definition](ultimatepython/advanced/mixins.py) (:exploding_head:) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - Threads: [ThreadPoolExecutor](ultimatepython/advanced/threads.py) (:exploding_head:) - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) diff --git a/ultimatepython/advanced/mixins.py b/ultimatepython/advanced/mixins.py new file mode 100644 index 00000000..d0891c2f --- /dev/null +++ b/ultimatepython/advanced/mixins.py @@ -0,0 +1,95 @@ +from abc import ABC, abstractmethod + + +class Request: + """Request model.""" + def __init__(self, url, user): + self.url = url + self.user = user + + +class RequestHandler(ABC): + """Request handler interface.""" + @abstractmethod + def handle(self, request): + raise NotImplementedError + + +class TemplateHandlerMixin(RequestHandler): + """Abstract template mixin for RequestHandler.""" + template_suffix = ".template" + failure_content = "

Not found

" + + def handle(self, request): + template_name = self.get_template_name(request.url) + if self.is_valid_template(template_name): + return self.render_template(template_name) + return self.failure_content + + @abstractmethod + def get_template_name(self, request_url): + raise NotImplementedError + + @abstractmethod + def is_valid_template(self, template_name): + return template_name.endswith(self.template_suffix) + + @abstractmethod + def render_template(self, template_name): + raise NotImplementedError + + +class AuthHandlerMixin(RequestHandler): + """Abstract auth mixin for RequestHandler.""" + success_content = "

Something private

" + failure_content = "

Access denied

" + + def handle(self, request): + if self.is_valid_user(request.user): + return self.success_content + return self.failure_content + + @abstractmethod + def is_valid_user(self, request_user): + raise NotImplementedError + + +class TemplateFolderHandler(TemplateHandlerMixin): + """Concrete template handler.""" + def __init__(self, template_dir): + self.template_dir = template_dir + + def get_template_name(self, request_url): + return request_url[1:] + + def is_valid_template(self, template_name): + return (super().is_valid_template(template_name) + and template_name in self.template_dir) + + def render_template(self, template_name): + return self.template_dir[template_name] + + +class AdminHandler(AuthHandlerMixin): + """Concrete auth handler.""" + success_content = "

Admin stuff

" + + def __init__(self, admin_users): + self.admin_users = admin_users + + def is_valid_user(self, request_user): + return request_user in self.admin_users + + +def main(): + welcome_handler = TemplateFolderHandler({"welcome.template": "

Hello world

"}) + assert welcome_handler.handle(Request("/welcome.template", "nobody")) == "

Hello world

" + assert welcome_handler.handle(Request("/foo.bar", "nobody")) == "

Not found

" + + admin_handler = AdminHandler({"john", "jane"}) + assert admin_handler.handle(Request("/admin.html", "john")) == "

Admin stuff

" + assert admin_handler.handle(Request("/admin.html", "nobody")) == "

Access denied

" + + +if __name__ == '__main__': + main() From 69f951407041ce0e63b5c853484bd4ff7dc8899f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 13:59:45 -0700 Subject: [PATCH 520/553] Improve mixins lesson (#19) --- ultimatepython/advanced/mixins.py | 48 ++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/ultimatepython/advanced/mixins.py b/ultimatepython/advanced/mixins.py index d0891c2f..6cdbf999 100644 --- a/ultimatepython/advanced/mixins.py +++ b/ultimatepython/advanced/mixins.py @@ -30,7 +30,6 @@ def handle(self, request): def get_template_name(self, request_url): raise NotImplementedError - @abstractmethod def is_valid_template(self, template_name): return template_name.endswith(self.template_suffix) @@ -41,13 +40,15 @@ def render_template(self, template_name): class AuthHandlerMixin(RequestHandler): """Abstract auth mixin for RequestHandler.""" - success_content = "

Something private

" - failure_content = "

Access denied

" def handle(self, request): - if self.is_valid_user(request.user): - return self.success_content - return self.failure_content + if not self.is_valid_user(request.user): + return self.get_failure_content(request) + return super().handle(request) + + @staticmethod + def get_failure_content(request): + return f"

Access denied for {request.url}

" @abstractmethod def is_valid_user(self, request_user): @@ -70,11 +71,11 @@ def render_template(self, template_name): return self.template_dir[template_name] -class AdminHandler(AuthHandlerMixin): +class AdminHandler(AuthHandlerMixin, TemplateFolderHandler): """Concrete auth handler.""" - success_content = "

Admin stuff

" - def __init__(self, admin_users): + def __init__(self, admin_users, template_dir): + super().__init__(template_dir) self.admin_users = admin_users def is_valid_user(self, request_user): @@ -82,13 +83,28 @@ def is_valid_user(self, request_user): def main(): - welcome_handler = TemplateFolderHandler({"welcome.template": "

Hello world

"}) - assert welcome_handler.handle(Request("/welcome.template", "nobody")) == "

Hello world

" - assert welcome_handler.handle(Request("/foo.bar", "nobody")) == "

Not found

" - - admin_handler = AdminHandler({"john", "jane"}) - assert admin_handler.handle(Request("/admin.html", "john")) == "

Admin stuff

" - assert admin_handler.handle(Request("/admin.html", "nobody")) == "

Access denied

" + # Attempt requests with unauthenticated template handler + simple_dir = {"welcome.template": "

Hello world

", + "about.template": "

About me

"} + welcome_from_nobody = Request("/welcome.template", "nobody") + about_from_nobody = Request("/about.template", "nobody") + foo_from_nobody = Request("/foo.bar", "nobody") + simple_handler = TemplateFolderHandler(simple_dir) + assert simple_handler.handle(welcome_from_nobody) == "

Hello world

" + assert simple_handler.handle(about_from_nobody) == "

About me

" + assert simple_handler.handle(foo_from_nobody) == "

Not found

" + + # Attempt requests with authenticated template handler + admin_users = {"john", "jane"} + admin_dir = {"fqdn.template": "

server.example.com

", + "salary.template": "

123456789.00

"} + fqdn_from_john = Request("/fqdn.template", "john") + salary_from_jane = Request("/salary.template", "jane") + salary_from_nobody = Request("/salary.template", "nobody") + admin_handler = AdminHandler(admin_users, admin_dir) + assert admin_handler.handle(fqdn_from_john) == "

server.example.com

" + assert admin_handler.handle(salary_from_jane) == "

123456789.00

" + assert admin_handler.handle(salary_from_nobody) == "

Access denied for /salary.template

" if __name__ == '__main__': From ec941c71a2955450a233d4e75fa1db4e72c57aea Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 14:01:07 -0700 Subject: [PATCH 521/553] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 572a2aba..b291f080 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ There are two ways of running the modules: - Decorator: [Decorator definition | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - - Mixins: [Mixin definition](ultimatepython/advanced/mixins.py) (:exploding_head:) + - Mixin: [Mixin definition](ultimatepython/advanced/mixins.py) (:exploding_head:) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - Threads: [ThreadPoolExecutor](ultimatepython/advanced/threads.py) (:exploding_head:) - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) From 426ebcc5cbfd40e17fc5f497cf91c88f1f181aa3 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 17:28:28 -0700 Subject: [PATCH 522/553] Normalize APIs in mixin lesson --- README.md | 2 +- .../advanced/{mixins.py => mixin.py} | 35 +++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) rename ultimatepython/advanced/{mixins.py => mixin.py} (77%) diff --git a/README.md b/README.md index b291f080..aae68e14 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ There are two ways of running the modules: - Decorator: [Decorator definition | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) - Context manager: [Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - - Mixin: [Mixin definition](ultimatepython/advanced/mixins.py) (:exploding_head:) + - Mixin: [Mixin definition](ultimatepython/advanced/mixin.py) (:exploding_head:) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - Threads: [ThreadPoolExecutor](ultimatepython/advanced/threads.py) (:exploding_head:) - Asyncio: [async | await](ultimatepython/advanced/async.py) (:exploding_head:) diff --git a/ultimatepython/advanced/mixins.py b/ultimatepython/advanced/mixin.py similarity index 77% rename from ultimatepython/advanced/mixins.py rename to ultimatepython/advanced/mixin.py index 6cdbf999..252ba3b6 100644 --- a/ultimatepython/advanced/mixins.py +++ b/ultimatepython/advanced/mixin.py @@ -18,13 +18,12 @@ def handle(self, request): class TemplateHandlerMixin(RequestHandler): """Abstract template mixin for RequestHandler.""" template_suffix = ".template" - failure_content = "

Not found

" def handle(self, request): template_name = self.get_template_name(request.url) - if self.is_valid_template(template_name): - return self.render_template(template_name) - return self.failure_content + if not self.is_valid_template(template_name): + return self.handle_invalid_template(request) + return self.render_template(template_name) @abstractmethod def get_template_name(self, request_url): @@ -33,6 +32,10 @@ def get_template_name(self, request_url): def is_valid_template(self, template_name): return template_name.endswith(self.template_suffix) + @staticmethod + def handle_invalid_template(request): + return f"

Invalid entry for {request.url}

" + @abstractmethod def render_template(self, template_name): raise NotImplementedError @@ -43,17 +46,17 @@ class AuthHandlerMixin(RequestHandler): def handle(self, request): if not self.is_valid_user(request.user): - return self.get_failure_content(request) + return self.handle_invalid_user(request) return super().handle(request) - @staticmethod - def get_failure_content(request): - return f"

Access denied for {request.url}

" - @abstractmethod def is_valid_user(self, request_user): raise NotImplementedError + @staticmethod + def handle_invalid_user(request): + return f"

Access denied for {request.url}

" + class TemplateFolderHandler(TemplateHandlerMixin): """Concrete template handler.""" @@ -71,8 +74,8 @@ def render_template(self, template_name): return self.template_dir[template_name] -class AdminHandler(AuthHandlerMixin, TemplateFolderHandler): - """Concrete auth handler.""" +class AdminTemplateHandler(AuthHandlerMixin, TemplateFolderHandler): + """Concrete auth and template handler.""" def __init__(self, admin_users, template_dir): super().__init__(template_dir) @@ -83,7 +86,7 @@ def is_valid_user(self, request_user): def main(): - # Attempt requests with unauthenticated template handler + # Attempt requests with simple template handler simple_dir = {"welcome.template": "

Hello world

", "about.template": "

About me

"} welcome_from_nobody = Request("/welcome.template", "nobody") @@ -92,19 +95,21 @@ def main(): simple_handler = TemplateFolderHandler(simple_dir) assert simple_handler.handle(welcome_from_nobody) == "

Hello world

" assert simple_handler.handle(about_from_nobody) == "

About me

" - assert simple_handler.handle(foo_from_nobody) == "

Not found

" + assert simple_handler.handle(foo_from_nobody) == "

Invalid entry for /foo.bar

" - # Attempt requests with authenticated template handler + # Attempt requests with admin template handler admin_users = {"john", "jane"} admin_dir = {"fqdn.template": "

server.example.com

", "salary.template": "

123456789.00

"} fqdn_from_john = Request("/fqdn.template", "john") salary_from_jane = Request("/salary.template", "jane") salary_from_nobody = Request("/salary.template", "nobody") - admin_handler = AdminHandler(admin_users, admin_dir) + foo_from_john = Request("/foo.bar", "john") + admin_handler = AdminTemplateHandler(admin_users, admin_dir) assert admin_handler.handle(fqdn_from_john) == "

server.example.com

" assert admin_handler.handle(salary_from_jane) == "

123456789.00

" assert admin_handler.handle(salary_from_nobody) == "

Access denied for /salary.template

" + assert admin_handler.handle(foo_from_john) == "

Invalid entry for /foo.bar

" if __name__ == '__main__': From aa79ad7439561727816658c8c842ebde4d7fd731 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 17:36:55 -0700 Subject: [PATCH 523/553] Add max_workers param to threads lesson --- ultimatepython/advanced/threads.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ultimatepython/advanced/threads.py b/ultimatepython/advanced/threads.py index 77c34661..8bead763 100644 --- a/ultimatepython/advanced/threads.py +++ b/ultimatepython/advanced/threads.py @@ -12,12 +12,12 @@ def multiply_by_two(item): return item * 2 -def run_thread_workers(work, data): +def run_thread_workers(work, data, max_workers=10): """Run thread workers that invoke work on each data element.""" results = set() # We can use a with statement to ensure workers are cleaned up promptly - with ThreadPoolExecutor() as executor: + with ThreadPoolExecutor(max_workers=max_workers) as executor: # Note that results are returned out of order work_queue = (executor.submit(work, item) for item in data) for future in as_completed(work_queue): @@ -27,7 +27,7 @@ def run_thread_workers(work, data): def main(): - original_data = {num for num in range(100)} + original_data = {num for num in range(50)} expected_data = {(item * 2) for item in original_data} # Let's get the data using the simple approach From 94ade19aa9c050d58408cc3c10c1921845631439 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 17:46:06 -0700 Subject: [PATCH 524/553] Reformat mixin lesson --- ultimatepython/advanced/mixin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index 252ba3b6..894f8a11 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -3,6 +3,7 @@ class Request: """Request model.""" + def __init__(self, url, user): self.url = url self.user = user @@ -10,6 +11,7 @@ def __init__(self, url, user): class RequestHandler(ABC): """Request handler interface.""" + @abstractmethod def handle(self, request): raise NotImplementedError @@ -60,6 +62,7 @@ def handle_invalid_user(request): class TemplateFolderHandler(TemplateHandlerMixin): """Concrete template handler.""" + def __init__(self, template_dir): self.template_dir = template_dir From e66d02d564d46d8ba04eb131c5dd1868c4ac3363 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 17:48:58 -0700 Subject: [PATCH 525/553] Refine comments in mixin lesson --- ultimatepython/advanced/mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index 894f8a11..fa04c04f 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -89,7 +89,7 @@ def is_valid_user(self, request_user): def main(): - # Attempt requests with simple template handler + # Handle requests with simple template handler simple_dir = {"welcome.template": "

Hello world

", "about.template": "

About me

"} welcome_from_nobody = Request("/welcome.template", "nobody") @@ -100,7 +100,7 @@ def main(): assert simple_handler.handle(about_from_nobody) == "

About me

" assert simple_handler.handle(foo_from_nobody) == "

Invalid entry for /foo.bar

" - # Attempt requests with admin template handler + # Handle requests with admin template handler admin_users = {"john", "jane"} admin_dir = {"fqdn.template": "

server.example.com

", "salary.template": "

123456789.00

"} From b532c6896cf7bf20d5878fbc3a658cd49204faaf Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 17:58:15 -0700 Subject: [PATCH 526/553] Update isort to 5.6.3 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index baa37cd0..18b7d83e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,7 +24,7 @@ pyflakes = ">=2.2.0,<2.3.0" [[package]] name = "isort" -version = "5.5.4" +version = "5.6.3" description = "A Python utility / library to sort Python imports." category = "main" optional = false @@ -106,8 +106,8 @@ flake8 = [ {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] isort = [ - {file = "isort-5.5.4-py3-none-any.whl", hash = "sha256:36f0c6659b9000597e92618d05b72d4181104cf59472b1c6a039e3783f930c95"}, - {file = "isort-5.5.4.tar.gz", hash = "sha256:ba040c24d20aa302f78f4747df549573ae1eaf8e1084269199154da9c483f07f"}, + {file = "isort-5.6.3-py3-none-any.whl", hash = "sha256:2c1d044a96c74367ab12188593d134c596747d97aec43b9cdb12eea83b6889d2"}, + {file = "isort-5.6.3.tar.gz", hash = "sha256:3820dd92c3214290cda6351f2ae2cedd5170759bc434af600eaad4f7a82a6ade"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, From acb55d2a252d4259d63cbaeafb23d4a56810a93f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 18:39:52 -0700 Subject: [PATCH 527/553] Rename threads lesson to thread --- README.md | 2 +- ultimatepython/advanced/{threads.py => thread.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename ultimatepython/advanced/{threads.py => thread.py} (100%) diff --git a/README.md b/README.md index aae68e14..48bb202c 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ There are two ways of running the modules: - Method resolution order: [mro](ultimatepython/advanced/mro.py) (:exploding_head:) - Mixin: [Mixin definition](ultimatepython/advanced/mixin.py) (:exploding_head:) - Metaclass: [Metaclass definition](ultimatepython/advanced/meta_class.py) (:exploding_head:) - - Threads: [ThreadPoolExecutor](ultimatepython/advanced/threads.py) (:exploding_head:) + - Thread: [ThreadPoolExecutor](ultimatepython/advanced/thread.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:) diff --git a/ultimatepython/advanced/threads.py b/ultimatepython/advanced/thread.py similarity index 100% rename from ultimatepython/advanced/threads.py rename to ultimatepython/advanced/thread.py From e005fdbd6ead9015da359d5f4332790cb117d850 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 21:47:11 -0700 Subject: [PATCH 528/553] Add repo links to meta_class --- ultimatepython/advanced/meta_class.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index f9011e33..40f361ca 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -7,7 +7,11 @@ class ModelMeta(type): By studying how SQLAlchemy and Django ORM work under the hood, we can see 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. + everything that happens in either framework. Check out the source code + in SQLAlchemy and Django to see what actually happens: + + https://github.com/sqlalchemy/sqlalchemy + https://github.com/django/django 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 From 7ebc8eef1bb98ee6cc271619b7c8f56784a15875 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 11 Oct 2020 21:47:23 -0700 Subject: [PATCH 529/553] Add more docs to mixin lesson --- ultimatepython/advanced/mixin.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index fa04c04f..64f15208 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -1,19 +1,34 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass +@dataclass class Request: - """Request model.""" + """Request data model. - def __init__(self, url, user): - self.url = url - self.user = user + Assumes only GET requests for simplicity so there is no method + attribute associated with this class. + """ + url: str + user: str class RequestHandler(ABC): - """Request handler interface.""" + """Request handler interface. + + In the real world, a URL is expected to handle different kinds of HTTP + methods. In order to enable this, we would define a `View` class whose + dispatch method runs the appropriate HTTP method given the request + payload. That class would then get bound to a URL router. Check out + the source code in Django and Flask to see how that can be done: + + https://github.com/django/django + https://github.com/pallets/flask + """ @abstractmethod def handle(self, request): + """Handle web requests.""" raise NotImplementedError @@ -29,17 +44,21 @@ def handle(self, request): @abstractmethod def get_template_name(self, request_url): + """Get template name.""" raise NotImplementedError def is_valid_template(self, template_name): + """Check if template name is valid.""" return template_name.endswith(self.template_suffix) @staticmethod def handle_invalid_template(request): + """Handle request for invalid template.""" return f"

Invalid entry for {request.url}

" @abstractmethod def render_template(self, template_name): + """Render contents of specified template name.""" raise NotImplementedError @@ -53,10 +72,12 @@ def handle(self, request): @abstractmethod def is_valid_user(self, request_user): + """Check if user is valid.""" raise NotImplementedError @staticmethod def handle_invalid_user(request): + """Handle request for invalid user.""" return f"

Access denied for {request.url}

" From 44fec3b03b920bb1dd0581209b40607a6cff7519 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 12 Oct 2020 09:11:20 -0700 Subject: [PATCH 530/553] Revise class docs in mixin lesson --- ultimatepython/advanced/mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index 64f15208..ed5f6651 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -17,10 +17,10 @@ class RequestHandler(ABC): """Request handler interface. In the real world, a URL is expected to handle different kinds of HTTP - methods. In order to enable this, we would define a `View` class whose - dispatch method runs the appropriate HTTP method given the request - payload. That class would then get bound to a URL router. Check out - the source code in Django and Flask to see how that can be done: + methods. To support this, we would define a `View` class whose dispatch + method runs the appropriate HTTP method given the request payload. That + class would then get registered to a URL router in an app server. Check + out the source code in Django and Flask to see how that works: https://github.com/django/django https://github.com/pallets/flask From 28f09e074705a40d885e734abcb1aa78f877cf5a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 12 Oct 2020 18:21:32 -0700 Subject: [PATCH 531/553] Update mixin.py --- ultimatepython/advanced/mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index ed5f6651..ae18bdcd 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -28,7 +28,7 @@ class would then get registered to a URL router in an app server. Check @abstractmethod def handle(self, request): - """Handle web requests.""" + """Handle incoming request.""" raise NotImplementedError From bb3cf119441a1b2fcffacd1c7e4ddef5ec5ae076 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 12 Oct 2020 20:08:44 -0700 Subject: [PATCH 532/553] Update mixin.py --- ultimatepython/advanced/mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index ae18bdcd..b26cd002 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -17,10 +17,10 @@ class RequestHandler(ABC): """Request handler interface. In the real world, a URL is expected to handle different kinds of HTTP - methods. To support this, we would define a `View` class whose dispatch - method runs the appropriate HTTP method given the request payload. That - class would then get registered to a URL router in an app server. Check - out the source code in Django and Flask to see how that works: + methods. To support this, we would define a `View` class with a method + that dispatches the request payload to the appropriate HTTP handler. + Afterwards, we would register the class to a URL router. Check out + the source code in Django and Flask to see how that works: https://github.com/django/django https://github.com/pallets/flask From 9a41a5c9e27f511a64ee849273555d1a0968a904 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 14 Oct 2020 08:14:12 -0700 Subject: [PATCH 533/553] Update async.py --- ultimatepython/advanced/async.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index c3fe49d8..fa1b556e 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -4,8 +4,8 @@ from uuid import uuid4 # Module-level constants -_MILLISECOND = .001 -_HOUR = 3600 +_DELAY_SMALL = .001 +_DELAY_LARGE = 3600 @dataclass @@ -38,7 +38,7 @@ async def start_job(delay, job_id): async def schedule_jobs(): """Schedule jobs concurrently.""" # Start a job which also represents a coroutine - single_job = start_job(_MILLISECOND, uuid4().hex) + single_job = start_job(_DELAY_SMALL, uuid4().hex) assert asyncio.iscoroutine(single_job) # Grab a job record from the coroutine @@ -46,7 +46,7 @@ async def schedule_jobs(): 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)) + single_task = asyncio.create_task(start_job(_DELAY_LARGE, uuid4().hex)) assert asyncio.isfuture(single_task) # Futures are different from coroutines in that they can be cancelled @@ -57,7 +57,7 @@ async def schedule_jobs(): assert single_task.cancelled() # Gather coroutines for batch start - batch_jobs = [start_job(_MILLISECOND, uuid4().hex) for _ in range(10)] + batch_jobs = [start_job(_DELAY_SMALL, uuid4().hex) for _ in range(10)] batch_records = await asyncio.gather(*batch_jobs) # We get the same amount of records as we have coroutines From 4d4fe4a2c98ae89eab27926f0e12f4b0aa5b9851 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 17 Oct 2020 08:57:49 -0700 Subject: [PATCH 534/553] Update isort to 5.6.4 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 18b7d83e..f16c7654 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,7 +24,7 @@ pyflakes = ">=2.2.0,<2.3.0" [[package]] name = "isort" -version = "5.6.3" +version = "5.6.4" description = "A Python utility / library to sort Python imports." category = "main" optional = false @@ -106,8 +106,8 @@ flake8 = [ {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] isort = [ - {file = "isort-5.6.3-py3-none-any.whl", hash = "sha256:2c1d044a96c74367ab12188593d134c596747d97aec43b9cdb12eea83b6889d2"}, - {file = "isort-5.6.3.tar.gz", hash = "sha256:3820dd92c3214290cda6351f2ae2cedd5170759bc434af600eaad4f7a82a6ade"}, + {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, + {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, From 7f35b5c9d9cd85b13ce405f9b005dd7508fb71e9 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 17 Oct 2020 22:19:18 -0700 Subject: [PATCH 535/553] Update CircleCI config (#22) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 15ae3ad6..fc944b04 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - python: circleci/python@1.1.0 + python: circleci/python@1.2.0 codecov: codecov/codecov@1.1.1 jobs: From 9648b4611183fadc75bcf88ea56f4b9df3af94f6 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 17 Oct 2020 22:46:01 -0700 Subject: [PATCH 536/553] Add Korean README content (#23) Credits to junhwang77 for the initial content --- README.ko.md | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 README.ko.md diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 00000000..657bbcde --- /dev/null +++ b/README.ko.md @@ -0,0 +1,121 @@ +# Ultimate Python 학습 가이드 + +[![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) +[![Code Coverage](https://img.shields.io/codecov/c/github/huangsam/ultimate-python)](https://codecov.io/gh/huangsam/ultimate-python) +[![Quality Gate Status](https://img.shields.io/sonar/quality_gate/huangsam_ultimate-python?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](https://github.com/huangsam/ultimate-python/blob/master/LICENSE) +[![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) + +초보자와 전문가 모두를위한 최고의 Python 학습 가이드입니다. :snake: :snake: :snake: + +```python +print("Ultimate Python 학습 가이드") +``` + +## 이 학습 가이드를 만든 이유 + +저는 지난 5 년 동안 대학 졸업생, 대규모 회사의 직원, 셀러리 및 풀 스택 Python과 같은 저장소의 오픈 소스 기고자로 +Python을 사용하면서 핵심 Python에 대해 배운 내용을 공유하기 위해 GitHub 저장소를 만들었습니다. 더 많은 사람들이 +파이썬을 배우고 그것을 통해 그들의 열정을 추구하는 것을 기대합니다. :mortar_board: + +## 목표 + +이 가이드를 만드는 기본 목표는 다음과 같습니다. + +:trophy: 실습 학습을 선호하는 Python 초보자를위한 리소스 역할을합니다. 이 저장소에는 PyCharm과 같은 IDE 및 +Repl.it와 같은 브라우저에서 실행할 수있는 독립형 모듈 모음이 있습니다. 평범한 오래된 터미널조차도 예제와 함께 +작동합니다. 대부분의 줄에는 프로그램이 단계별로 수행하는 작업을 독자에게 안내하는 신중하게 작성된 주석이 있습니다. +기본 루틴이 삭제되지 않고 각 변경 후 성공적으로 실행되는 한 사용자는 어디에서나 소스 코드를 수정하는 것이 좋습니다. + +:trophy: 핵심 Python 개념을 다시 검토하려는 사용자를위한 순수한 가이드 역할을합니다. 기본 라이브러리 만 활용되므로 +도메인 별 개념의 오버 헤드없이 이러한 개념을 전달할 수 있습니다. 따라서 인기있는 오픈 소스 라이브러리 및 프레임 워크 +(예 : sqlalchemy, requests, pandas)는 설치되지 않습니다. 그러나 목표가 진정한 Pythonista가되는 것이라면 +이러한 프레임 워크의 소스 코드를 읽는 것은 고무적이고 적극 권장됩니다. + +## 시작하기 + +로컬 컴퓨터에 Git 및 Python을 설치하지 않고도 브라우저에서 작업 환경을 시작하려면 위의 배지를 클릭하세요. 이러한 +요구 사항이 이미 충족 된 경우 저장소를 직접 복제해도됩니다. + +저장소에 액세스 할 수있게되면 독립형 모듈에서 배울 준비가 된 것입니다. 각 모듈을 최대한 활용하려면 모듈 코드를 +읽고 실행하십시오. 모듈을 실행하는 두 가지 방법이 있습니다. + +1. 단일 모듈 실행 : `python ultimatepython/syntax/variable.py` +2. 모든 모듈을 실행합니다. `python runner.py` + +## 목차 + +:books: = 외부 리소스, +:cake: = 초급 주제, +:exploding_head: = 고급 주제 + +1. **Python 정보** + - 개요 : Python이란 무엇입니까 (:books:, :cake:) + - 디자인 철학 : The Zen of Python (:books:) + - 스타일 가이드 : Python 코드 용 스타일 가이드 (:books:, :exploding_head:) + - 데이터 모델 : 데이터 모델 (:books:, :exploding_head:) + - 표준 라이브러리 : Python 표준 라이브러리 (:books:, :exploding_head:) + - 내장 기능 : 내장 기능 (:books:) +2. **통사론** + - 변수 : 내장 리터럴 (:cake:) + - 식 : 숫자 연산 (:cake:) + - 조건부 : if | if-else | if-elif-else (:cake:) + - 루프 : for 루프 | while-loop (:cake:) + - 함수 : def | 람다 (:cake:) +3. **데이터 구조** + - 목록 : 목록 작업 (:cake:) + - 튜플 : 튜플 연산 + - 설정 : 설정 작업 + - Dict : 사전 작업 (:cake:) + - 이해력 : 목록 | 튜플 | 세트 | dict + - 문자열 : 문자열 연산 (:cake:) + - 시간 복잡성 : cPython 작업 (:books:, :exploding_head:) +4. **클래스** + - 기본 클래스 : 기본 정의 (:cake:) + - 추상 클래스 : 추상 정의 + - 예외 클래스 : 예외 정의 + - 반복기 클래스 : 반복기 정의 | 수익률 (:exploding_head:) +5. **고급** + - 데코레이터 : 데코레이터 정의 | 랩 (:exploding_head:) + - 컨텍스트 관리자 : 컨텍스트 관리자 (:exploding_head:) + - 메서드 해결 순서 : mro (:exploding_head:) + - Mixin : Mixin 정의 (:exploding_head:) + - 메타 클래스 : 메타 클래스 정의 (:exploding_head:) + - 글타래 (쓰레드) : ThreadPoolExecutor (:exploding_head:) + - Asyncio : 비동기 | 기다리다 (:exploding_head:) + - 약한 참조 : weakref (:exploding_head:) + - 벤치 마크 : cProfile | pstats (:exploding_head:) + - 조롱 : MagicMock | PropertyMock | 패치 (:exploding_head:) + - 정규식 : 검색 | findall | 일치 | fullmatch (:exploding_head:) + - 데이터 형식 : json | xml | csv (:exploding_head:) + +## 추가 자료 + +:necktie: = 인터뷰 리소스, +:test_tube: = 코드 샘플, +:brain: = 프로젝트 아이디어 + +### GitHub 저장소 + +다른 잘 알려진 자료를 읽으면서 계속 배우십시오. + +- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) +- [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:) +- [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) +- [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) (:test_tube:) +- [karan/Projects](https://github.com/karan/Projects) (:brain:) +- [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) +- [academic/awesome-datascience](https://github.com/academic/awesome-datascience) +- [josephmisiti/awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning) +- [ZuzooVn/machine-learning-for-software-engineers](https://github.com/ZuzooVn/machine-learning-for-software-engineers) + +### 대화 형 연습 + +코딩 기술이 녹슬지 않도록 계속 연습하십시오. + +- [leetcode.com](https://leetcode.com/) (:necktie:) +- [hackerrank.com](https://www.hackerrank.com/) (:necktie:) +- [kaggle.com](https://www.kaggle.com/) (:brain:) +- [exercism.io](https://exercism.io/) +- [projecteuler.net](https://projecteuler.net/) From 040eb05905080f9c6a7c96cbb4023a9157db289f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sat, 17 Oct 2020 23:13:50 -0700 Subject: [PATCH 537/553] Link Python modules in Korean README (#24) --- README.ko.md | 70 +++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/README.ko.md b/README.ko.md index 657bbcde..453f3aab 100644 --- a/README.ko.md +++ b/README.ko.md @@ -34,6 +34,8 @@ Repl.it와 같은 브라우저에서 실행할 수있는 독립형 모듈 모음 ## 시작하기 +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) + 로컬 컴퓨터에 Git 및 Python을 설치하지 않고도 브라우저에서 작업 환경을 시작하려면 위의 배지를 클릭하세요. 이러한 요구 사항이 이미 충족 된 경우 저장소를 직접 복제해도됩니다. @@ -50,44 +52,44 @@ Repl.it와 같은 브라우저에서 실행할 수있는 독립형 모듈 모음 :exploding_head: = 고급 주제 1. **Python 정보** - - 개요 : Python이란 무엇입니까 (:books:, :cake:) - - 디자인 철학 : The Zen of Python (:books:) - - 스타일 가이드 : Python 코드 용 스타일 가이드 (:books:, :exploding_head:) - - 데이터 모델 : 데이터 모델 (:books:, :exploding_head:) - - 표준 라이브러리 : Python 표준 라이브러리 (:books:, :exploding_head:) - - 내장 기능 : 내장 기능 (:books:) + - 개요 : [Python이란 무엇입니까](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:, :cake:) + - 디자인 철학 : [The Zen of Python](https://www.python.org/dev/peps/pep-0020/) (:books:) + - 스타일 가이드 : [Python 코드 용 스타일 가이드](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) + - 데이터 모델 : [데이터 모델](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) + - 표준 라이브러리 : [Python 표준 라이브러리](https://docs.python.org/3/library/) (:books:, :exploding_head:) + - 내장 기능 : [내장 기능](https://docs.python.org/3/library/functions.html) (:books:) 2. **통사론** - - 변수 : 내장 리터럴 (:cake:) - - 식 : 숫자 연산 (:cake:) - - 조건부 : if | if-else | if-elif-else (:cake:) - - 루프 : for 루프 | while-loop (:cake:) - - 함수 : def | 람다 (:cake:) + - 변수 : [내장 리터럴](ultimatepython/syntax/variable.py) (:cake:) + - 식 : [숫자 연산](ultimatepython/syntax/expression.py) (:cake:) + - 조건부 : [if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) (:cake:) + - 루프 : [for 루프 | while-loop](ultimatepython/syntax/loop.py) (:cake:) + - 함수 : [def | 람다](ultimatepython/syntax/function.py) (:cake:) 3. **데이터 구조** - - 목록 : 목록 작업 (:cake:) - - 튜플 : 튜플 연산 - - 설정 : 설정 작업 - - Dict : 사전 작업 (:cake:) - - 이해력 : 목록 | 튜플 | 세트 | dict - - 문자열 : 문자열 연산 (:cake:) - - 시간 복잡성 : cPython 작업 (:books:, :exploding_head:) + - 목록 : [목록 작업](ultimatepython/data_structures/list.py) (:cake:) + - 튜플 : [튜플 연산](ultimatepython/data_structures/tuple.py) + - 설정 : [설정 작업](ultimatepython/data_structures/set.py) + - Dict : [사전 작업](ultimatepython/data_structures/dict.py) (:cake:) + - 이해력 : [목록 | 튜플 | 세트 | dict](ultimatepython/data_structures/comprehension.py) + - 문자열 : [문자열 연산](ultimatepython/data_structures/string.py) (:cake:) + - 시간 복잡성 : [cPython 작업](https://wiki.python.org/moin/TimeComplexity) (:books:, :exploding_head:) 4. **클래스** - - 기본 클래스 : 기본 정의 (:cake:) - - 추상 클래스 : 추상 정의 - - 예외 클래스 : 예외 정의 - - 반복기 클래스 : 반복기 정의 | 수익률 (:exploding_head:) + - 기본 클래스 : [기본 정의](ultimatepython/classes/basic_class.py) (:cake:) + - 추상 클래스 : [추상 정의](ultimatepython/classes/abstract_class.py) + - 예외 클래스 : [예외 정의](ultimatepython/classes/exception_class.py) + - 반복기 클래스 : [반복기 정의 | 수익률](ultimatepython/classes/iterator_class.py) (:exploding_head:) 5. **고급** - - 데코레이터 : 데코레이터 정의 | 랩 (:exploding_head:) - - 컨텍스트 관리자 : 컨텍스트 관리자 (:exploding_head:) - - 메서드 해결 순서 : mro (:exploding_head:) - - Mixin : Mixin 정의 (:exploding_head:) - - 메타 클래스 : 메타 클래스 정의 (:exploding_head:) - - 글타래 (쓰레드) : ThreadPoolExecutor (:exploding_head:) - - Asyncio : 비동기 | 기다리다 (:exploding_head:) - - 약한 참조 : weakref (:exploding_head:) - - 벤치 마크 : cProfile | pstats (:exploding_head:) - - 조롱 : MagicMock | PropertyMock | 패치 (:exploding_head:) - - 정규식 : 검색 | findall | 일치 | fullmatch (:exploding_head:) - - 데이터 형식 : json | xml | csv (:exploding_head:) + - 데코레이터 : [데코레이터 정의 | 랩](ultimatepython/advanced/decorator.py) (:exploding_head:) + - 컨텍스트 관리자 : [컨텍스트 관리자](ultimatepython/advanced/context_manager.py) (:exploding_head:) + - 메서드 해결 순서 : [mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Mixin : [Mixin 정의](ultimatepython/advanced/mixin.py) (:exploding_head:) + - 메타 클래스 : [메타 클래스 정의](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - 글타래 (쓰레드) : [ThreadPoolExecutor](ultimatepython/advanced/thread.py) (:exploding_head:) + - Asyncio : [비동기 | 기다리다](ultimatepython/advanced/async.py) (:exploding_head:) + - 약한 참조 : [weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) + - 벤치 마크 : [cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) + - 조롱 : [MagicMock | PropertyMock | 패치](ultimatepython/advanced/mocking.py) (:exploding_head:) + - 정규식 : [검색 | findall | 일치 | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) + - 데이터 형식 : [json | xml | csv](ultimatepython/advanced/data_format.py) (:exploding_head:) ## 추가 자료 From 081532e428fab9532cb2b8ee79d2dfdbe3a2edff Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Sun, 18 Oct 2020 19:40:02 -0700 Subject: [PATCH 538/553] Add README links to README files (#26) --- README.ko.md | 3 +++ README.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.ko.md b/README.ko.md index 453f3aab..74e400de 100644 --- a/README.ko.md +++ b/README.ko.md @@ -12,6 +12,9 @@ print("Ultimate Python 학습 가이드") ``` +[English](README.md) | +[한국어](README.ko.md) + ## 이 학습 가이드를 만든 이유 저는 지난 5 년 동안 대학 졸업생, 대규모 회사의 직원, 셀러리 및 풀 스택 Python과 같은 저장소의 오픈 소스 기고자로 diff --git a/README.md b/README.md index 48bb202c..8b2a26ab 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Ultimate Python study guide for newcomers and professionals alike. :snake: :snak print("Ultimate Python study guide") ``` +[English](README.md) | +[한국어](README.ko.md) + ## Motivation I created a GitHub repo to share what I've learned about [core Python](https://www.python.org/) From e670a6c157ae16b1369d3ff2896dc884337e8080 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 19 Oct 2020 22:23:37 -0700 Subject: [PATCH 539/553] Implement deque lesson (#27) * Implement deque lesson * Add references to the new deque lesson --- README.ko.md | 1 + README.md | 1 + ultimatepython/data_structures/deque.py | 41 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 ultimatepython/data_structures/deque.py diff --git a/README.ko.md b/README.ko.md index 74e400de..7f9d8caf 100644 --- a/README.ko.md +++ b/README.ko.md @@ -74,6 +74,7 @@ Repl.it와 같은 브라우저에서 실행할 수있는 독립형 모듈 모음 - Dict : [사전 작업](ultimatepython/data_structures/dict.py) (:cake:) - 이해력 : [목록 | 튜플 | 세트 | dict](ultimatepython/data_structures/comprehension.py) - 문자열 : [문자열 연산](ultimatepython/data_structures/string.py) (:cake:) + - Deque: [deque](ultimatepython/data_structures/deque.py) (:exploding_head:) - 시간 복잡성 : [cPython 작업](https://wiki.python.org/moin/TimeComplexity) (:books:, :exploding_head:) 4. **클래스** - 기본 클래스 : [기본 정의](ultimatepython/classes/basic_class.py) (:cake:) diff --git a/README.md b/README.md index 8b2a26ab..40380915 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ There are two ways of running the modules: - 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:) + - Deque: [deque](ultimatepython/data_structures/deque.py) (:exploding_head:) - Time complexity: [cPython operations](https://wiki.python.org/moin/TimeComplexity) (:books:, :exploding_head:) 4. **Classes** - Basic class: [Basic definition](ultimatepython/classes/basic_class.py) (:cake:) diff --git a/ultimatepython/data_structures/deque.py b/ultimatepython/data_structures/deque.py new file mode 100644 index 00000000..aa26cf22 --- /dev/null +++ b/ultimatepython/data_structures/deque.py @@ -0,0 +1,41 @@ +from collections import deque + + +def main(): + # A list is identical to a vector where a new array is created when + # there are too many elements and the old elements are moved over to + # the new array one-by-one. The time complexity involved with growing + # its size is linear. A deque is identical to a doubly linked list + # whose nodes have a left pointer and a right pointer. In order to + # grow the linked list, a new node is created and added to the left + # or the right of the linked list. The time complexity involved with + # growing its size is constant. Check out the source code for a list + # and a deque to learn more: + # https://github.com/python/cpython/blob/3.8/Objects/listobject.c + # https://github.com/python/cpython/blob/3.8/Modules/_collectionsmodule.c + dq = deque() + + for i in range(1, 5): + # Similar to adding a new node to the right of the linked list + dq.append(i) + + # Similar to adding a new node to the left of the linked list + dq.appendleft(i * 2) + + # A deque can be iterated on to build a sequence of choice + assert [el for el in dq] == [8, 6, 4, 2, 1, 2, 3, 4] + assert tuple(el for el in dq) == (8, 6, 4, 2, 1, 2, 3, 4) + + # A deque can be used as a stack + # https://en.wikipedia.org/wiki/Stack_(abstract_data_type) + assert dq.pop() == 4 + assert dq.pop() == 3 + + # A deque can be used as a queue + # https://en.wikipedia.org/wiki/Queue_(abstract_data_type) + assert dq.popleft() == 8 + assert dq.popleft() == 6 + + +if __name__ == '__main__': + main() From acaea9b5a3fa1028cae43b389c9e518ecc14d171 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 19 Oct 2020 22:32:13 -0700 Subject: [PATCH 540/553] Tweak config in thread lesson --- ultimatepython/advanced/thread.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ultimatepython/advanced/thread.py b/ultimatepython/advanced/thread.py index 8bead763..645add99 100644 --- a/ultimatepython/advanced/thread.py +++ b/ultimatepython/advanced/thread.py @@ -3,7 +3,7 @@ from datetime import datetime # Module-level constants -_MULTIPLY_DELAY = 0.001 +_MULTIPLY_DELAY = 0.01 def multiply_by_two(item): @@ -12,12 +12,12 @@ def multiply_by_two(item): return item * 2 -def run_thread_workers(work, data, max_workers=10): +def run_thread_workers(work, data): """Run thread workers that invoke work on each data element.""" results = set() # We can use a with statement to ensure workers are cleaned up promptly - with ThreadPoolExecutor(max_workers=max_workers) as executor: + with ThreadPoolExecutor() as executor: # Note that results are returned out of order work_queue = (executor.submit(work, item) for item in data) for future in as_completed(work_queue): @@ -27,7 +27,7 @@ def run_thread_workers(work, data, max_workers=10): def main(): - original_data = {num for num in range(50)} + original_data = {num for num in range(5)} expected_data = {(item * 2) for item in original_data} # Let's get the data using the simple approach From 9ec934a926acbab0cb92c799811711a357484eda Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 19 Oct 2020 23:05:01 -0700 Subject: [PATCH 541/553] Refine comments in deque lesson --- ultimatepython/data_structures/deque.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/ultimatepython/data_structures/deque.py b/ultimatepython/data_structures/deque.py index aa26cf22..12138e1b 100644 --- a/ultimatepython/data_structures/deque.py +++ b/ultimatepython/data_structures/deque.py @@ -3,14 +3,14 @@ def main(): # A list is identical to a vector where a new array is created when - # there are too many elements and the old elements are moved over to - # the new array one-by-one. The time complexity involved with growing - # its size is linear. A deque is identical to a doubly linked list - # whose nodes have a left pointer and a right pointer. In order to - # grow the linked list, a new node is created and added to the left - # or the right of the linked list. The time complexity involved with - # growing its size is constant. Check out the source code for a list - # and a deque to learn more: + # there are too many elements in the old array, and the old array + # elements are moved over to the new array one-by-one. The time + # involved with growing its size increases linearly. A deque is + # identical to a doubly linked list whose nodes have a left pointer + # and a right pointer. In order to grow the linked list, a new node + # is created and added to the left or the right of the linked list. + # The time complexity involved with growing its size is constant. + # Check out the source code for a list and a deque here: # https://github.com/python/cpython/blob/3.8/Objects/listobject.c # https://github.com/python/cpython/blob/3.8/Modules/_collectionsmodule.c dq = deque() @@ -22,9 +22,10 @@ def main(): # Similar to adding a new node to the left of the linked list dq.appendleft(i * 2) - # A deque can be iterated on to build a sequence of choice + # A deque can be iterated over to build any data structure assert [el for el in dq] == [8, 6, 4, 2, 1, 2, 3, 4] assert tuple(el for el in dq) == (8, 6, 4, 2, 1, 2, 3, 4) + assert {el for el in dq} == {8, 6, 4, 2, 1, 3} # A deque can be used as a stack # https://en.wikipedia.org/wiki/Stack_(abstract_data_type) From 5c27bc1a4cf0cea50262e8d927770649197c5878 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 20 Oct 2020 09:03:48 -0700 Subject: [PATCH 542/553] Update README.ko.md --- README.ko.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.ko.md b/README.ko.md index 7f9d8caf..2d06d6ae 100644 --- a/README.ko.md +++ b/README.ko.md @@ -32,7 +32,7 @@ Repl.it와 같은 브라우저에서 실행할 수있는 독립형 모듈 모음 :trophy: 핵심 Python 개념을 다시 검토하려는 사용자를위한 순수한 가이드 역할을합니다. 기본 라이브러리 만 활용되므로 도메인 별 개념의 오버 헤드없이 이러한 개념을 전달할 수 있습니다. 따라서 인기있는 오픈 소스 라이브러리 및 프레임 워크 -(예 : sqlalchemy, requests, pandas)는 설치되지 않습니다. 그러나 목표가 진정한 Pythonista가되는 것이라면 +(예 : `sqlalchemy`, `requests`, `pandas`)는 설치되지 않습니다. 그러나 목표가 진정한 Pythonista가되는 것이라면 이러한 프레임 워크의 소스 코드를 읽는 것은 고무적이고 적극 권장됩니다. ## 시작하기 From 558a63665637fd03eb8be7b9127d89fee6591968 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 26 Oct 2020 09:28:26 -0700 Subject: [PATCH 543/553] Add module docs for advanced modules (#28) --- ultimatepython/advanced/async.py | 8 ++++++++ ultimatepython/advanced/benchmark.py | 6 ++++++ ultimatepython/advanced/context_manager.py | 7 +++++++ ultimatepython/advanced/data_format.py | 6 ++++++ ultimatepython/advanced/decorator.py | 8 ++++++++ ultimatepython/advanced/meta_class.py | 6 ++++++ ultimatepython/advanced/mixin.py | 7 +++++++ ultimatepython/advanced/mocking.py | 6 ++++++ ultimatepython/advanced/mro.py | 8 ++++++++ ultimatepython/advanced/regex.py | 6 ++++++ ultimatepython/advanced/thread.py | 7 +++++++ ultimatepython/advanced/weak_ref.py | 6 ++++++ 12 files changed, 81 insertions(+) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index fa1b556e..aac4febc 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -1,3 +1,11 @@ +""" +Asynchronous programming is a relatively new concept in Python 3.x. +This module aims to highlight how it could be used in the context of a +scheduler which runs a fire-and-forget operation for starting jobs. In +the real world, it takes time for a scheduler to start a job (i.e. hit +an API endpoint, ask the operating system for resources) so we assume +that starting a job has some intrinsic delay. +""" import asyncio from dataclasses import dataclass from datetime import datetime diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 9cfe3018..2d7a8628 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -1,3 +1,9 @@ +""" +As programs increase in size, they have the risk of getting slow as new +features are added and extra layers of complexity to the main features. +Benchmarking is an approach that helps developers use profiling metrics +and their code intuition to optimize programs further. +""" import cProfile import io import pstats diff --git a/ultimatepython/advanced/context_manager.py b/ultimatepython/advanced/context_manager.py index 12a0cd5c..61312ef4 100644 --- a/ultimatepython/advanced/context_manager.py +++ b/ultimatepython/advanced/context_manager.py @@ -1,3 +1,10 @@ +""" +Context managers are used to manage opening and closing resources as Python +enters and exits a code block respectively. Some examples of the resources +it can manage are files, database connections and sockets. In this module, +we simulate how a context manager can handle open and close operations of +a file-like object called StringIO. +""" from contextlib import contextmanager from io import StringIO diff --git a/ultimatepython/advanced/data_format.py b/ultimatepython/advanced/data_format.py index 5fd9d2a2..d858a848 100644 --- a/ultimatepython/advanced/data_format.py +++ b/ultimatepython/advanced/data_format.py @@ -1,3 +1,9 @@ +""" +One of the reasons people use Python is for its ease of use in parsing +structured data and converting them to serialized objects for use cases +such as database storage or data analysis. This module aims to show to +parse and process these structured data formats: JSON, XML and CSV. +""" import json from csv import DictReader from dataclasses import dataclass, fields diff --git a/ultimatepython/advanced/decorator.py b/ultimatepython/advanced/decorator.py index fd3340ed..4caf4837 100644 --- a/ultimatepython/advanced/decorator.py +++ b/ultimatepython/advanced/decorator.py @@ -1,3 +1,11 @@ +""" +Decorator is a form of programming where you modify a function or class +with new functionality or state at runtime. This module shows how a simple +"encryption" function for one string can be decorated to work with a +collection of strings. Note that the decorator handles nested collections +so it needs to recurse into them to process strings at all layers of the +initial collection from top to bottom. +""" from functools import wraps # Module-level constants diff --git a/ultimatepython/advanced/meta_class.py b/ultimatepython/advanced/meta_class.py index 40f361ca..45841fef 100644 --- a/ultimatepython/advanced/meta_class.py +++ b/ultimatepython/advanced/meta_class.py @@ -1,3 +1,9 @@ +""" +Metaclass is a form of programming where we modify a class as it is being +created at runtime. This module shows how a metaclass can be used to add +database attributes and tables for "logic-free" model classes without a +developer having to do it manually in the code. +""" from abc import ABC diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index b26cd002..4969da41 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -1,3 +1,10 @@ +""" +Mixins are a way to provide implementation details that simplify the work +for a developer to conform to an opaque interface specification. In this +module, we have one request handler interface and two request handler +mixins to illustrate how mixins can make our lives easier when defining +concrete classes that conform to the interface. +""" from abc import ABC, abstractmethod from dataclasses import dataclass diff --git a/ultimatepython/advanced/mocking.py b/ultimatepython/advanced/mocking.py index a7a03183..46fd2f71 100644 --- a/ultimatepython/advanced/mocking.py +++ b/ultimatepython/advanced/mocking.py @@ -1,3 +1,9 @@ +""" +Mocking objects is a common strategy that developers use to test code that +depends on an external system or external resources for it to work +properly. This module shows how to use mocking to modify an application +server so that it is easier to test. +""" from collections import Counter from unittest.mock import MagicMock, PropertyMock, patch diff --git a/ultimatepython/advanced/mro.py b/ultimatepython/advanced/mro.py index 809a5f9f..ae75fe0c 100644 --- a/ultimatepython/advanced/mro.py +++ b/ultimatepython/advanced/mro.py @@ -1,3 +1,11 @@ +""" +MRO stands for method resolution order and it's used by class definitions +to determine which method will be run by a class instance. This module +shows how the MRO is useful for the classic diamond problem where classes +B and C depend on class A, and class D depends on classes B and C. +""" + + class BasePlayer: """Base player.""" diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index 88c04e49..0bb5463c 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -1,3 +1,9 @@ +""" +Regular expressions is a way to search text efficiently. Implementing them +is difficult but thankfully Python provides a package for us to use them +easily. This module shows a few examples of use the `re` package to search +predefined text snippets stored in module-level constants. +""" import re # Module-level constants diff --git a/ultimatepython/advanced/thread.py b/ultimatepython/advanced/thread.py index 645add99..5745cc07 100644 --- a/ultimatepython/advanced/thread.py +++ b/ultimatepython/advanced/thread.py @@ -1,3 +1,10 @@ +""" +Threaded programming is used by developers to improve the performance of +an application. This module shows a simple multiplication operation with +some delay can be parallelized using `ThreadPoolExecutor`. A good +understanding of operating systems and CPUs is recommended before +reading the code in this module. +""" import time from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime diff --git a/ultimatepython/advanced/weak_ref.py b/ultimatepython/advanced/weak_ref.py index e88e36a3..b5bffedb 100644 --- a/ultimatepython/advanced/weak_ref.py +++ b/ultimatepython/advanced/weak_ref.py @@ -1,3 +1,9 @@ +""" +Weak references are a way to help the Python interpreter remove unused data +more easily. This module shows how it can be used to keep a server registry +up-to-date as it explicitly sets up and implicitly tears down servers as +the program enters and leaves a function scope. +""" import weakref from uuid import uuid4 From 401928cadf9fa750b28828b3a31fc0e9d675e6e0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Mon, 26 Oct 2020 21:51:23 -0700 Subject: [PATCH 544/553] Add module docs for classes modules (#29) --- ultimatepython/classes/abstract_class.py | 22 ++++++++++++++-------- ultimatepython/classes/basic_class.py | 12 ++++++++---- ultimatepython/classes/exception_class.py | 7 +++++++ ultimatepython/classes/iterator_class.py | 7 +++++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 4014e762..7d68103f 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -1,17 +1,23 @@ +""" +Abstract class is an extension of a basic class. Like a basic class, an +abstract class has methods and state. Unlike a basic class, it inherits +the `ABC` class and has at least one `abstractmethod`. That means we +cannot create an instance directly from its constructor. Also, all concrete +classes must implement `abstractmethod` definitions. In this module, we +will see an example of an abstract class and a concrete class. + +For more about abstract classes, click the link below: + +https://www.python.org/dev/peps/pep-3119/ +""" from abc import ABC, abstractmethod 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 we cannot create - an instance directly from its constructor. Also, all subclasses - need to implement every `abstractmethod` in this class. - - For more about abstract classes, click the link below: - - https://www.python.org/dev/peps/pep-3119/ + Any employee can work and relax. The way that one type of employee + can work and relax is different from another type of employee. """ def __init__(self, name, title): diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 8fc4592e..c1a18ce1 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,13 +1,17 @@ +""" +A basic class comprises of state and methods. This module defines +a simple definition of a car class and creates some car instances +for demonstration purposes. +""" from inspect import isfunction, ismethod, signature class Car: """Basic definition of a car. - A car is a good entity to define a class with because it has state - and methods associated with it. We begin with a simple mental model - of what a car is. That way, we can start talking about core concepts - that are associated with a class definition. + We begin with a simple mental model of what a car is. That way, we + can start exploring the core concepts that are associated with a + class definition. """ def __init__(self, make, model, year, miles): diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index f658f0b0..71c7a987 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -1,3 +1,10 @@ +""" +Exception classes are used to indicate that something has gone wrong with +the program at runtime. This module defines a handful of custom exception +classes and shows how they can be used in the context of a function. +""" + + class CustomError(Exception): """Custom class of errors. diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 0e55c22b..41216cc4 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,3 +1,10 @@ +""" +Iterator classes implement the __iter__ and __next__ magic methods. This +module defines an employee iterator class that goes iterates through each +employee in a hierarchy one-by-one. This module also shows how a similar +thing can be achieved with a generator function. +""" + # Module-level constants _ITERATION_MESSAGE = "Cyclic loop detected" From 5fdd61dfb4d87ef7d5ffc1b6d50b58777e861ab5 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 27 Oct 2020 06:52:14 -0700 Subject: [PATCH 545/553] Add module docs for DS modules (#30) --- ultimatepython/data_structures/comprehension.py | 6 ++++++ ultimatepython/data_structures/deque.py | 6 ++++++ ultimatepython/data_structures/dict.py | 5 +++++ ultimatepython/data_structures/list.py | 7 +++++++ ultimatepython/data_structures/set.py | 7 +++++++ ultimatepython/data_structures/string.py | 6 ++++++ ultimatepython/data_structures/tuple.py | 6 ++++++ 7 files changed, 43 insertions(+) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index e0b4aee8..8d60993b 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -1,3 +1,9 @@ +""" +This module shows comprehensions where we make lists, tuples, sets and +dictionaries by looping through iterators. +""" + + def main(): # One interesting fact about data structures is that we can build # them with comprehensions. Let's explain how the first one works: diff --git a/ultimatepython/data_structures/deque.py b/ultimatepython/data_structures/deque.py index 12138e1b..7cdf61a5 100644 --- a/ultimatepython/data_structures/deque.py +++ b/ultimatepython/data_structures/deque.py @@ -1,3 +1,9 @@ +""" +A deque functions similarly to all of the other sequential data structures +but has some implementation details that are different from that of a list. +This module aims to highlight those differences and show how they make a +deque useful as a LIFO stack and a FIFO queue. +""" from collections import deque diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 666cb08b..2beb70b7 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,3 +1,8 @@ +""" +Dictionaries are a mapping of keys to values. This module shows how to +do some simple things with this data structure. +""" + # Module-level constants _GPA_MIN = 0.0 _GPA_MAX = 4.0 diff --git a/ultimatepython/data_structures/list.py b/ultimatepython/data_structures/list.py index 75a32efd..2787c993 100644 --- a/ultimatepython/data_structures/list.py +++ b/ultimatepython/data_structures/list.py @@ -1,3 +1,10 @@ +""" +Lists are a sequence of values that can be modified at runtime. This +module shows how lists are created, iterated, accessed, extended +and shortened. +""" + + def main(): # This is a list of strings where # "a" is a string at index 0 and diff --git a/ultimatepython/data_structures/set.py b/ultimatepython/data_structures/set.py index 06d714a1..883b2605 100644 --- a/ultimatepython/data_structures/set.py +++ b/ultimatepython/data_structures/set.py @@ -1,3 +1,10 @@ +""" +Sets are an unordered collection of unique values that can be modified at +runtime. This module shows how sets are created, iterated, accessed, +extended and shortened. +""" + + def main(): # Let's define one `set` for starters simple_set = {0, 1, 2} diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 68b001c1..45b38431 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -1,3 +1,9 @@ +""" +Strings are an ordered collection of unicode characters that cannot be +modified at runtime. This module shows how strings are created, iterated +and accessed. +""" + # Module-level constants _DELIMITER = " | " diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 6f42cad9..330ba947 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -1,3 +1,9 @@ +""" +Tuples are an ordered collection of values that cannot be modified at +runtime. This module shows how tuples are created, iterated and accessed. +""" + + def main(): # This is a tuple of integers immutable = (1, 2, 3, 4) From c82876e5722efb74d8ace0c4dd164475204be528 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 27 Oct 2020 07:17:36 -0700 Subject: [PATCH 546/553] Add module docs for syntax modules (#31) * Add module docs for syntax modules * Fixup loop docs --- ultimatepython/syntax/conditional.py | 6 ++++++ ultimatepython/syntax/expression.py | 6 ++++++ ultimatepython/syntax/function.py | 8 ++++++++ ultimatepython/syntax/loop.py | 9 +++++++++ ultimatepython/syntax/variable.py | 7 +++++++ 5 files changed, 36 insertions(+) diff --git a/ultimatepython/syntax/conditional.py b/ultimatepython/syntax/conditional.py index 5ef8f45b..6daa6e6d 100644 --- a/ultimatepython/syntax/conditional.py +++ b/ultimatepython/syntax/conditional.py @@ -1,3 +1,9 @@ +""" +This module shows how to use if blocks, if-else blocks and if-elif-else +blocks to decide which lines of code to run (and skip). +""" + + def main(): x = 1 x_add_two = x + 2 diff --git a/ultimatepython/syntax/expression.py b/ultimatepython/syntax/expression.py index 45f80c11..b0fd0d13 100644 --- a/ultimatepython/syntax/expression.py +++ b/ultimatepython/syntax/expression.py @@ -1,3 +1,9 @@ +""" +This module shows how to create new integers by applying math expressions +on existing integers. +""" + + def main(): # This is a simple integer x = 1 diff --git a/ultimatepython/syntax/function.py b/ultimatepython/syntax/function.py index e868e3e2..4b1b634b 100644 --- a/ultimatepython/syntax/function.py +++ b/ultimatepython/syntax/function.py @@ -1,3 +1,11 @@ +""" +Functions allow us to consolidate simple / complex code into a single +block that can be invoked with specific parameters. This module defines +a simple function and a composite function that uses the simple function +in an interesting way. +""" + + def add(x, y): """Add two objects together to produce a new object. diff --git a/ultimatepython/syntax/loop.py b/ultimatepython/syntax/loop.py index a547427e..091e8a70 100644 --- a/ultimatepython/syntax/loop.py +++ b/ultimatepython/syntax/loop.py @@ -1,3 +1,12 @@ +""" +Loops are to expressions as multiplication is to addition. They help us +run the same code many times until a certain condition is not met. This +module shows how to use the for-loop and while-loop, while also showing +how `continue` and `break` give us fine-grained control on a +loop's lifecycle. +""" + + def main(): # This is a `for` loop that iterates on values 0..4 and adds each # value to `total`. It leverages the `range` iterator. Providing diff --git a/ultimatepython/syntax/variable.py b/ultimatepython/syntax/variable.py index dc005c57..23bcf783 100644 --- a/ultimatepython/syntax/variable.py +++ b/ultimatepython/syntax/variable.py @@ -1,3 +1,10 @@ +""" +Variables allow us to store values in named records that can be used in +a program. This module shows how to define variables and add assertions +about the state of each defined variable. +""" + + def main(): # Here are the main literal types to be aware of a = 1 From a264200570c6526995762c35270b6cfe968c7ea2 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 28 Oct 2020 22:55:14 -0700 Subject: [PATCH 547/553] Enhance docs of advanced modules --- ultimatepython/advanced/async.py | 10 +++++----- ultimatepython/advanced/benchmark.py | 3 ++- ultimatepython/advanced/mixin.py | 2 +- ultimatepython/advanced/regex.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ultimatepython/advanced/async.py b/ultimatepython/advanced/async.py index aac4febc..cf1b7ae7 100644 --- a/ultimatepython/advanced/async.py +++ b/ultimatepython/advanced/async.py @@ -1,9 +1,9 @@ """ -Asynchronous programming is a relatively new concept in Python 3.x. -This module aims to highlight how it could be used in the context of a -scheduler which runs a fire-and-forget operation for starting jobs. In -the real world, it takes time for a scheduler to start a job (i.e. hit -an API endpoint, ask the operating system for resources) so we assume +Concurrent programming with an event loop is a relatively new concept in +Python 3.x. This module aims to highlight how it could be used in the +context of a scheduler which runs a fire-and-forget operation for starting +jobs. In the real world, it takes time for a scheduler to start a job (i.e. +hit an API endpoint, ask the operating system for resources) so we assume that starting a job has some intrinsic delay. """ import asyncio diff --git a/ultimatepython/advanced/benchmark.py b/ultimatepython/advanced/benchmark.py index 2d7a8628..386f95bd 100644 --- a/ultimatepython/advanced/benchmark.py +++ b/ultimatepython/advanced/benchmark.py @@ -2,7 +2,8 @@ As programs increase in size, they have the risk of getting slow as new features are added and extra layers of complexity to the main features. Benchmarking is an approach that helps developers use profiling metrics -and their code intuition to optimize programs further. +and their code intuition to optimize programs further. This module uses +cProfile to compare the performance of two functions with each other. """ import cProfile import io diff --git a/ultimatepython/advanced/mixin.py b/ultimatepython/advanced/mixin.py index 4969da41..06473b18 100644 --- a/ultimatepython/advanced/mixin.py +++ b/ultimatepython/advanced/mixin.py @@ -3,7 +3,7 @@ for a developer to conform to an opaque interface specification. In this module, we have one request handler interface and two request handler mixins to illustrate how mixins can make our lives easier when defining -concrete classes that conform to the interface. +concrete classes. """ from abc import ABC, abstractmethod from dataclasses import dataclass diff --git a/ultimatepython/advanced/regex.py b/ultimatepython/advanced/regex.py index 0bb5463c..3f23ecba 100644 --- a/ultimatepython/advanced/regex.py +++ b/ultimatepython/advanced/regex.py @@ -1,8 +1,8 @@ """ Regular expressions is a way to search text efficiently. Implementing them is difficult but thankfully Python provides a package for us to use them -easily. This module shows a few examples of use the `re` package to search -predefined text snippets stored in module-level constants. +easily. This module shows a few examples of how to use the `re` package to +search predefined text snippets stored in module-level constants. """ import re From 6cfb2378cfec92e7a254d99bb4a3a8f28f490d58 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 29 Oct 2020 23:35:52 -0700 Subject: [PATCH 548/553] Fix docs in basic_class lesson --- ultimatepython/classes/basic_class.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index c1a18ce1..2d3cbf9e 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,7 +1,7 @@ """ A basic class comprises of state and methods. This module defines -a simple definition of a car class and creates some car instances -for demonstration purposes. +a simple definition of a car class, creates a car instance and +uses it for demonstration purposes. """ from inspect import isfunction, ismethod, signature From 0508eec9d04c532a53e6eb3b2776fcd29660c491 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 30 Oct 2020 09:38:02 -0700 Subject: [PATCH 549/553] Enhance module docs of classes modules (#32) --- ultimatepython/classes/abstract_class.py | 5 ++--- ultimatepython/classes/basic_class.py | 7 ++++--- ultimatepython/classes/exception_class.py | 6 ++++-- ultimatepython/classes/iterator_class.py | 8 ++++---- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ultimatepython/classes/abstract_class.py b/ultimatepython/classes/abstract_class.py index 7d68103f..601498dd 100644 --- a/ultimatepython/classes/abstract_class.py +++ b/ultimatepython/classes/abstract_class.py @@ -2,9 +2,8 @@ Abstract class is an extension of a basic class. Like a basic class, an abstract class has methods and state. Unlike a basic class, it inherits the `ABC` class and has at least one `abstractmethod`. That means we -cannot create an instance directly from its constructor. Also, all concrete -classes must implement `abstractmethod` definitions. In this module, we -will see an example of an abstract class and a concrete class. +cannot create an instance directly from its constructor. In this module, +we will see an example of an abstract class and a concrete class. For more about abstract classes, click the link below: diff --git a/ultimatepython/classes/basic_class.py b/ultimatepython/classes/basic_class.py index 2d3cbf9e..063a1812 100644 --- a/ultimatepython/classes/basic_class.py +++ b/ultimatepython/classes/basic_class.py @@ -1,7 +1,8 @@ """ -A basic class comprises of state and methods. This module defines -a simple definition of a car class, creates a car instance and -uses it for demonstration purposes. +A class is made up of state and methods. This allows us to combine data +and function-like code as one logical entity. This module defines a basic +definition of a car class, creates a car instance and uses it for +demonstration purposes. """ from inspect import isfunction, ismethod, signature diff --git a/ultimatepython/classes/exception_class.py b/ultimatepython/classes/exception_class.py index 71c7a987..0d71bfbd 100644 --- a/ultimatepython/classes/exception_class.py +++ b/ultimatepython/classes/exception_class.py @@ -1,7 +1,9 @@ """ Exception classes are used to indicate that something has gone wrong with -the program at runtime. This module defines a handful of custom exception -classes and shows how they can be used in the context of a function. +the program at runtime. Functions use the `raise` keyword, if an error is +anticipated, and specify the exception class they intend to throw. This +module defines a handful of custom exception classes and shows how they +can be used in the context of a function. """ diff --git a/ultimatepython/classes/iterator_class.py b/ultimatepython/classes/iterator_class.py index 41216cc4..88ccae41 100644 --- a/ultimatepython/classes/iterator_class.py +++ b/ultimatepython/classes/iterator_class.py @@ -1,8 +1,8 @@ """ -Iterator classes implement the __iter__ and __next__ magic methods. This -module defines an employee iterator class that goes iterates through each -employee in a hierarchy one-by-one. This module also shows how a similar -thing can be achieved with a generator function. +Iterator classes implement the `__iter__` and `__next__` magic methods. +This module defines an employee iterator class that goes iterates through +each employee in a hierarchy one-by-one. This module also shows how a +similar thing can be achieved with a generator function. """ # Module-level constants From eb6bfbb7c150a20ad152775911a2ef401efea03a Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Fri, 30 Oct 2020 09:51:10 -0700 Subject: [PATCH 550/553] Enhance module docs in DS modules (#33) --- ultimatepython/data_structures/comprehension.py | 4 ++-- ultimatepython/data_structures/deque.py | 8 ++++---- ultimatepython/data_structures/dict.py | 3 ++- ultimatepython/data_structures/string.py | 4 ++-- ultimatepython/data_structures/tuple.py | 3 ++- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ultimatepython/data_structures/comprehension.py b/ultimatepython/data_structures/comprehension.py index 8d60993b..69086642 100644 --- a/ultimatepython/data_structures/comprehension.py +++ b/ultimatepython/data_structures/comprehension.py @@ -1,6 +1,6 @@ """ -This module shows comprehensions where we make lists, tuples, sets and -dictionaries by looping through iterators. +This module shows one-liner comprehensions where we make lists, tuples, +sets and dictionaries by looping through iterators. """ diff --git a/ultimatepython/data_structures/deque.py b/ultimatepython/data_structures/deque.py index 7cdf61a5..98fceb47 100644 --- a/ultimatepython/data_structures/deque.py +++ b/ultimatepython/data_structures/deque.py @@ -1,8 +1,8 @@ """ -A deque functions similarly to all of the other sequential data structures -but has some implementation details that are different from that of a list. -This module aims to highlight those differences and show how they make a -deque useful as a LIFO stack and a FIFO queue. +A deque is similar to all of the other sequential data structures but +has some implementation details that are different from other sequences +like a list. This module highlights those differences and shows how +a deque can be used as a LIFO stack and a FIFO queue. """ from collections import deque diff --git a/ultimatepython/data_structures/dict.py b/ultimatepython/data_structures/dict.py index 2beb70b7..809c92e8 100644 --- a/ultimatepython/data_structures/dict.py +++ b/ultimatepython/data_structures/dict.py @@ -1,6 +1,7 @@ """ Dictionaries are a mapping of keys to values. This module shows how to -do some simple things with this data structure. +access, modify, remove and extend key-value pairs with this data +structure. """ # Module-level constants diff --git a/ultimatepython/data_structures/string.py b/ultimatepython/data_structures/string.py index 45b38431..a58ebae1 100644 --- a/ultimatepython/data_structures/string.py +++ b/ultimatepython/data_structures/string.py @@ -1,7 +1,7 @@ """ Strings are an ordered collection of unicode characters that cannot be -modified at runtime. This module shows how strings are created, iterated -and accessed. +modified at runtime. This module shows how strings are created, iterated, +accessed and concatenated. """ # Module-level constants diff --git a/ultimatepython/data_structures/tuple.py b/ultimatepython/data_structures/tuple.py index 330ba947..3dc2da1d 100644 --- a/ultimatepython/data_structures/tuple.py +++ b/ultimatepython/data_structures/tuple.py @@ -1,6 +1,7 @@ """ Tuples are an ordered collection of values that cannot be modified at -runtime. This module shows how tuples are created, iterated and accessed. +runtime. This module shows how tuples are created, iterated, accessed +and combined. """ From a57081cd9b8ee17d88a80da98bd28c36c226e1a0 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Tue, 3 Nov 2020 09:56:24 -0800 Subject: [PATCH 551/553] Add Chinese README content (#34) * Add Chinese README content * Update list and tuple wording * Add a few more changes to README --- README.ko.md | 3 +- README.md | 3 +- README.zh_tw.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 README.zh_tw.md diff --git a/README.ko.md b/README.ko.md index 2d06d6ae..7bdb8008 100644 --- a/README.ko.md +++ b/README.ko.md @@ -13,7 +13,8 @@ print("Ultimate Python 학습 가이드") ``` [English](README.md) | -[한국어](README.ko.md) +[한국어](README.ko.md) | +[中文](README.zh_tw.md) ## 이 학습 가이드를 만든 이유 diff --git a/README.md b/README.md index 40380915..5af65a7b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ print("Ultimate Python study guide") ``` [English](README.md) | -[한국어](README.ko.md) +[한국어](README.ko.md) | +[中文](README.zh_tw.md) ## Motivation diff --git a/README.zh_tw.md b/README.zh_tw.md new file mode 100644 index 00000000..97201b6f --- /dev/null +++ b/README.zh_tw.md @@ -0,0 +1,124 @@ +# Ultimate Python 學習大綱 + +[![CircleCI](https://img.shields.io/circleci/build/github/huangsam/ultimate-python)](https://circleci.com/gh/huangsam/ultimate-python) +[![Code Coverage](https://img.shields.io/codecov/c/github/huangsam/ultimate-python)](https://codecov.io/gh/huangsam/ultimate-python) +[![Quality Gate Status](https://img.shields.io/sonar/quality_gate/huangsam_ultimate-python?server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=huangsam_ultimate-python) +[![License](https://img.shields.io/github/license/huangsam/ultimate-python)](https://github.com/huangsam/ultimate-python/blob/master/LICENSE) +[![r/Python](https://img.shields.io/reddit/subreddit-subscribers/Python)](https://www.reddit.com/r/Python/comments/inllmf/ultimate_python_study_guide/) + +Ultimate Python 學習大綱 - 適用於新手和專業人士。:snake: :snake: :snake: + +```python +print("Ultimate Python 學習大綱") +``` + +[English](README.md) | +[한국어](README.ko.md) | +[中文](README.zh_tw.md) + +## 動力 + +我為了分享過去五年作為一個學生、大公司員工、以及開源(例如 Celery 和 Full Stack Python) 貢獻者所習得的知識而創 +建了這個代碼倉庫。我期待更多人能抱持熱忱並開始一段與Python的美好旅程。:mortar_board: + +## 目標 + +這是創建本指南的主要目標: + +:trophy: **為喜歡動手學習的Python新手提供資源。** 本存儲庫集合了不同題目的獨立模組範例,而每個模組可以獨立在普通 +終端機 (Terminal),IDE(如PyCharm)或者瀏覽器(如Repl.it)中運行。範例中的註解都經過精心編寫,引導讀者逐步了解程 +式流程。在不刪除主例程(main)並在修改後成功運行大前題下,我鼓勵讀者修改源代碼作練習。 + +:trophy: **為想重溫Python核心概念的程式員提供指南。** 本存儲庫主要借助內置庫(builtin libraries) 作重溫工具, +故不需額外安裝開源庫(如`sqlalchemy`,`requests`, `pandas`)。但是,如果您的目標是成為一個真正的Python +達人(Pythonista),那麼我會鼓勵您閱讀這些源代碼,而激發靈感。 + +## 學習之旅 + +[![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) + +單擊上面的徽章就可在瀏覽器中啟動工作環境,而無需在電腦上額外安裝Git和Python。當你完成啟動,請複製這存儲庫。 +當你可以開啟你所複製存儲庫後,您就準備好Python學習之旅!善用每個模組, 請細讀註解並嘗試運行模組代碼。 + +有兩種運行模組的方式: + +1. 運行單一模組:`python ultimatepython/syntax/variable.py` +2. 運行所有模組:`python runner.py` + +## 目錄 + +:books: = 外部資源, +:cake: = 入門題目, +:exploding_head: = 進階題目 + +1. **關於 Python** + - 概述:[什麼是 Python](https://github.com/trekhleb/learn-python/blob/master/src/getting_started/what_is_python.md) (:books:, :cake:) + - 設計理念:[Python之格言](https://www.python.org/dev/peps/pep-0020/) (:books:) + - 樣式指南:[Python代碼樣式指南](https://www.python.org/dev/peps/pep-0008/) (:books:, :exploding_head:) + - 數據模型:[數據模型](https://docs.python.org/3/reference/datamodel.html) (:books:, :exploding_head:) + - 標準庫:[Python標準庫](https://docs.python.org/3/library/) (:books:, :exploding_head:) + - 內置函式:[內置函式](https://docs.python.org/3/library/functions.html) (:books:) +2. **語法** + - 變數:[內置值](ultimatepython/syntax/variable.py) (:cake:) + - 運算式:[數值運算](ultimatepython/syntax/expression.py) (:cake:) + - 條件運算式:[if | if-else | if-elif-else](ultimatepython/syntax/conditional.py) (:cake:) + - 迴圈:[for迴圈 | while迴圈](ultimatepython/syntax/loop.py) (:cake:) + - 定義函式:[def | lambda](ultimatepython/syntax/function.py) (:cake:) +3. **資料結構** + - 列表:[列表操作](ultimatepython/data_structures/list.py) (:cake:) + - 元組:[元組操作](ultimatepython/data_structures/tuple.py) + - 集合:[集合操作](ultimatepython/data_structures/set.py) + - 字典:[字典操作](ultimatepython/data_structures/dict.py) (:cake:) + - 綜合:[list | tuple | set | dict](ultimatepython/data_structures/comprehension.py) + - 字串:[字串操作](ultimatepython/data_structures/string.py) (:cake:) + - 雙端隊列:[deque](ultimatepython/data_structures/deque.py) (:exploding_head:) + - 時間複雜度:[cPython操作](https://wiki.python.org/moin/TimeComplexity) (:books:, :exploding_head:) +4. **類別** + - 基本類別:[基本定義](ultimatepython/classes/basic_class.py) (:cake:) + - 抽象類別:[抽象定義](ultimatepython/classes/abstract_class.py) + - 異常類別:[異常定義](ultimatepython/classes/exception_class.py) + - 迭代類別:[迭代器定義](ultimatepython/classes/iterator_class.py) (:exploding_head:) +5. **進階技巧** + - 裝飾器:[Decorator definition | wraps](ultimatepython/advanced/decorator.py) (:exploding_head:) + - 資源管理器:[Context managers](ultimatepython/advanced/context_manager.py) (:exploding_head:) + - 方法解析順序:[mro](ultimatepython/advanced/mro.py) (:exploding_head:) + - Mixin:[Mixin定義](ultimatepython/advanced/mixin.py) (:exploding_head:) + - 元類:[Metaclass定義](ultimatepython/advanced/meta_class.py) (:exploding_head:) + - 執行緒:[ThreadPoolExecutor](ultimatepython/advanced/thread.py) (:exploding_head:) + - 異步:[async | await](ultimatepython/advanced/async.py) (:exploding_head:) + - 弱引用:[weakref](ultimatepython/advanced/weak_ref.py) (:exploding_head:) + - 基準:[cProfile | pstats](ultimatepython/advanced/benchmark.py) (:exploding_head:) + - 模擬:[MagicMock | PropertyMock | patch](ultimatepython/advanced/mocking.py) (:exploding_head:) + - 正規表示式:[search | findall | match | fullmatch](ultimatepython/advanced/regex.py) (:exploding_head:) + - 數據格式:[json | xml | csv](ultimatepython/advanced/data_format.py) (:exploding_head:) + +## 額外資源 + +:necktie: = 面試資源, +:test_tube: = 代碼範例, +:brain: = 項目構想 + +### GitHub儲存庫 + +通過閱讀其他備受尊重的資源來繼續學習。 + +- [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python) (:necktie:, :test_tube:) +- [faif/python-patterns](https://github.com/faif/python-patterns) (:necktie:, :test_tube:) +- [geekcomputers/Python](https://github.com/geekcomputers/Python) (:test_tube:) +- [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) (:test_tube:) +- [karan/Projects](https://github.com/karan/Projects) (:brain:) +- [MunGell/awesome-for-beginners](https://github.com/MunGell/awesome-for-beginners) (:brain:) +- [vinta/awesome-python](https://github.com/vinta/awesome-python) +- [academic/awesome-datascience](https://github.com/academic/awesome-datascience) +- [josephmisiti/awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning) +- [ZuzooVn/machine-learning-for-software-engineers](https://github.com/ZuzooVn/machine-learning-for-software-engineers) + +### 互動練習 + +繼續練習才能使您的編碼技能不會生疏。 + +- [leetcode.com](https://leetcode.com/) (:necktie:) +- [hackerrank.com](https://www.hackerrank.com/) (:necktie:) +- [kaggle.com](https://www.kaggle.com/) (:brain:) +- [exercism.io](https://exercism.io/) +- [projecteuler.net](https://projecteuler.net/) From 306da1d3418d4e18c5518671c46e53b78062893f Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 4 Nov 2020 08:44:55 -0800 Subject: [PATCH 552/553] Normalize punctuation in Chinese README --- README.zh_tw.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.zh_tw.md b/README.zh_tw.md index 97201b6f..255efb33 100644 --- a/README.zh_tw.md +++ b/README.zh_tw.md @@ -18,7 +18,7 @@ print("Ultimate Python 學習大綱") ## 動力 -我為了分享過去五年作為一個學生、大公司員工、以及開源(例如 Celery 和 Full Stack Python) 貢獻者所習得的知識而創 +我為了分享過去五年作為一個學生,大公司員工,以及開源(例如 Celery 和 Full Stack Python)貢獻者所習得的知識而創 建了這個代碼倉庫。我期待更多人能抱持熱忱並開始一段與Python的美好旅程。:mortar_board: ## 目標 @@ -26,19 +26,19 @@ print("Ultimate Python 學習大綱") 這是創建本指南的主要目標: :trophy: **為喜歡動手學習的Python新手提供資源。** 本存儲庫集合了不同題目的獨立模組範例,而每個模組可以獨立在普通 -終端機 (Terminal),IDE(如PyCharm)或者瀏覽器(如Repl.it)中運行。範例中的註解都經過精心編寫,引導讀者逐步了解程 -式流程。在不刪除主例程(main)並在修改後成功運行大前題下,我鼓勵讀者修改源代碼作練習。 +終端機(Terminal),IDE(如PyCharm)或者瀏覽器(如Repl.it)中運行。範例中的註解都經過精心編寫,引導讀者逐步了解程 +式流程。在不刪除主例程(main)並在修改後成功運行大前題下,我鼓勵讀者修改源代碼作練習。 -:trophy: **為想重溫Python核心概念的程式員提供指南。** 本存儲庫主要借助內置庫(builtin libraries) 作重溫工具, -故不需額外安裝開源庫(如`sqlalchemy`,`requests`, `pandas`)。但是,如果您的目標是成為一個真正的Python -達人(Pythonista),那麼我會鼓勵您閱讀這些源代碼,而激發靈感。 +:trophy: **為想重溫Python核心概念的程式員提供指南。** 本存儲庫主要借助內置庫(builtin libraries)作重溫工具, +故不需額外安裝開源庫(如`sqlalchemy`,`requests`,`pandas`)。但是,如果您的目標是成為一個真正的Python +達人(Pythonista),那麼我會鼓勵您閱讀這些源代碼,而激發靈感。 ## 學習之旅 [![Run on Repl.it](https://repl.it/badge/github/huangsam/ultimate-python)](https://repl.it/github/huangsam/ultimate-python) 單擊上面的徽章就可在瀏覽器中啟動工作環境,而無需在電腦上額外安裝Git和Python。當你完成啟動,請複製這存儲庫。 -當你可以開啟你所複製存儲庫後,您就準備好Python學習之旅!善用每個模組, 請細讀註解並嘗試運行模組代碼。 +當你可以開啟你所複製存儲庫後,您就準備好Python學習之旅!善用每個模組,請細讀註解並嘗試運行模組代碼。 有兩種運行模組的方式: From b2195ba2fdf623cd283cb3219004726e3140b725 Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Wed, 4 Nov 2020 09:56:56 -0800 Subject: [PATCH 553/553] Qualify Chinese translation in README links --- README.ko.md | 2 +- README.md | 2 +- README.zh_tw.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.ko.md b/README.ko.md index 7bdb8008..88dce180 100644 --- a/README.ko.md +++ b/README.ko.md @@ -14,7 +14,7 @@ print("Ultimate Python 학습 가이드") [English](README.md) | [한국어](README.ko.md) | -[中文](README.zh_tw.md) +[繁体中文](README.zh_tw.md) ## 이 학습 가이드를 만든 이유 diff --git a/README.md b/README.md index 5af65a7b..809ff9c8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ print("Ultimate Python study guide") [English](README.md) | [한국어](README.ko.md) | -[中文](README.zh_tw.md) +[繁体中文](README.zh_tw.md) ## Motivation diff --git a/README.zh_tw.md b/README.zh_tw.md index 255efb33..83ba7651 100644 --- a/README.zh_tw.md +++ b/README.zh_tw.md @@ -14,7 +14,7 @@ print("Ultimate Python 學習大綱") [English](README.md) | [한국어](README.ko.md) | -[中文](README.zh_tw.md) +[繁体中文](README.zh_tw.md) ## 動力